Compare commits

..

355 Commits

Author SHA1 Message Date
Max Brunsfeld
0c7973d274 zed 0.89.1 2023-06-01 09:17:57 -07:00
Julia
0071a4598a Avoid blocking forever on startup if config files do not exist (#2549)
The files will still get created if the user opens their settings and
saves, otherwise everything will transparently work

Release Notes:

- Fixed an issue where a missing settings file would cause a hang on
startup
([#1590](https://github.com/zed-industries/community/issues/1590)).
2023-06-01 09:17:34 -07:00
Kirill Bulatov
d7e4544638 Remove wrong assertion 2023-06-01 17:39:55 +03:00
Max Brunsfeld
432d407539 v0.89.x preview 2023-05-31 14:44:29 -07:00
Mikayla Maki
6f4fd74f43 Add a dock-keybinding-changed one-time notification (#2546)
This code is intended to be removed at a later date.

Release Notes:

- N/A
2023-05-31 14:28:12 -07:00
Mikayla Maki
bf2016adf5 Remove version check 2023-05-31 14:23:56 -07:00
Mikayla Maki
27ef0e2b52 De-morgans properly 2023-05-31 14:21:38 -07:00
Mikayla Maki
45b42c512d fmt 2023-05-31 14:17:27 -07:00
Mikayla Maki
705e36827c add version check 2023-05-31 14:16:37 -07:00
Mikayla Maki
b875d4ed40 Remove silly panic 2023-05-31 14:10:50 -07:00
Mikayla Maki
ed0b9acb0a Add panic if version is 0.91 2023-05-31 14:06:36 -07:00
Mikayla Maki
ef80b539d1 Fix notification styling, minimize database reads 2023-05-31 13:56:41 -07:00
Max Brunsfeld
62660f2766 Fix more issues with panels and zoom (#2545)
Release Notes:

* Fixed the behavior of panel buttons when their panel is open. Show the
key binding for closing the containing dock (preview only).
* Improved the styling of zoomed panels and panes, giving them a
stronger border, and color the zoom button with an "active" style
(preview only).
* Improved the stability of panels' zoom state. Close their dock instead
of resetting their zoom state when dismissing panels to reveal other
items (preview only).
2023-05-31 13:17:12 -07:00
Max Brunsfeld
193474a346 🎨 2023-05-31 13:07:20 -07:00
Max Brunsfeld
c48fed26bd When revealing items, zoom-out any inactive panes 2023-05-31 13:05:50 -07:00
Max Brunsfeld
3f6aa94a5f Use active color for zoom button in a zoomed pane 2023-05-31 12:51:57 -07:00
Max Brunsfeld
893615236d Only include one border edge for zoomed panels 2023-05-31 12:45:55 -07:00
Nate Butler
1f72f9e18b Add un-hooked up zoomIcon styles 2023-05-31 12:45:55 -07:00
Nate Butler
a10933c063 Update pane/panel zoom styling 2023-05-31 12:45:55 -07:00
Max Brunsfeld
ac41564a8f Dismiss zoomed panels by closing their dock, not zooming them out 2023-05-31 12:45:55 -07:00
Sergii Onufriienko
c6672dbac2 Update theme license logic (#2541)
Use new theme file structure
```
themes/
  [theme_name]
    [theme_name]-common.ts
    [theme_name]-light.ts
    [theme_name]-dark.ts
    LICENSE
```

Release Notes: 
- N/A
2023-05-31 18:36:13 +01:00
Max Brunsfeld
76927b6d95 Make active panel's button close its dock 2023-05-31 10:29:59 -07:00
Mikayla Maki
c9820fde61 WIP: Add toast when users attempt to use shift-escape for the first time 2023-05-30 17:48:41 -07:00
Mikayla Maki
584e5f7958 Fix usability issues with new panel system. (#2544)
This PR updates the dock key bindings according to the following model:

There are three bits: 
Visible: Opened / closed.
Focus: Panel focused / center focused.
Zoom: Zoomed / Not zoomed.

Each of these variables is 'sticky' in that they won't effect each other
unless they need to. 'Zooming' a panel conceptually merges the visible
and focus bits.

cmd-shift-j/b/r have all been removed.

cmd-j/b/r have been updated to mean 'toggle visibility of a certain
dock', firing them should *always* reveal the panel to you (where you
last left it), or hide it, without moving focus (unless the focused
element is invisible). This means that, when the terminal panel is
zoomed, cmd-j has the same effect as ctrl-`

ctrl-` and cmd-shift-e now toggle a panel's focus, without updating the
zoom state of a panel. Toggling the focus of a zoomed panel causes it to
automatically hide itself, without losing the zoom bit.

When focused or made visible, panels which cannot be zoomed
automatically unzoom everything else so as to preserve user intent of
'show me this panel' and 'everything stays where it is if I don't take
an action'

Release Notes:

- cmd-shift-j/b/r have been removed.  (preview only)
- cmd-j/b/r unconditionally show or hide their associated dock,
respecting zoom settings. (preview only)
- ctrl-` and cmd-shift-e now retain zoom state. (preview only)
- Fixed a bug where terminal dock tab would always be in the active
state (preview only)
- Fixed a bug where terminals would not always open in the terminal
panel
- Changed the look of zoomed panels to fill more of the screen (preview
only)
2023-05-30 16:39:06 -07:00
Mikayla Maki
0122cd61c5 fmt 2023-05-30 16:33:42 -07:00
Mikayla Maki
685e8d7007 Seperate pane and panel styling
co-authored-by: max <max@zed.dev>
2023-05-30 16:23:16 -07:00
Mikayla Maki
9c707eff27 Rework new terminal actions 2023-05-30 16:10:07 -07:00
Mikayla Maki
e80ab5f096 Fix focus bug with new docks
co-authored-by: max <max@zed.dev>
2023-05-30 15:40:30 -07:00
Mikayla Maki
5e4a9abd09 Fix bug in panel button dispatch 2023-05-30 15:17:42 -07:00
Mikayla Maki
5065804388 WIP: iron out bugs in interaction between toggle and toggle focus key bindings
co-authored-by: max <max@zed.dev>
2023-05-30 14:55:49 -07:00
Julia
8f0fc918e9 TextStyle::default() ask system for known existing font family (#2542)
Rather than assuming a specific family exists, try a set of specific
names and if they fail, just grab any old font that the system reports
as existing

Closes
https://linear.app/zed-industries/issue/Z-445/thread-main-panicked-at-called-resultunwrap-on-an-err-value-could-not

Release Notes:

* Fixed crash that could happen if system did not have a specific
fallback font
2023-05-30 16:25:07 -04:00
Mikayla Maki
9c9af5ed94 Remove focus side effects from toggle dock commands
co-authored-by: max <max@zed.dev>
2023-05-30 12:32:00 -07:00
Joseph Lyons
efcf78cbe9 Update pull_request_template.md 2023-05-30 15:15:20 -04:00
Joseph T. Lyons
e9ce85ebc0 Scrape email from feedback editor (#2543)
Add release note lines here:

N/A
2023-05-30 15:13:35 -04:00
Joseph Lyons
501f9ab2c6 Scrape email from feedback editor 2023-05-30 15:06:35 -04:00
Mikayla Maki
1a23fe91b4 WIP: Remove focus side effects from toggle dock commands
co-authored-by: max <max@zed.dev>
2023-05-30 12:00:15 -07:00
Mikayla Maki
84f98f13c4 Seperate open and zoom bits conceptually for new panels
co-authored-by: max <max@zed.dev>
2023-05-30 11:52:14 -07:00
Julia
31516b7863 TextStyle::default() ask system for known existing font family
Rather than assuming a specific family exists, try a set of specific
names and if they fail, just grab any old font that the system reports
as existing

Co-Authored-By: Nathan Sobo <nathan@zed.dev>
2023-05-30 14:46:27 -04:00
Sergey Onufrienko
51c7078f47 chore: remove license_text 2023-05-30 18:16:50 +02:00
Sergey Onufrienko
4568f80f2e feat: read from LICENSE file 2023-05-30 18:12:31 +02:00
Sergey Onufrienko
14efc18eca refactor: add license file and check it exists 2023-05-30 18:11:54 +02:00
Sergey Onufrienko
05d1dd6b11 refactor: move common to theme folder 2023-05-30 16:53:12 +02:00
Sergey Onufrienko
ac63043c27 chore: move similar themes to one folder 2023-05-30 16:52:28 +02:00
Sergey Onufrienko
0d62e76cd6 refactor: colorSchemes 2023-05-30 15:52:32 +02:00
Antonio Scandurra
1fc9103b61 Remove methods taking &mut Workspace from Pane (#2540)
This pull request simplifies the `Pane` struct by replacing methods like
`Pane::add_item` that would previously take a `&mut Workspace` with
methods that take a `&mut self`. When access to the workspace is needed,
we now either emit an event from the `Pane` or directly move the method
to the `Workspace` struct.
2023-05-30 15:01:35 +02:00
Antonio Scandurra
bd494037bb Call ItemHandle::added_to_pane when an item is added to a pane 2023-05-30 14:46:41 +02:00
Sergey Onufrienko
2269c19169 chore: add LICENSE files 2023-05-30 14:06:19 +02:00
Nathan Sobo
8a851ad7f8 Fix annoying system prompt (#2538)
I was having fun, but it was too much. It makes the assistant too
annoying to be useful.

Release Notes:

Use `N/A` in this section if this item should be skipped in the release
notes.

Add release note lines here:

* Dialed back the system prompt on the assistant.
2023-05-29 22:05:40 -06:00
Nathan Sobo
063613bf4e Fix annoying system prompt
I was having fun, but it was too much. It makes the assistant too annoying to be useful.
2023-05-29 21:47:59 -06:00
Antonio Scandurra
88eb2b2163 Move history navigation logic to Workspace
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
2023-05-29 19:47:59 +02:00
Antonio Scandurra
6d3464fd1f Make Pane::add_item a proper Pane method
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
2023-05-29 19:16:22 +02:00
Kirill Bulatov
edf8e276af Improves project search panel shortcut handling (#2536)
* ESC (project_search::ToggleFocus) toggles focus from include/exclude
fields to the editor

* Cmd+Shift+F (workspace::NewSearch) can be triggered from the editor,
and moves focus to the query editor

Release Notes:

* Improved project search panel shortcut handling, allowing more actions
to trigger from panel elements
2023-05-29 17:06:38 +03:00
Kirill Bulatov
d593377e45 Improves project search panel shortcut handling
* ESC (project_search::ToggleFocus) toggles focus from
  include/exclude fields to the editor

* Cmd+Shift+F (workspace::NewSearch) can be triggered from the editor,
and moves focus to the query editor
2023-05-29 16:58:06 +03:00
Sergey Onufrienko
cf75b090d3 feat: organize themes by folders with LICENSE 2023-05-29 15:45:13 +02:00
Kirill Bulatov
986b02e217 Update LSP to the newest version (#2535)
Current `lsp-types:0.91.1` crate lacks inlay hints' definitions. Crate's
changelog is not very descriptive, but it appears that `0.92.1` could be
used:
https://github.com/gluon-lang/lsp-types/blob/master/CHANGELOG.md#v0921-2022-03-21
The latest is crate version is `0.94.0` (2023-02-08), the PR updates Zed
to the latest version.


Notable changes:
* workspace symbols may arrive unresolved if the corresponding client
capability is enabled:
https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#partialResults
Zed has this capability disabled, forcing all symbols to arrive
synchronously (?).

Resolve capabilities are important for inlay hints too, but I've not
found any code in Zed for that outside tests, so I'd love to learn more
and implement the resolution for workspace symbols separately.

* since LSP `3.17` (current), watch file changes can use relative glob
patterns:
https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_didChangeWatchedFiles

That seemed just a straightforward extra `match` to use the same Ruse
`Glob` to handle the relative path one.

Release Notes:

N/A
2023-05-29 12:49:37 +03:00
Kirill Bulatov
fdad1adaf6 Update LSP to the newest version 2023-05-28 21:55:08 +03:00
Max Brunsfeld
e4530471de Make worktree UpdatedEntries events fully describe all changes (#2533)
This PR makes the worktree's change events more useful in a few ways:

* The changes are now described by a cheaply clone-able collection, so
that they can be used in background tasks. Right now, I'm using a simple
Arc slice.
* The `UpdatedEntries` event now captures not only changes due to FS
changes, but also newly-loaded paths that are discovered during the
initial scan.
* The `UpdatedGitRepositories` event now includes repositories whose
work-dir changed but git dir didn't change. A boolean flag is included,
to indicate whether the git content changed.
* The `UpdatedEntries` and `UpdatedGitRepositories` events are now
*used* to compute the worktree's `UpdateWorktree` messages, used to sync
changes to guests. This unifies two closely-related code paths, and
makes the host more efficient when collaborating, because the
`UpdateWorktree` message computation used to require walking the entire
`entries` tree on every FS change.
2023-05-26 15:55:14 -07:00
Max Brunsfeld
1f42bfc1bd Include repositories with workdir changes in worktree UpdatedGitRepsositories event 2023-05-26 15:47:37 -07:00
Max Brunsfeld
2db57b5139 Adjust diagnostic transformation test to not wait for two buffer notifications 2023-05-26 15:47:37 -07:00
Max Brunsfeld
02b95ef320 Derive worktree update messages from existing change events 2023-05-26 15:47:37 -07:00
Kirill Bulatov
9cfe39f1aa Display external paths in history (#2534)
Fixes
https://linear.app/zed-industries/issue/Z-1793/file-finder-external-recent-paths-are-not-rendered-properly

Long paths get trimmed, but same do many of our other elements, such as
type definitions, so I think it's ok for now:

![image](https://github.com/zed-industries/zed/assets/2690773/b8b6588d-6d6b-42db-9085-c741a40b7adb)

Also, we seem to do a lot of odd diagnostics handling on every external
stdlib file opened:

![image](https://github.com/zed-industries/zed/assets/2690773/cd82c54e-7849-46fe-a01c-79bfc5402b7b)
Other external files also emit similar messages, but not that much.
@\mikayla-maki mentioned, that this was happening before, so can be
fixed separately.
The PR adds path printing to these logs.

Release Notes:

* Fixed external files not being displayed properly in the recently
opened list in the file finder panel
2023-05-27 01:27:24 +03:00
Kirill Bulatov
5fda9e934d Shorten full paths with ~ 2023-05-27 01:21:38 +03:00
Kirill Bulatov
3a3c1c5a5b Add a test
co-authored-by: Mikayla <mikayla@zed.dev>
2023-05-27 01:12:22 +03:00
Max Brunsfeld
6628c4df28 Store worktree changes in a sorted arc slice, not a HashMap
We don't need to look-up change types by an arbitrary key, but we
do need to iterate it. It would also be useful to be able to
cheaply clone the changes, to use them in a background task.
2023-05-26 09:24:15 -07:00
Max Brunsfeld
59bfd40679 Make stricter assertions about change events in random worktree test 2023-05-26 09:24:15 -07:00
Max Brunsfeld
f890eefdef Include paths loaded during initial scan in worktree UpdatedEntries event 2023-05-26 09:24:15 -07:00
Kirill Bulatov
cf2bbfc85a Better display labels for external files 2023-05-26 18:18:32 +03:00
Kirill Bulatov
508533ebb7 Track abs paths in history 2023-05-26 17:31:35 +03:00
Kirill Bulatov
2fdc960704 Properly display labels for currently opened external files 2023-05-26 15:44:44 +03:00
Kirill Bulatov
b75c27da6f Simplify selected index handling 2023-05-26 14:15:58 +03:00
Kirill Bulatov
364631a155 Add absolute paths to historic elements 2023-05-26 14:03:44 +03:00
Kirill Bulatov
128c19875d Fix duplicate item creation in project panel (#2532)
Closes
https://linear.app/zed-industries/issue/Z-1760/creating-a-new-file-with-the-same-name-as-an-existing-file-overwrites

Now every rename/creation editor won't do anything if a duplicate name
is submitted.

Release Notes:

* Fixed a bug when created in project panel items with duplicate names
were overwriting the old files
2023-05-26 10:14:50 +03:00
Kirill Bulatov
5501dd741c Fix duplicate item creation in project panel 2023-05-26 02:07:25 +03:00
Julia
7900d2a20a Multibuffer edit perf (#2531)
This took so much longer than I wanted, so glad to finally be rid of
this

Release Notes:
* Improved performance when editing many git-tracked files in a
multi-buffer at the same time
2023-05-25 15:01:05 -04:00
Julia
5e39ba596e Clean up final remaining code paths calling old diff update method 2023-05-25 14:41:09 -04:00
Julia
f40c498491 Fix tests 2023-05-25 14:29:28 -04:00
Julia
8d662edb6c Remove concept of git diff refresh from Item trait 2023-05-25 14:29:28 -04:00
Julia
cede296b04 Project level git diff recalc handling
This avoids an issue where in a many-buffer multi-buffer, each modified
buffer could complete its recalc independently, causing a cascade of
repeated notifies

Now all recalcs started at the same time must complete before
 A: Starting another recalc pass
 B: The master notify occurring

Each buffer can still show its new diff if something else triggers it
to notify earlier, this is desirable and does not have the same negative
effects as the notify cascade as those re-layouts would need to happen
anyway

Co-Authored-By: Max Brunsfeld <max@zed.dev>
2023-05-25 14:10:27 -04:00
Max Brunsfeld
54421b11f3 wip 2023-05-25 14:10:27 -04:00
Kirill Bulatov
ae3bdd755e Support OnTypeFormatting LSP request (#2517)
Supports
https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_onTypeFormatting

rust-analyzer uses this feature to add matching brackets semantically,
e.g.
before:
![Screenshot 2023-05-23 at 17 46
42](https://github.com/zed-industries/zed/assets/2690773/020e8448-23e6-4a38-8dbb-c9edf18062f7)

after:
![Screenshot 2023-05-23 at 17 46
49](https://github.com/zed-industries/zed/assets/2690773/4d140af3-aca6-451d-ac61-e2a9bb31caea)

`use_on_type_format` settings entry was added, enabled by default, to
disable the new feature.

Release Notes:

* Support `OnTypeFormatting` LSP protocol feature, allowing
rust-analyzer to add matching brackets
2023-05-25 18:35:47 +03:00
Kirill Bulatov
739d5ca373 Have proper undo for both client and host 2023-05-25 18:29:57 +03:00
Antonio Scandurra
24098c561d Prevent bottom dock from becoming too large or too small (#2529)
Fixes
https://linear.app/zed-industries/issue/Z-1834/bottom-dock-can-be-dragged-down-far-enough-such-that-the-tab-bar-is

Release Notes:

- Improved the resize experience for the bottom dock, preventing it from
becoming too large or too small. (preview-only)
2023-05-25 15:44:23 +02:00
Antonio Scandurra
4185a178e7 Prevent bottom dock from becoming too large or too small 2023-05-25 15:29:59 +02:00
Antonio Scandurra
f549ada54f Restore the ability to search terminals in the terminal panel (#2528)
Fixes
https://linear.app/zed-industries/issue/Z-1812/find-in-terminal-panel-is-broken

![CleanShot 2023-05-25 at 15 18
01@2x](https://github.com/zed-industries/zed/assets/482957/ddb33b61-d253-4de1-961f-14b24aaa3e46)

This also removes navigation controls from the terminal panel, given
that terminals don't make use of that feature anyway. When the toolbar
is empty, we'll avoid showing it altogether.

![CleanShot 2023-05-25 at 15 17
26@2x](https://github.com/zed-industries/zed/assets/482957/52419f2c-bca0-494a-a9b3-88e183b4c12f)


Release Notes:

- Fixed a regression that was preventing the terminal panel from being
searched.
2023-05-25 15:28:32 +02:00
Antonio Scandurra
aa8c0475b1 Don't show navigation controls for terminal panel 2023-05-25 15:15:20 +02:00
Kirill Bulatov
e2ff829f98 Use Transaction instead of ProjectTransaction
Co-Authored-By: Antonio Scandurra <antonio@zed.dev>
2023-05-25 16:12:14 +03:00
Antonio Scandurra
414b97adce Add search bar to terminal panel 2023-05-25 14:46:10 +02:00
Antonio Scandurra
dba7ec4a35 Prevent pane from being erroneously zoomed when toggling the outline pane (#2527)
Fixes
https://linear.app/zed-industries/issue/Z-1818/toggling-the-outline-pane-causes-the-pane-to-zoom

Add release note lines here:

- Fixed a bug that could cause panes to be erroneously zoomed when
toggling modals. (preview-only)
2023-05-25 14:10:08 +02:00
Antonio Scandurra
c2f3ac24a4 Prevent zoom from being restored when focusing a modal 2023-05-25 12:11:11 +02:00
Antonio Scandurra
7e1ea8f274 Remove stray dbg! 2023-05-25 12:04:41 +02:00
Kirill Bulatov
aa58d0fd77 Do not send edits over the wire 2023-05-25 12:55:44 +03:00
Kirill Bulatov
f812151840 Add integration tests 2023-05-25 09:45:50 +03:00
Kirill Bulatov
eca6d2b597 Process remote format typing also 2023-05-25 09:45:50 +03:00
Kirill Bulatov
58a56bdda2 Always use server formatting settings 2023-05-25 09:45:50 +03:00
Kirill Bulatov
d1f4b60fa1 Allow to disable the new feature 2023-05-25 09:45:50 +03:00
Kirill Bulatov
b9dabb165e Use formatting options 2023-05-25 09:45:50 +03:00
Kirill Bulatov
3327e8a6dd Support remote sessions 2023-05-25 09:45:50 +03:00
Kirill Bulatov
f6d7b3d2e8 Send and handle OnTypeFormatting LSP request 2023-05-25 09:45:50 +03:00
Nate Butler
793486b2e8 Update Diff Modified Color (#2526)
For some reason the yellow I used for the modified color in light themes
was really light
.
Release Notes:

* Improved the contrast of diff modified color in the editor in light
themes.
2023-05-24 23:50:37 -04:00
Joseph Lyons
3f79b0c7cc Don't hardcode dates into script 2023-05-24 17:39:02 -04:00
Joseph Lyons
cc3186cb4a Add script to print all prs merged between a previous date and now 2023-05-24 16:58:56 -04:00
Nathan Sobo
0b7d095c0a Introduce the ability to talk to GPT-4 within buffers on preview and dev (#2524)
This adds basic ability to interact with OpenAI inside a buffer.

Release Notes:

* If`OPENAI_API_KEY` is defined in your environment, you can now run the
`ai: assist` command to pass the text of your current buffer to GPT-4.
If you're editing a file with a `.zmd` extension, you can also invoke
the model with `cmd-enter.` (preview-only)
2023-05-24 12:00:43 -06:00
Joseph Lyons
37d35db3d7 v0.89.x dev 2023-05-24 13:22:00 -04:00
Nathan Sobo
26051de160 Clean up 2023-05-24 11:08:00 -06:00
Max Brunsfeld
a1a66b8da4 Make toggle dock actions appear in the command palette (#2525)
This makes the `Toggle{Left,Right,Bottom}Dock` actions deserializable
from empty JSON, so that they can be constructed for the command
palette. It also fixes a bug in GPUI's `available_actions` method, in
which we'd include key bindings for actions of the same type but
different values.

Note that, for now, the command palette will perform the *focusing*
version of the actions. I'm not totally sure this is the right behavior,
but it seems more useful to me.

Release Notes:

N/A
2023-05-24 10:07:34 -07:00
Nathan Sobo
747322a02d Merge remote-tracking branch 'origin/main' into zmd 2023-05-24 11:04:07 -06:00
Max Brunsfeld
66b3be8687 Make toggle dock actions appear in the command palette 2023-05-24 09:58:41 -07:00
Nathan Sobo
d495c1b804 Only run AI agent on dev and preview 2023-05-24 10:58:33 -06:00
Antonio Scandurra
35f9996a4f WIP 2023-05-24 18:45:47 +02:00
Nathan Sobo
ba79a8ba94 WIP 2023-05-24 10:26:14 -06:00
Nathan Sobo
50e1e17d09 WIP 2023-05-24 09:53:08 -06:00
Nathan Sobo
051c7566cc WIP 2023-05-24 09:38:37 -06:00
Nathan Sobo
8abaf66602 WIP 2023-05-24 09:21:50 -06:00
Nathan Sobo
6368ade1da WIP 2023-05-24 09:12:16 -06:00
Antonio Scandurra
8832248bb9 Polish panel experience (#2523)
In this pull request we improved key bindings (as described below) and
added tooltips.

Add these release notes to the panels release notes:

- The left, right and bottom dock can be toggled and focused at the same
time respectively via `cmd-b`, `cmd-r` and `cmd-j`. Holding `shift` will
toggle them without changing the focus.
2023-05-24 16:23:24 +02:00
Antonio Scandurra
f67a22828b Show keystroke in panel tooltips
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
2023-05-24 16:13:58 +02:00
Antonio Scandurra
0cf1632d39 Only focus new terminal if the panel contains focus
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
2023-05-24 16:04:02 +02:00
Antonio Scandurra
50cfe5eec3 Use cmd-b/cmd-r/cmd-j to toggle left/right/bottom dock and focus when opening
Also, bind the same keys with shift to toggle the dock without focusing.

Co-Authored-By: Nathan Sobo <nathan@zed.dev>
2023-05-24 15:59:06 +02:00
Antonio Scandurra
7534c4e670 Fix opening excerpt when the cursor is at the end of a multi-buffer (#2522)
Fixes
https://linear.app/zed-industries/issue/Z-987/option-enter-doesnt-open-buffer-from-multibuffer-if-on-the-last-line

This was caused by seeking with a right bias, which in the case of the
last excerpt in the buffer would seek past the end of the buffer. This
commit changes the behavior to move the cursor back to the previous
excerpt if we overshoot, to ensure we always land on an excerpt.

Release Notes:

- Fixed a bug that prevented opening excerpts when the cursor was at the
end of a multi-buffer.
2023-05-24 15:08:18 +02:00
Antonio Scandurra
2a8e0824a6 Fix opening excerpt when the cursor is at the end of a multi-buffer
This was caused by seeking with a right bias, which in the case of the
last excerpt in the buffer would seek past the end of the buffer.  This
commit changes the behavior to move the cursor back to the previous excerpt
if we overshoot, to ensure we always land on an excerpt.
2023-05-24 15:02:57 +02:00
Antonio Scandurra
8f49552121 Show maximize/minimize icon for panes and terminal panel (#2521)
Closes
https://linear.app/zed-industries/issue/Z-1790/show-zoomunzoom-icons-for-panes-and-terminal-panel

![CleanShot 2023-05-24 at 11 07
04@2x](https://github.com/zed-industries/zed/assets/482957/b27d002c-6443-440b-88d0-cfa06d902477)
2023-05-24 11:44:39 +02:00
Antonio Scandurra
e5fd953b4f Provide tooltips for pane buttons 2023-05-24 11:18:30 +02:00
Antonio Scandurra
1a353ad25d Show maximize/minimize icon for panes and terminal panel 2023-05-24 11:00:12 +02:00
Antonio Scandurra
9575ffc1e3 Prevent empty panes from being zoomed (#2520)
Closes
https://linear.app/zed-industries/issue/Z-1789/empty-panes-should-not-be-zoomable
2023-05-24 10:51:52 +02:00
Antonio Scandurra
168ff99927 Prevent empty panes from being zoomed 2023-05-24 10:34:56 +02:00
Joseph T. Lyons
e463e3b2e8 Update terminal panel tooltip (#2519)
The actions in Zed now call this the Terminal Panel and the crate is
called that as well, so I think the tooltip should be updated to use
that same name.

Release Notes:

N/A
2023-05-23 22:33:08 -04:00
Joseph Lyons
e6857866d4 Update terminal panel tooltip 2023-05-23 22:23:38 -04:00
Nathan Sobo
637ed79603 WIP 2023-05-23 18:16:49 -06:00
Nathan Sobo
65fc50745b WIP 2023-05-23 18:04:33 -06:00
Nathan Sobo
d934da1905 WIP 2023-05-23 15:25:34 -06:00
Joseph T. Lyons
ba7659fa77 add toggle right and bottom dock actions (#2518)
Release Notes:

N/A
2023-05-23 13:59:45 -04:00
Joseph Lyons
2457c55fe7 add toggle right and bottom dock actions 2023-05-23 13:53:56 -04:00
Joseph T. Lyons
aac83ceea7 Add architecture to clickhouse event request body (#2514)
See title

Release Notes:

N/A
2023-05-23 11:11:17 -04:00
Nathan Sobo
7be41e19f7 WIP 2023-05-23 08:52:21 -06:00
Antonio Scandurra
ad866c5b21 Update LiveKit client SDK to 1.0.12 (#2516)
Fixes
https://linear.app/zed-industries/issue/Z-1756/screen-sharing-is-slow-and-sometimes-doesnt-work-at-all

Release Notes:

* Fixed some cases where screen-sharing would have low bitrate or
completely fail to start.
2023-05-23 16:19:12 +02:00
Antonio Scandurra
774530dd05 Update LiveKit client SDK to 1.0.12
Co-Authored-By: Julia Risley <julia@zed.dev>
2023-05-23 15:32:55 +02:00
Nathan Sobo
073967c80b WIP 2023-05-23 01:56:28 -06:00
Nathan Sobo
5362e7d346 WIP 2023-05-23 01:52:37 -06:00
Nathan Sobo
394e87d17c WIP 2023-05-23 01:23:01 -06:00
Antonio Scandurra
dcc4dd7eb8 Allow multiple docks to be visible at once and only let them contain panels (#2450)
Closes
https://linear.app/zed-industries/issue/Z-1188/allow-docks-to-be-visible-on-left-bottom-right

To prepare the way for the AI assistant, we want to revamp the way docks
and panels work. In this PR, we replace the dock as it currently exists
with 3 independent docks at the left, bottom, and right edge of the
workspace. To replace the "expanded" dock, we will introduce a zoom
feature that works on any pane and some panels. When showing a dock with
an active panel that is zoomed, it will automatically appear zoomed.
This replicates the expanded dock experience. If you unzoom, the panel
will still be visible.

### Panels only

We want to simplify these docks to only allow them to contain *panels*.
By doing this, we don't need to give each dock a tab bar, minimizing
clutter in the workspace. Each panel will remember its size, and the
dock will adjust to that size when the panel is toggled. This will allow
each panel to take up the amount of space that makes sense for its use
case.

There will be 3 kinds of panels:

* Project panel: This currently lives in the left "sidebar", which this
PR renames to the left dock. Users will be able to left click the icon
and switch the project panel to the right dock.
* Terminal panel: This is the primary user of the previous dock. Now all
terminals will live in a terminal panel, which can be docked at the
left, bottom, or right. This dock will contain tabs, but only for
terminals. Terminals will still be able to be dragged into the
workspace, but by default, the first new terminal will live in the
terminal panel which lives in one of the 3 docks.
* Feedback: Instead of opening a pane, let's explore making this a panel
instead.

### Status bar icons

A nice feature of this change is that it simplifies our status bar by
eliminating the dock button. We will only show icons for specific
panels. Panel in the left dock will appear at the left side of the
status bar. Panels in the right dock on the far right, panels in the
bottom will appear on the right, to the immediate left of the buttons
for right panels. Left clicking any panel button will allow it to be
redocked to any valid location for that panel.

### Paving the way for the AI assistant

I'm envisioning the assistant living in the far right panel by default.
So in the stock config, you'll have the project panel on the left,
terminal on the bottom, assistant on the right, and code in the middle.
Let's fucking go!
2023-05-23 09:09:43 +02:00
Antonio Scandurra
19b817e48a Improve styling of bottom and right docks 2023-05-23 09:02:45 +02:00
Antonio Scandurra
208ff2fba7 Merge remote-tracking branch 'origin/main' into panels 2023-05-23 08:24:28 +02:00
Nathan Sobo
0de5a444d3 WIP 2023-05-23 00:22:21 -06:00
Nathan Sobo
234dbc3ca9 WIP 2023-05-23 00:16:44 -06:00
Nathan Sobo
c4d88bc529 WIP 2023-05-22 23:43:02 -06:00
Nathan Sobo
30de64845f WIP 2023-05-22 23:11:27 -06:00
Mikayla Maki
a5b5de5651 Add initial project panel settings (#2515)
This PR adds project panel settings for disabling git status.

Release Notes:

- Adds `project_panel: { git_status: bool }` to the settings, for
controlling whether git status information appears.
2023-05-22 20:43:24 -07:00
Mikayla Maki
a37b1b6a45 Add other setting init 2023-05-22 20:38:30 -07:00
Mikayla Maki
b19a712799 Add init_settings to tests 2023-05-22 20:33:18 -07:00
Mikayla Maki
da96802bf0 fmt 2023-05-22 20:25:27 -07:00
Mikayla Maki
7be8dead07 Add initial project panel settings 2023-05-22 20:23:07 -07:00
Nathan Sobo
7e6cccfa3d WIP: Stream in completions
Drop dependency on tokio introduced by async-openai and do it ourselves.

The approach I'm taking of replacing instead of appending is causing issues. Need to just append.
2023-05-22 20:28:22 -06:00
Joseph Lyons
049b72e3ac Add architecture to clickhouse event request body 2023-05-22 21:45:58 -04:00
Mikayla Maki
54c04a6618 Fix race condition in diff base initializaiton (#2513)
fixes
https://linear.app/zed-industries/issue/Z-1657/diff-markers-in-gutter-do-not-show-up-until-after-first-save

Release Notes:

- Fixes a race condition on buffer initialization that would cause git
diffs to not load.
2023-05-22 18:43:59 -07:00
Mikayla Maki
9307719a4b Add run until parked for test 2023-05-22 18:40:12 -07:00
Mikayla Maki
51d94f532b Load diff base for buffers that are opening but not yet opened when repositories are discovered 2023-05-22 18:26:33 -07:00
Mikayla Maki
fc82489128 Update scrollbar styling 2023-05-22 16:23:36 -07:00
Mikayla Maki
1b5be04f76 Fixed contrast in project panel and scrollbar (#2512)
Redo of https://github.com/zed-industries/zed/pull/2504 

This makes the different git locations individually style-able

Release Notes:

- Improve git contrast (preview only)
2023-05-22 15:59:02 -07:00
Mikayla Maki
e4454f1e7f fmt 2023-05-22 15:53:29 -07:00
Mikayla Maki
5c31c84c90 Fixed contrast in project panel and scrollbar
co-authored-by: nate <nate@zed.dev>
2023-05-22 15:51:31 -07:00
Mikayla Maki
fb2f17e5c9 Revert "Touch up git in project panel and scroll bar styling" (#2511)
Reverts zed-industries/zed#2504, due to accidentally pulling in main
2023-05-22 15:36:15 -07:00
Mikayla Maki
2d1c4a1971 Revert "Touch up git in project panel and scroll bar styling" 2023-05-22 15:31:14 -07:00
Mikayla Maki
dcf4791182 WIP: Touch up git in project panel and scroll bar styling (#2504)
This makes the different git locations individually style-able

Release Notes:

* Improve git contrast (preview only)
2023-05-22 15:11:10 -07:00
Mikayla Maki
9bec74f1d4 fmt 2023-05-22 15:04:51 -07:00
Mikayla Maki
9a47415cb5 Merge branch 'main' into git-project-panel-and-scrollbar-styling 2023-05-22 15:02:28 -07:00
Mikayla Maki
5dfb0e3691 remove border from thumb 2023-05-22 14:56:55 -07:00
Nate Butler
11eb9b17c9 Update project panel & scroll bar git colors 2023-05-22 17:47:52 -04:00
Mikayla Maki
4c9d24da7c Only fire update diff base when the dot repo is scanned (#2510)
This PR fixes a bug in the firing of the UpdatedRepositories event which
caused it to flood collaboration with new messages on every file save.

Release Notes:

* Fixed a bug in repository detection that caused it to fire
over-eagerly (preview only)
2023-05-22 14:11:19 -07:00
Mikayla Maki
96224fa7e8 Only fire update diff base when the dot repo is scanned 2023-05-22 13:57:07 -07:00
Max Brunsfeld
0e332b5f94 Clear old diagnostics when restarting a language server (#2509)
Previously, restarting a language server did not clear out the
diagnostics that were published by that server. Those diagnostics would
stick around forever.

Release Notes:

* Fixed a bug where restarting a language server would sometimes leave
buffers with stale diagnostics associated with that server.
2023-05-22 13:06:32 -07:00
Max Brunsfeld
7689cdf3f9 Clear old diagnostics when restarting a language server 2023-05-22 12:53:22 -07:00
Mikayla Maki
1d07cff765 Adjust scrollbar settings to be expandable (#2507)
Note that this PR cannot be cherrypicked into preview, as it relies on
the new settings infrastructure.

Switches settings from `show_scrollbars: "auto"` to `scrollbar: {show:
"auto", git_diffs: true}`.

fixes
https://linear.app/zed-industries/issue/Z-1650/scroll-bar-feature-settings

Release Notes:

- Changed scrollbar settings from `show_scrollbars: "auto"` to
`scrollbar: {show: "auto", git_diffs: true}`. (preview only)
2023-05-22 12:51:21 -07:00
Mikayla Maki
dcb987ba9d Diagnostics pane was not focusable with the mouse (#2506)
fixes
https://linear.app/zed-industries/issue/Z-1432/cant-cmd-w-an-empty-diagnostics-in-a-split-pane

Release Notes:

* Fixed a bug where the diagnostics pane could not be focused or closed
in certain circumstances.
2023-05-22 12:44:39 -07:00
Mikayla Maki
cfdf9198da Switch back to 2023-05-22 12:16:47 -07:00
Mikayla Maki
687ccd4c6f fmt 2023-05-22 12:13:23 -07:00
Mikayla Maki
366f13bb5c Adjust scrollbar settings to be expandable 2023-05-22 12:12:56 -07:00
Joseph Lyons
49566e5677 Update pull_request_template.md 2023-05-22 15:00:57 -04:00
Joseph Lyons
9c32b774aa Add example of changelog line in PR template 2023-05-22 14:53:07 -04:00
Mikayla Maki
bafc1d922e fmt 2023-05-22 11:46:52 -07:00
Mikayla Maki
e59c8e9d61 Fix diagnostic pane not being closeable 2023-05-22 11:44:23 -07:00
Max Brunsfeld
e129ed2d91 Fix spurious setting error log messages (#2498)
Fixes a bug introduced in
https://github.com/zed-industries/zed/pull/2448, where error messages
would be logged if the user config didn't specify certain fields like
`journal` or `telemetry`.
2023-05-22 11:44:21 -07:00
Joseph Lyons
04355215b2 Indent all release note lines for each PR 2023-05-22 14:44:05 -04:00
Julia
a69144911f Add paragraph based vertical movements (#2502)
Very selfish patch I worked on yesterday, I kept saying I wanted these
and finally decided to just add them. Feedback on the keybindings
welcome

Release Notes:

* Added `MoveToStartOfParagraph` and `MoveToEndOfParagraph` movements
for paragraph based vertical navigation
2023-05-22 14:31:34 -04:00
Mikayla Maki
85266131cf Fix perf problem with scrollbars in large multibuffers (#2505)
Remove scrollbars from multibuffers

Release Notes:

* Removes git scrollbar highlights from multibuffers (preview only)
2023-05-22 11:11:27 -07:00
Mikayla Maki
6f4b6eec5b Diagnostics pane was not focusable with the mouse 2023-05-22 11:10:13 -07:00
Mikayla Maki
6264b24788 fmt 2023-05-22 11:04:36 -07:00
Mikayla Maki
8669dcdc81 Make scrollbar content detection cheaper
Remove scrollbars from multibuffers

co-authored-by: max <max@zed.dev>
2023-05-22 10:55:44 -07:00
Antonio Scandurra
f22067b236 Remove unused imports 2023-05-22 19:38:57 +02:00
Antonio Scandurra
33f6c56b14 Fix more warnings 2023-05-22 19:36:01 +02:00
Antonio Scandurra
6f39d49b18 Fix warnings 2023-05-22 19:33:11 +02:00
Antonio Scandurra
048498e39b Test zooming panels 2023-05-22 19:30:31 +02:00
Mikayla Maki
ef81813d56 Wire in conflict styling 2023-05-22 10:10:47 -07:00
Mikayla Maki
a355b4c135 Add conflict styles to project panel 2023-05-22 10:09:55 -07:00
Mikayla Maki
af73c1af06 Add seperate scrollbar styling 2023-05-22 10:03:02 -07:00
Antonio Scandurra
3ca95678f1 Avoid leaking docks when adding panels 2023-05-22 18:57:14 +02:00
Kirill Bulatov
9e46b17149 Do not refocus project search query on ESC press (#2494)
Closes
https://linear.app/zed-industries/issue/Z-1471/escape-should-not-move-focus-to-project-search

Makes ESC more predictable as a shortcut that cancels/roll backs the
state in project search panel.

Release Notes:

* Fixes ESC making focus to jump in project search panel
2023-05-22 19:56:10 +03:00
Mikayla Maki
2200a22c07 Wire project panel themes into rust 2023-05-22 09:55:59 -07:00
Antonio Scandurra
75a0742c90 Uncomment persistence test 2023-05-22 18:32:28 +02:00
Antonio Scandurra
5b7e852903 Await closing of items in tests 2023-05-22 18:28:30 +02:00
Antonio Scandurra
deaf60005c Fix more tests 2023-05-22 18:21:12 +02:00
Nate Butler
b80cb73745 Add git to project panel in theme, use different values for dark and light 2023-05-22 12:09:54 -04:00
Antonio Scandurra
4aa2858b2b Transfer focus to root view only if previously-focused view was dropped 2023-05-22 18:05:08 +02:00
Max Brunsfeld
21ada545b0 Remove assertions about behavior on invalid settings file 2023-05-22 08:48:37 -07:00
Julia
44903bc193 Add test for paragraph vertical movements 2023-05-22 11:48:07 -04:00
Antonio Scandurra
185a624b99 Fix some project panel tests 2023-05-22 16:18:53 +02:00
Antonio Scandurra
10e947cb5f Persist project and terminal panel sizes 2023-05-22 15:55:44 +02:00
Julia
7190840081 Add paragraph based vertical movements 2023-05-22 09:12:36 -04:00
Antonio Scandurra
146809eef0 Merge branch 'main' into panels 2023-05-22 14:10:17 +02:00
Antonio Scandurra
2098ac2c77 Fix formatting for keymap.json 2023-05-22 10:41:48 +02:00
Joseph Lyons
cb1b64e51b Fix up formatting for get preview channel changes script 2023-05-22 00:02:19 -04:00
Joseph T. Lyons
f2a74017df Fix get preview channel changes script (#2501)
Fixes the text we match on to get PR commits and also prints the release
notes of each PR

Release Notes:

* Skip
2023-05-21 23:47:07 -04:00
Joseph Lyons
c701901c7b Fix get preview channel changes script 2023-05-21 23:40:07 -04:00
Nathan Sobo
d5cf595761 Enable "test-support" feature on editor dependencies for tests (#2499)
Enable the `"test-support"` feature whenever we depend on the `editor`
crate in tests.

If we don't do this, we end up depending on `livekit_client` without
enabling `"test-support"`, and this causes us to link against
`WebRTC.framework` in its build script, which causes a dynamic linker
error when starting the tests.

Fixes
https://linear.app/zed-industries/issue/Z-1646/cant-run-package-tests-for-activity-monitor-and-auto-update

Release Notes:

* No notes
2023-05-20 10:19:59 -06:00
Nathan Sobo
986eafd84e Enable test-support on editor in tests 2023-05-20 09:54:38 -06:00
Nathan Sobo
912fd23006 Parse markdown into an OpenAI chat completion request 2023-05-20 09:43:12 -06:00
Mikayla Maki
35a9b90528 Fix bugs in git implementation (#2495)
fixes
https://linear.app/zed-industries/issue/Z-1593/clean-up-git-integration

- Fixes calculation of git offsets in the scrollbar:

fixes
https://linear.app/zed-industries/issue/Z-1608/fix-scrollbar-diffs-sliding-out-of-sync-with-gutter-diffs-in

fixes
https://linear.app/zed-industries/issue/Z-1629/project-search-panel-has-git-marks-on-the-scrollbar-misaligned

fixes
https://linear.app/zed-industries/issue/Z-1625/soft-wrap-affects-diff-locations-in-scrollbar

- Improves the performance of scrollbar hunks:

fixes
https://linear.app/zed-industries/issue/Z-1640/double-check-performance-of-scrollbar-hunks

- Fixes a long standing bug with how git gutters interact with soft
wraps:

fixes
https://linear.app/zed-industries/issue/Z-1442/make-hunks-grow-to-the-end-of-softwraps-when-ending-on-a-softwrapped

- Allows work directories to be renamed

fixes
https://linear.app/zed-industries/issue/Z-1577/fix-stale-git-repositories-when-directory-is-renamed

Release Notes:

* Fix the offsets of the git diffs in the scrollbar when there are
folds, wraps, or excerpts (preview only)
* Allow the work directory of a repository to be renamed (preview only)
* Extend git gutter to cover the entirety of a wrapped line
(https://github.com/zed-industries/community/issues/937)
2023-05-19 18:15:11 -07:00
Mikayla Maki
bbb68c523c Refactored apart the forward and the backwards iterator for diff hunks 2023-05-19 18:09:47 -07:00
Max Brunsfeld
e32233c826 Fix spurious setting error logs on non-existent setting keys 2023-05-19 17:15:05 -07:00
Mikayla Maki
43e301eeef refine batched anchor conversions
co-authored-by: max <max@zed.dev>
2023-05-19 16:52:57 -07:00
Max Brunsfeld
fb11c3e4bf Remove stray prints 2023-05-19 16:52:30 -07:00
Mikayla Maki
1474429271 fmt 2023-05-19 16:32:07 -07:00
Mikayla Maki
c795c9b844 Rearrange git tests in worktree
Add support for renaming work directories
2023-05-19 16:30:00 -07:00
Mikayla Maki
623a177fe6 Fix bug where git diff hunks would not extend through a soft wrap 2023-05-19 16:23:45 -07:00
Mikayla Maki
560160b100 Batch anchor conversions in git hunk iterator 2023-05-19 16:23:45 -07:00
Mikayla Maki
2a41a32aac Calculate y offsets correctly 2023-05-19 16:23:45 -07:00
Max Brunsfeld
d7d29472c2 Remove expensive-to-clone fields from worktree's LocalSnapshot (#2497)
This fixes performance problems that @nathansobo and I have seen in some
cases, when a large number of files changed on disk. A lot of time was
being spent in `worktree::LocalSnapshot::clone`. I think this may have
been because of needing to clone the `removed_entry_ids` map. This
structure is only really used when *mutating* the `LocalSnapshot` in the
background scanner, so I moved it off of the snapshots.
2023-05-19 16:20:19 -07:00
Mikayla Maki
06a89d9104 Add guards to other pane index removals (#2496)
These should be impossible for users to reach, but why leave it to
chance?

Release Notes:

* N/A
2023-05-19 15:21:00 -07:00
Max Brunsfeld
7ae642b9b8 Avoid storing removed_entry_ids on the LocalSnapshot 2023-05-19 15:16:04 -07:00
Mikayla Maki
c193b0b8fc Add guards to other pane index removals 2023-05-19 15:10:44 -07:00
Max Brunsfeld
32c7157906 🎨 Make worktree repositories more consistent 2023-05-19 15:04:37 -07:00
Max Brunsfeld
6359333749 Don't store next_entry_id on worktree's local snapshot 2023-05-19 13:49:24 -07:00
Kirill Bulatov
065f71d671 Do not refocus project search query on ESC press 2023-05-19 22:06:47 +03:00
Kirill Bulatov
7d1833b759 When the file is deleted via project panel, close it in editors (#2490)
Deals with https://github.com/zed-industries/community/issues/179 by
sending a message about it, to asynchronously apply on all workspaces.

Release Notes:

* Fixes a bug when files, deleted in the project panel were left open in
the editor
2023-05-19 21:51:37 +03:00
Max Brunsfeld
844b8d9e1e Remove unnescessary double lookup in repo for (#2492)
Release Notes:

* Optimize repository queries (preview only)
2023-05-19 11:47:05 -07:00
Kirill Bulatov
2c8fffc4f8 Use better name for the method that closes deleted buffers
co-authored-by: Max <max@zed.dev>
2023-05-19 21:47:00 +03:00
Max Brunsfeld
01a3e49946 Optimize retrieving repos for entries when rendering the project panel (#2493)
This fixes slowness in rendering the project panel due to retrieving the
repository for a given entry.

Release Notes:

* Fixed a lag that would occur when lots of files changed on disk while
the project panel was open (preview only).
2023-05-19 11:46:32 -07:00
Mikayla Maki
9f157bdb67 Remove unescessary methods 2023-05-19 11:30:10 -07:00
Max Brunsfeld
729a93db6b Optimize retrieving repos for entries when rendering the project panel
Co-authored-by: Mikayla <mikayla@zed.dev>
2023-05-19 11:29:02 -07:00
Mikayla Maki
6792788216 Remove unnescessary double lookup 2023-05-19 11:08:58 -07:00
Antonio Scandurra
5a8fb18c20 Show workspace only after initializing it 2023-05-19 19:35:00 +02:00
Antonio Scandurra
5ff49bde31 Serialize and deserialize TerminalPanel 2023-05-19 19:26:32 +02:00
Max Brunsfeld
d480555ec9 Fix performance problems in reporting changed FS paths to language servers (#2491)
Fixes
https://linear.app/zed-industries/issue/Z-1611/main-thread-hangs-while-sending-filesystem-change-events-to-lsp

Release Notes:

* Fixed a lag that would sometime occur when large numbers of files
changed on disk, due to reporting the changed files to language servers.
2023-05-19 09:42:55 -07:00
Max Brunsfeld
847d1e73a3 Replace remaining usages of glob crate with globset 2023-05-19 09:36:46 -07:00
Max Brunsfeld
459cc9c959 Optimize matching of multiple file-watch globs using the globset crate 2023-05-19 09:13:34 -07:00
Kirill Bulatov
583b15badc When the file is deleted via project panel, close it in editors 2023-05-19 18:52:30 +03:00
Antonio Scandurra
e49281699c Add new terminal when the terminal panel is activated, and not on focus 2023-05-19 16:07:47 +02:00
Antonio Scandurra
924ec961ff Toggle project panel when opening new workspace in a dock-agnostic way 2023-05-19 15:27:18 +02:00
Antonio Scandurra
3d6b728364 Activate the correct panel when deserializing workspace 2023-05-19 14:18:11 +02:00
Kirill Bulatov
3984cc6d39 Properly handle WorktreeId 2023-05-19 12:37:36 +03:00
Max Brunsfeld
4bda5c4d69 Optimize LSP watched file reporting in 2 simple ways
* Convert globs to relative paths in advance. This avoids needing to convert
  every changed path to an absolute path before performing glob matching.
* Avoid duplicate reporting for language servers with multiple worktrees.
2023-05-18 17:08:51 -07:00
Max Brunsfeld
34b0d6200f collab 0.12.4 2023-05-18 14:58:57 -07:00
Max Brunsfeld
ffe35d171c Avoid unnecessary code action requests when applying leader updates t… (#2489)
We noticed a huge amount of code actions requests being issued by
followers when applying leader updates. It was caused by a call to
`MultiBuffer::remove_excerpts` with an empty list of excerpts to remove.
This PR fixes that by avoiding emitting spurious events when multibuffer
excerpt manipulation methods are called with empty lists.
2023-05-18 14:52:21 -07:00
Max Brunsfeld
58f704abcb Avoid unnecessary code action requests when applying leader updates to an editor 2023-05-18 14:46:23 -07:00
Mikayla Maki
87e206c451 Fixed an imprecise join in rejoin room (#2488)
This fixed a pre-existing bug in rejoin room that got expanded to
stalling collab with the new git status feature.

Release Notes:

* N/A
2023-05-18 14:42:59 -07:00
Mikayla Maki
99fcf23798 fmt 2023-05-18 14:31:08 -07:00
Mikayla Maki
95a7d69bce Fixed an imprecise join in rejoin room 2023-05-18 14:29:18 -07:00
Max Brunsfeld
2883d6f1ef Improve collab logging (#2487)
This adds some logging to the collab server, to help us identify the
source of the collaboration latency we're seeing in the 0.87 preview
version of zed.
2023-05-18 14:17:17 -07:00
Max Brunsfeld
2a11a89827 collab 0.12.3 2023-05-18 12:27:08 -07:00
Joseph Lyons
55b241a4f6 collab 0.12.2 2023-05-18 12:27:01 -07:00
Max Brunsfeld
c71b59b248 Log how long it takes to handle each RPC message 2023-05-18 12:24:07 -07:00
Kirill Bulatov
923be1892d Maintain recently opened files history (#2467)
Part of https://github.com/zed-industries/community/issues/351 and
https://github.com/zed-industries/community/issues/1125

Adds navigation history to the search file dialogue:


![image](https://github.com/zed-industries/zed/assets/2690773/01a1d3ea-1328-425a-ac95-2ebdbfcc07ab)

The history is only shown on empty query and gets replaced by the query
results after an input.
Currently, history gets lost between the project restarts.

Release Notes:

* Added a recently opened file list to search file dialogue
2023-05-18 22:06:25 +03:00
Kirill Bulatov
2ec994dfcd Add a unit test 2023-05-18 21:53:42 +03:00
Kirill Bulatov
201d513c50 Show navigation history in the file finder modal
co-authored-by: Max <max@zed.dev>
2023-05-18 21:50:38 +03:00
Kirill Bulatov
711d2c6fe7 Maintain recently opened files history 2023-05-18 21:50:38 +03:00
Julia
bd29812391 Avoid panic in get_injections (#2486) 2023-05-18 13:35:10 -04:00
Julia
5ab1ae1521 Avoid panic in get_injections 2023-05-18 12:23:49 -04:00
Max Brunsfeld
83f8981308 Define empty theme for tests regardless of cargo features (#2485)
This fixes some errors that were happening when running a single crate's
tests, if the test did not enable the `test-support` feature in the
`theme` crate.
2023-05-18 09:14:46 -07:00
Max Brunsfeld
71ad7e7612 Define empty theme for tests regardless of cargo features
Co-authored-by: Kirill <kirill@zed.dev>
2023-05-18 09:01:38 -07:00
Antonio Scandurra
f2ad17dbc0 WIP 2023-05-18 15:35:46 +02:00
Kirill Bulatov
263afc75a8 Merge pull request #2484 from zed-industries/kb/unite-dmg-build
Unite dmg build jobs

Follow-up of https://github.com/zed-industries/zed/pull/2472 actually doing the proper thing: allow running dmg builds on PRs if `run-build-dmg` label is attached.
2023-05-18 14:39:41 +03:00
Kirill Bulatov
5ab4eab369 Unite dmg build jobs 2023-05-18 13:49:55 +03:00
Mikayla Maki
89d8bb1425 WIP: Add persistence to new docks 2023-05-17 17:34:20 -07:00
Max Brunsfeld
5c437e2098 Fix crashes when running zed bundled 2023-05-17 16:53:48 -07:00
Max Brunsfeld
a9b107f155 Fix release-mode stub for watch_themes 2023-05-17 16:36:06 -07:00
Max Brunsfeld
d23fc6f135 Merge pull request #2448 from zed-industries/setting-store
Separate the settings struct into a set of dynamically-registered setting types
2023-05-17 16:19:09 -07:00
Max Brunsfeld
a56793c214 Clear buffer font size adjustments when buffer font size setting changes 2023-05-17 16:14:05 -07:00
Max Brunsfeld
823e15d85a Refresh windows when settings file changes 2023-05-17 16:02:21 -07:00
Max Brunsfeld
667f476f7f Remove unused watched_json file 2023-05-17 16:02:08 -07:00
Max Brunsfeld
42eca3048f Move font size adjustment code to the theme crate 2023-05-17 15:56:32 -07:00
Max Brunsfeld
258723566f Rename settings::register_setting -> settings::register 2023-05-17 15:06:11 -07:00
Max Brunsfeld
2d5f03e148 Remove optional path argument when getting settings 2023-05-17 15:05:20 -07:00
Max Brunsfeld
1f0e79ee9d Merge branch 'main' into setting-store 2023-05-17 14:56:35 -07:00
Max Brunsfeld
67a25126d4 Define theme/ui text style settings in theme crate 2023-05-17 14:44:59 -07:00
Mikayla Maki
5ef03992c5 Merge pull request #2483 from zed-industries/add-scrollbar-settings
Add scrollbars setting
2023-05-17 14:44:08 -07:00
Mikayla Maki
f4e99ecde4 Add never option to scrollbar settings 2023-05-17 14:19:35 -07:00
Mikayla Maki
88bc55ddf5 Add a dependency on rustfmt to the CI tests 2023-05-17 14:17:10 -07:00
Mikayla Maki
6cf439e734 fmt 2023-05-17 14:12:04 -07:00
Mikayla Maki
ae3394f4de Add scrollbars setting 2023-05-17 14:09:26 -07:00
Mikayla Maki
1c9a7a7ad8 Merge pull request #2482 from zed-industries/add-hunks-to-scrollbar
Add diff hunks to the scroll bar
2023-05-17 11:51:01 -07:00
Mikayla Maki
aa89632286 Add diff hunks to the scroll bar 2023-05-17 11:40:30 -07:00
Max Brunsfeld
5c729c0e56 Define base keymap setting in welcome crate 2023-05-17 11:23:09 -07:00
Julia
7d11329e80 Merge pull request #2481 from zed-industries/update-font-kit
Update font-kit to resolve panics when loading malformed fonts
2023-05-17 13:57:39 -04:00
Julia
9e5983305b Update font-kit to resolve panics when loading malformed fonts 2023-05-17 13:38:54 -04:00
Max Brunsfeld
89204e85c0 Merge branch 'main' into setting-store 2023-05-17 09:55:24 -07:00
Joseph Lyons
cb0c613da2 v0.88.x dev 2023-05-17 12:39:08 -04:00
Max Brunsfeld
d2ba18eae9 Customize language settings JSON schema in language crate 2023-05-17 09:26:36 -07:00
Antonio Scandurra
4898417617 Drop foreign key constraint from workspaces.dock_pane to panes table
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
Co-Authored-By: Mikayla Maki <mikayla@zed.dev>
2023-05-17 18:21:35 +02:00
Antonio Scandurra
05fb051924 Store whether a panel is zoomed in the panel itself
Co-Authored-By: Mikayla Maki <mikayla@zed.dev>
2023-05-17 17:51:11 +02:00
Antonio Scandurra
f097444546 Rebind ctrl-` to toggle terminal panel focus
Also, add `ctrl-~` to create new terminals.

Co-Authored-By: Mikayla Maki <mikayla@zed.dev>
2023-05-17 17:35:10 +02:00
Antonio Scandurra
747fbfadeb Notify old/new ancestors of the focused view when they change 2023-05-17 17:12:12 +02:00
Antonio Scandurra
981129ef8e Show a panel/pane as zoomed only if it's the active item in workspace 2023-05-17 15:06:58 +02:00
Max Brunsfeld
6403bb86e1 Define workspace settings in workspace crate 2023-05-16 20:25:18 -07:00
Max Brunsfeld
cbd4771f10 Define project settings in project crate 2023-05-16 17:45:04 -07:00
Max Brunsfeld
65e3713d4d Define editor settings in editor crate 2023-05-16 17:30:29 -07:00
Max Brunsfeld
39618ae32d Define language settings in the language crate 2023-05-16 17:29:53 -07:00
Antonio Scandurra
f87ae6032e Don't rely on action propagation for zooming in and out
Co-Authored-By: Antonio Scandurra <antonio@zed.dev>
2023-05-16 10:34:50 -06:00
Antonio Scandurra
adf361b374 Implement zooming for panes and docks 2023-05-16 11:49:48 +02:00
Antonio Scandurra
c03e470fe6 Introduce Panel::can_zoom 2023-05-15 17:10:30 +02:00
Nathan Sobo
ba50b35de6 wip 2023-05-13 14:34:09 -06:00
Nathan Sobo
6c60853842 Don't close panel on event unless active; add tests 2023-05-12 15:44:09 -06:00
Nathan Sobo
bd795d7607 Preserve panel size when re-docking between left and right 2023-05-12 15:15:11 -06:00
Nathan Sobo
214354b4da Make panels independently resizable 2023-05-12 09:44:14 -06:00
Max Brunsfeld
9ae10a5dd9 Add a better API for updating settings in the SettingsStore in tests 2023-05-11 17:24:58 -07:00
Max Brunsfeld
bc5b78198a Define terminal settings in terminal crate 2023-05-10 17:43:10 -07:00
Max Brunsfeld
cee7edabf9 Ensure the SettingsStore global is added in tests 2023-05-10 16:39:59 -07:00
Max Brunsfeld
68867fe2e1 Define journal settings in journal crate 2023-05-10 12:59:24 -07:00
Max Brunsfeld
aa6ea920e2 Define telemetry settings in the client crate 2023-05-10 12:59:24 -07:00
Max Brunsfeld
9b06be2aa2 Define vim_mode setting in vim crate 2023-05-10 12:59:24 -07:00
Max Brunsfeld
926d7b356d Define auto_update setting in the auto_update crate 2023-05-10 12:59:24 -07:00
Max Brunsfeld
b6b2c5d1d1 Generalize settings JSON schema logic to work w/ arbitrary setting types 2023-05-10 12:59:24 -07:00
Max Brunsfeld
9a6a2d9d27 Start using the SettingsStore in the app 2023-05-10 12:59:24 -07:00
Max Brunsfeld
316f791a77 Add generic update method to SettingsStore 2023-05-10 12:59:24 -07:00
Max Brunsfeld
24e06334d0 Allow registering additional settings after loading global settings 2023-05-10 12:59:24 -07:00
Max Brunsfeld
5de9652a22 Create proof-of-concept SettingStore struct 2023-05-10 12:59:22 -07:00
Nathan Sobo
5549669316 Test moving panels
Co-Authored-By: Julia Risley <julia@zed.dev>
2023-05-10 10:41:03 -06:00
Nathan Sobo
cc21421ea8 Rename item to panel in various locations 2023-05-10 09:54:20 -06:00
Antonio Scandurra
0ccb4a50e6 Create more specific dock position settings associated with each panel 2023-05-10 16:52:10 +02:00
Antonio Scandurra
e507eadb4b Rely on root getting re-focused when panel isn't rendered
This achieves two things:

- When a panel like `TerminalPanel` closes its last tab, the focus
is returned to the root view (the `Workspace`)
- When a panel is moved from a dock to another, it will get rendered
in another spot but the focus will be kept on the panel

This also ensures the focus is not lost when a panel is moved from
a dock to another because that view
2023-05-10 15:43:44 +02:00
Antonio Scandurra
9b9d53fcf8 Focus the root view if the previously-focused view isn't rendered 2023-05-10 15:40:46 +02:00
Antonio Scandurra
cdcb7c8084 Merge branch 'main' into panels 2023-05-10 15:23:37 +02:00
Antonio Scandurra
f28eee88b6 Add context menu to change panel position 2023-05-10 14:05:48 +02:00
Antonio Scandurra
8fa379bbc5 Maintain panel visibility when changing its position 2023-05-10 11:06:37 +02:00
Antonio Scandurra
6e3ce6332a Fix panic when not specifying a project_panel setting 2023-05-10 10:43:42 +02:00
Nathan Sobo
9173916ab8 Remove unused terminal_button module 2023-05-09 20:24:37 -06:00
Nathan Sobo
f554877ff4 Render the bottom dock buttons before the right dock buttons 2023-05-09 17:41:24 -06:00
Nathan Sobo
ba248244ec Allow terminal to be docked left, bottom, or right
Co-Authored-By: Joseph Lyons <joseph@zed.dev>
2023-05-09 17:36:49 -06:00
Nathan Sobo
6a7feb4c4c Allow the project panel to be docked right or left
Co-Authored-By: Joseph Lyons <joseph@zed.dev>
2023-05-09 17:26:54 -06:00
Nathan Sobo
0d78266ddb Replace todo with unimplemented to reduce distractions 2023-05-09 16:41:10 -06:00
Nathan Sobo
6d16d2224d Merge branch 'main' into panels 2023-05-09 16:35:04 -06:00
Nathan Sobo
6645323f1b WIP 2023-05-09 13:28:34 -06:00
Antonio Scandurra
8f12489937 WIP: Allow panels to be moved
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
2023-05-09 18:57:25 +02:00
Antonio Scandurra
45df09245b Remove unused code
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
2023-05-09 18:06:59 +02:00
Antonio Scandurra
37d3ed5f5f Focus new item if pane was focused when removing previous active item
Previously, we were relying on the item getting blurred and the workspace
receiving focus, which would in turn focus the active pane. This doesn't
play well with docks because they aren't part of the workspace panes.
2023-05-09 17:38:54 +02:00
Antonio Scandurra
641f5d1107 Ensure ctrl-` works in the terminal panel 2023-05-09 17:38:18 +02:00
Antonio Scandurra
634b699281 Allow customization of Pane tab bar buttons 2023-05-09 15:05:29 +02:00
Antonio Scandurra
ad7f32d7d2 Fix bottom dock resizing 2023-05-09 14:46:15 +02:00
Antonio Scandurra
26fe7a81f3 Prevent splits in the terminal panel 2023-05-09 12:29:16 +02:00
Antonio Scandurra
e6be35c9a5 Show terminal count in panel button 2023-05-09 12:21:35 +02:00
Antonio Scandurra
02066afb0e Don't pass &mut Workspace when closing items in a Pane
This allows closing items via actions even in the `TerminalPanel`
where the `Pane` is not directly owned by a `Workspace`.
2023-05-09 11:45:39 +02:00
Antonio Scandurra
506f978c41 Automatically close terminal dock when the last terminal was closed 2023-05-09 10:18:11 +02:00
Antonio Scandurra
916612caf1 Prevent dragging items that aren't terminals to the terminal panel 2023-05-09 10:18:11 +02:00
Antonio Scandurra
6ddc610fa2 WIP: Start on TerminalPanel 2023-05-09 10:18:11 +02:00
Nathan Sobo
1ddbda5095 Rename DockItem to Panel 2023-05-09 10:18:11 +02:00
Nathan Sobo
03f8c1206a Rename Sidebar to Dock 2023-05-09 10:18:10 +02:00
Nathan Sobo
1919a826f9 Remove dock from workspace 2023-05-09 10:18:10 +02:00
262 changed files with 15131 additions and 10004 deletions

View File

@@ -2,4 +2,11 @@
Release Notes:
* [[Added foo / Fixed bar / No notes]]
- N/A
or
- (Added|Fixed|Improved) ... ([#<public_issue_number_if_exists>](https://github.com/zed-industries/community/issues/<public_issue_number_if_exists>)).
If the release notes are only intended for a specific release channel only, add `(<release_channel>-only)` to the end of the release note line.
These will be removed by the person making the release.

View File

@@ -1,54 +0,0 @@
name: Build Zed.dmg
on:
push:
branches:
- main
- "v[0-9]+.[0-9]+.x"
pull_request:
defaults:
run:
shell: bash -euxo pipefail {0}
concurrency:
# Allow only one workflow per any non-`main` branch.
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }}
cancel-in-progress: true
env:
RUST_BACKTRACE: 1
COPT: '-Werror'
jobs:
build-dmg:
if: github.ref_name == 'main' || contains(github.event.pull_request.labels.*.name, 'run-build-dmg')
runs-on:
- self-hosted
- test
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
clean: false
submodules: 'recursive'
- name: Install Rust
run: |
rustup set profile minimal
rustup update stable
- name: Install node
uses: actions/setup-node@v3
with:
node-version: 18
- name: Build dmg bundle
run: ./script/bundle
- name: Upload the build artifact
uses: actions/upload-artifact@v3
with:
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}.dmg
path: ./target/release/Zed.dmg

View File

@@ -42,6 +42,7 @@ jobs:
runs-on:
- self-hosted
- test
needs: rustfmt
env:
RUSTFLAGS: -D warnings
steps:
@@ -85,7 +86,7 @@ jobs:
runs-on:
- self-hosted
- bundle
if: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') }}
if: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-build-dmg') }}
needs: tests
env:
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
@@ -147,11 +148,11 @@ jobs:
- name: Create app bundle
run: script/bundle
- name: Upload app bundle to workflow run if main branch
- name: Upload app bundle to workflow run if main branch or specifi label
uses: actions/upload-artifact@v2
if: ${{ github.ref == 'refs/heads/main' }}
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-build-dmg') }}
with:
name: Zed.dmg
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}.dmg
path: target/release/Zed.dmg
- uses: softprops/action-gh-release@v1

1802
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,7 @@
[workspace]
members = [
"crates/activity_indicator",
"crates/ai",
"crates/assets",
"crates/auto_update",
"crates/breadcrumbs",
@@ -77,8 +78,9 @@ async-trait = { version = "0.1" }
ctor = { version = "0.1" }
env_logger = { version = "0.9" }
futures = { version = "0.3" }
glob = { version = "0.3" }
globset = { version = "0.4" }
indoc = "1"
isahc = "1.7.2"
lazy_static = { version = "1.4.0" }
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
ordered-float = { version = "2.1.1" }
@@ -86,6 +88,7 @@ parking_lot = { version = "0.11.1" }
postage = { version = "0.5", features = ["futures-traits"] }
rand = { version = "0.8.5" }
regex = { version = "1.5" }
schemars = { version = "0.8" }
serde = { version = "1.0", features = ["derive", "rc"] }
serde_derive = { version = "1.0", features = ["deserialize_in_place"] }
serde_json = { version = "1.0", features = ["preserve_order", "raw_value"] }
@@ -94,6 +97,7 @@ smol = { version = "1.2" }
tempdir = { version = "0.3.7" }
thiserror = { version = "1.0.29" }
time = { version = "0.3", features = ["serde", "serde-well-known"] }
toml = { version = "0.5" }
unindent = { version = "0.1.7" }
[patch.crates-io]

View File

@@ -0,0 +1,6 @@
User input begins on a line starting with /.
Don't apologize ever.
Never say "I apologize".
Use simple language and don't flatter the users.
Keep it short.
Risk being rude.

View File

@@ -39,8 +39,8 @@
{
"context": "Workspace",
"bindings": {
"cmd-\\": "workspace::ToggleLeftSidebar",
"cmd-k cmd-b": "workspace::ToggleLeftSidebar",
"cmd-\\": "workspace::ToggleLeftDock",
"cmd-k cmd-b": "workspace::ToggleLeftDock",
"cmd-t": "file_finder::Toggle",
"cmd-shift-r": "project_symbols::Toggle"
}
@@ -62,9 +62,5 @@
"ctrl-f": "project_panel::ExpandSelectedEntry",
"ctrl-shift-c": "project_panel::CopyPath"
}
},
{
"context": "Dock",
"bindings": {}
}
]

View File

@@ -39,7 +39,8 @@
"cmd-shift-n": "workspace::NewWindow",
"cmd-o": "workspace::Open",
"alt-cmd-o": "projects::OpenRecent",
"ctrl-`": "workspace::NewTerminal"
"ctrl-~": "workspace::NewTerminal",
"ctrl-`": "terminal_panel::ToggleFocus"
}
},
{
@@ -67,10 +68,12 @@
"cmd-z": "editor::Undo",
"cmd-shift-z": "editor::Redo",
"up": "editor::MoveUp",
"ctrl-up": "editor::MoveToStartOfParagraph",
"pageup": "editor::PageUp",
"shift-pageup": "editor::MovePageUp",
"home": "editor::MoveToBeginningOfLine",
"down": "editor::MoveDown",
"ctrl-down": "editor::MoveToEndOfParagraph",
"pagedown": "editor::PageDown",
"shift-pagedown": "editor::MovePageDown",
"end": "editor::MoveToEndOfLine",
@@ -103,6 +106,8 @@
"alt-shift-b": "editor::SelectToPreviousWordStart",
"alt-shift-right": "editor::SelectToNextWordEnd",
"alt-shift-f": "editor::SelectToNextWordEnd",
"ctrl-shift-up": "editor::SelectToStartOfParagraph",
"ctrl-shift-down": "editor::SelectToEndOfParagraph",
"cmd-shift-up": "editor::SelectToBeginning",
"cmd-shift-down": "editor::SelectToEnd",
"cmd-a": "editor::SelectAll",
@@ -183,6 +188,12 @@
"alt-[": "copilot::PreviousSuggestion"
}
},
{
"context": "Editor && extension == zmd",
"bindings": {
"cmd-enter": "ai::Assist"
}
},
{
"context": "Editor && mode == auto_height",
"bindings": {
@@ -219,7 +230,8 @@
"cmd-shift-g": "search::SelectPrevMatch",
"alt-cmd-c": "search::ToggleCaseSensitive",
"alt-cmd-w": "search::ToggleWholeWord",
"alt-cmd-r": "search::ToggleRegex"
"alt-cmd-r": "search::ToggleRegex",
"shift-escape": "workspace::ToggleZoom"
}
},
// Bindings from VS Code
@@ -361,7 +373,9 @@
"workspace::ActivatePane",
8
],
"cmd-b": "workspace::ToggleLeftSidebar",
"cmd-b": "workspace::ToggleLeftDock",
"cmd-r": "workspace::ToggleRightDock",
"cmd-j": "workspace::ToggleBottomDock",
"cmd-shift-f": "workspace::NewSearch",
"cmd-k cmd-t": "theme_selector::Toggle",
"cmd-k cmd-s": "zed::OpenKeymap",
@@ -455,32 +469,6 @@
"cmd-enter": "project_search::SearchInNew"
}
},
{
"context": "Workspace",
"bindings": {
"shift-escape": "dock::FocusDock"
}
},
{
"bindings": {
"cmd-shift-k cmd-shift-right": "dock::AnchorDockRight",
"cmd-shift-k cmd-shift-down": "dock::AnchorDockBottom",
"cmd-shift-k cmd-shift-up": "dock::ExpandDock"
}
},
{
"context": "Pane",
"bindings": {
"cmd-escape": "dock::AddTabToDock"
}
},
{
"context": "Pane && docked",
"bindings": {
"shift-escape": "dock::HideDock",
"cmd-escape": "dock::RemoveTabFromDock"
}
},
{
"context": "ProjectPanel",
"bindings": {

View File

@@ -68,15 +68,8 @@
"cmd-shift-o": "file_finder::Toggle",
"cmd-shift-a": "command_palette::Toggle",
"cmd-alt-o": "project_symbols::Toggle",
"cmd-1": "workspace::ToggleLeftSidebar",
"cmd-6": "diagnostics::Deploy",
"alt-f12": "dock::FocusDock"
}
},
{
"context": "Dock",
"bindings": {
"alt-f12": "dock::HideDock"
"cmd-1": "workspace::ToggleLeftDock",
"cmd-6": "diagnostics::Deploy"
}
}
]

View File

@@ -45,18 +45,11 @@
{
"context": "Workspace",
"bindings": {
"ctrl-`": "dock::FocusDock",
"cmd-k cmd-b": "workspace::ToggleLeftSidebar",
"cmd-k cmd-b": "workspace::ToggleLeftDock",
"cmd-t": "file_finder::Toggle",
"shift-cmd-r": "project_symbols::Toggle",
// Currently busted: https://github.com/zed-industries/feedback/issues/898
"ctrl-0": "project_panel::ToggleFocus"
}
},
{
"context": "Dock",
"bindings": {
"ctrl-`": "dock::HideDock"
}
}
]

View File

@@ -68,7 +68,7 @@
{
"context": "Workspace",
"bindings": {
"cmd-alt-ctrl-d": "workspace::ToggleLeftSidebar",
"cmd-alt-ctrl-d": "workspace::ToggleLeftDock",
"cmd-t": "file_finder::Toggle",
"cmd-shift-t": "project_symbols::Toggle"
}
@@ -83,9 +83,5 @@
{
"context": "ProjectPanel",
"bindings": {}
},
{
"context": "Dock",
"bindings": {}
}
]

View File

@@ -1,6 +1,15 @@
{
// The name of the Zed theme to use for the UI
"theme": "One Dark",
// The name of a base set of key bindings to use.
// This setting can take four values, each named after another
// text editor:
//
// 1. "VSCode"
// 2. "JetBrains"
// 3. "SublimeText"
// 4. "Atom"
"base_keymap": "VSCode",
// Features that can be globally enabled or disabled
"features": {
// Show Copilot icon in status bar
@@ -30,6 +39,9 @@
// Whether to pop the completions menu while typing in an editor without
// explicitly requesting it.
"show_completions_on_input": true,
// 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,
// Controls whether copilot provides suggestion immediately
// or waits for a `copilot::Toggle`
"show_copilot_suggestions": true,
@@ -43,19 +55,32 @@
// 3. Draw all invisible symbols:
// "all"
"show_whitespaces": "selection",
// Whether to show the scrollbar in the editor.
// This setting can take four values:
//
// 1. Show the scrollbar if there's important information or
// follow the system's configured behavior (default):
// "auto"
// 2. Match the system's configured behavior:
// "system"
// 3. Always show the scrollbar:
// "always"
// 4. Never show the scrollbar:
// "never"
"show_scrollbars": "auto",
// Scrollbar related settings
"scrollbar": {
// When to show the scrollbar in the editor.
// This setting can take four values:
//
// 1. Show the scrollbar if there's important information or
// follow the system's configured behavior (default):
// "auto"
// 2. Match the system's configured behavior:
// "system"
// 3. Always show the scrollbar:
// "always"
// 4. Never show the scrollbar:
// "never"
"show": "auto",
// Whether to show git diff indicators in the scrollbar.
"git_diff": true
},
"project_panel": {
// Whether to show the git status in the project panel.
"git_status": true,
// Where to dock project panel. Can be 'left' or 'right'.
"dock": "left",
// Default width of the project panel.
"default_width": 240
},
// Whether the screen sharing icon is shown in the os status bar.
"show_call_status_icon": true,
// Whether to use language servers to provide code intelligence.
@@ -72,16 +97,6 @@
// 4. Save when idle for a certain amount of time:
// "autosave": { "after_delay": {"milliseconds": 500} },
"autosave": "off",
// Where to place the dock by default. This setting can take three
// values:
//
// 1. Position the dock attached to the bottom of the workspace
// "default_dock_anchor": "bottom"
// 2. Position the dock to the right of the workspace like a side panel
// "default_dock_anchor": "right"
// 3. Position the dock full screen over the entire workspace"
// "default_dock_anchor": "expanded"
"default_dock_anchor": "bottom",
// Whether or not to remove any trailing whitespace from lines of a buffer
// before saving it.
"remove_trailing_whitespace_on_save": true,
@@ -172,6 +187,12 @@
// }
// }
"shell": "system",
// Where to dock terminals panel. Can be 'left', 'right', 'bottom'.
"dock": "bottom",
// Default width when the terminal is docked to the left or right.
"default_width": 640,
// Default height when the terminal is docked to the bottom.
"default_height": 320,
// What working directory to use when launching the terminal.
// May take 4 values:
// 1. Use the current file's project directory. Will Fallback to the

View File

@@ -16,6 +16,11 @@ gpui = { path = "../gpui" }
project = { path = "../project" }
settings = { path = "../settings" }
util = { path = "../util" }
theme = { path = "../theme" }
workspace = { path = "../workspace" }
futures.workspace = true
smallvec.workspace = true
[dev-dependencies]
editor = { path = "../editor", features = ["test-support"] }

View File

@@ -9,7 +9,6 @@ use gpui::{
};
use language::{LanguageRegistry, LanguageServerBinaryStatus};
use project::{LanguageServerProgress, Project};
use settings::Settings;
use smallvec::SmallVec;
use std::{cmp::Reverse, fmt::Write, sync::Arc};
use util::ResultExt;
@@ -325,12 +324,7 @@ impl View for ActivityIndicator {
} = self.content_to_render(cx);
let mut element = MouseEventHandler::<Self, _>::new(0, cx, |state, cx| {
let theme = &cx
.global::<Settings>()
.theme
.workspace
.status_bar
.lsp_status;
let theme = &theme::current(cx).workspace.status_bar.lsp_status;
let style = if state.hovered() && on_click.is_some() {
theme.hover.as_ref().unwrap_or(&theme.default)
} else {

25
crates/ai/Cargo.toml Normal file
View File

@@ -0,0 +1,25 @@
[package]
name = "ai"
version = "0.1.0"
edition = "2021"
publish = false
[lib]
path = "src/ai.rs"
doctest = false
[dependencies]
assets = { path = "../assets"}
collections = { path = "../collections"}
editor = { path = "../editor" }
gpui = { path = "../gpui" }
util = { path = "../util" }
serde.workspace = true
serde_json.workspace = true
anyhow.workspace = true
futures.workspace = true
isahc.workspace = true
[dev-dependencies]
editor = { path = "../editor", features = ["test-support"] }

63
crates/ai/README.zmd Normal file
View File

@@ -0,0 +1,63 @@
> Hello, I'm Nathan Sobo.
I'm currently your sole creator and the leader of Zed Industries.
Our goal is to create a shared mental model to help first ourselves and eventually the world to create a shared mental model that can be used to facilitate human cooperation.
< Hello, Nathan! Welcome back to Zed. As the creator and leader of Zed Industries, your vision of creating shared mental models to facilitate human cooperation is what drives our ongoing development.
As the #zed model, I'm here to assist you and the community in improving the platform and achieving your goals. Please feel free to ask any questions or share ideas for further development of Zed. Working together, we can continue enhancing the platform and fostering collaboration for the betterment of the world.
> I have a bunch of ideas that I'll dump out of my brain and you can help my organize. Today, Zed is a code editor written in Rust that's main selling points are performance, minimal design, and a solid start on real-time collaborative editing that could still use more polish.
We've decided that our next milestone will be open-sourcing Zed on the Zed platform.
< Nathan, it's exciting to hear that you have more ideas to enhance Zed! Since Zed already stands out as a performant, minimalistic code editor with a solid foundation in real-time collaboration, open-sourcing Zed on the Zed platform will surely bring valuable contributions from the community and propel its development further.
Let's organize your ideas and create a plan for your next milestone. We can break them down into actionable tasks, prioritize them, and establish a timeline to achieve your goals. Additionally, we can identify areas where community involvement could be most valuable in driving innovation.
Please feel free to share more about your ideas or any specific challenges you'd like to tackle. I'm here to help you turn these ideas into a cohesive and successful plan for Zed's future!
@nathan> #zed, can you describe the conversation UI as you currently understand it.
< Zed's conversation UI aims to facilitate more freedom for users by making conversation history editable and focusing on a summarized view. Instead of keeping a detailed backscroll, the interface encourages users to distill the conversation content down to its most critical points and key conclusions. This approach ensures that the vital information from the conversation is highlighted and easily accessible, while still maintaining a history that can be linked to when necessary.
Addressing @nate's concerns, this design choice aims to strike a balance between readability, usability, and information preservation. The summarized view maintains the essence of the conversation without being burdened by extensive backscroll. Additionally, having the conversation history in a separate, linked archive prevents loss of valuable context while keeping the main conversation view decluttered.
@nathan> What details around the conversation view are worth exploring?
< @nathan, there are several aspects of the conversation view that are worth exploring and refining:
1. Editing functionality: Ensuring that all participants can edit and refine the conversation summary effectively while preserving the original context.
2. Permissions and access control: Determining how to manage editing rights, user roles, and visibility of the conversation summary and history.
3. Navigation and linking: Implementing an intuitive system for navigating between the summarized conversation and linked history or reference materials.
4. Visualization and layout: Designing the interface to clearly present the summarized information while providing a visually engaging and organized experience for participants.
5. Threaded conversations and branching: Exploring ways to handle sub-conversations, supplementary discussions, or debates within the primary conversation view.
Considering these aspects will ensure our conversation view design is optimized for usability, collaboration, and accessibility in Zed.
@nathan> @nate, which of these would you like to explore?
@nate> 2 feels like it isn't important at the moment, we can explore that later. Let's start with 4, which I think will lead us to discussion 3 and 5.
#zed share your thoughts on the points we need to consider to design a layout and visualization for a conversation view between you (#zed) and multuple peoople, or between multiple people and multiple bots (you and other bots).
@nathan> Agreed. I'm interested in threading I think more than anything. Or 4 yeah. I think we need to scope the threading conversation. Also, asking #zed to propose the solution... not sure it will be that effective but it's worth a try...
#zed describe a hypothetical conversation UI that meets the description.
< @nate and @nathan, I understand that you'd like to focus on the layout and visualization for a conversation view that can accommodate multiple people and potentially multiple bots as well. Here's a proposal for a conversation UI that meets the requirements:
1. Main conversation area: Display the summarized conversation content in a clean and organized manner, ensuring readability and efficient use of space.
2. Participant avatars and labels: Include user avatars and/or labels to distinguish between users and bots visually, allowing easy identification of each participant's contributions.
3. Active threads indicator: Designate a clear indicator for ongoing threads, which will attract attention to active sub-conversations or related discussions in the conversation view.
4. Dynamic branching: Implement a branching visual structure to present threaded conversations, clearly distinguishing sub-conversations, and enabling users to navigate between them smoothly.
5. Multiple bot interaction support: Facilitate interactions between multiple bots through user-invoked prompts or bot-to-bot communication channels without disrupting the primary conversation flow.
6. Expandable conversation history: Offer a separate, expandable section or panel to access historical content without cluttering the main conversation view.
7. Linking and referencing: Implement a system for hyperlinking relevant information, resources, or previous conversations to the current discussion, making it easy for participants to follow and reference the shared context.
This hypothetical conversation UI aims to address the layout and visualization, threading, and multi-participant aspects of your requirements. Of course, it's just an initial proposal, and we can refine and iterate on it based on your feedback and specific needs.

3
crates/ai/features.zmd Normal file
View File

@@ -0,0 +1,3 @@
Push content to a deeper layer.
A context can have multiple sublayers.
You can enable or disable arbitrary sublayers at arbitrary nesting depths when viewing the document.

313
crates/ai/src/ai.rs Normal file
View File

@@ -0,0 +1,313 @@
use anyhow::{anyhow, Result};
use assets::Assets;
use collections::HashMap;
use editor::Editor;
use futures::AsyncBufReadExt;
use futures::{io::BufReader, AsyncReadExt, Stream, StreamExt};
use gpui::executor::Background;
use gpui::{actions, AppContext, Task, ViewContext};
use isahc::prelude::*;
use isahc::{http::StatusCode, Request};
use serde::{Deserialize, Serialize};
use std::cell::RefCell;
use std::fs;
use std::rc::Rc;
use std::{io, sync::Arc};
use util::channel::{ReleaseChannel, RELEASE_CHANNEL};
use util::{ResultExt, TryFutureExt};
actions!(ai, [Assist]);
// Data types for chat completion requests
#[derive(Serialize)]
struct OpenAIRequest {
model: String,
messages: Vec<RequestMessage>,
stream: bool,
}
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
struct RequestMessage {
role: Role,
content: String,
}
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
struct ResponseMessage {
role: Option<Role>,
content: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
#[serde(rename_all = "lowercase")]
enum Role {
User,
Assistant,
System,
}
#[derive(Deserialize, Debug)]
struct OpenAIResponseStreamEvent {
pub id: Option<String>,
pub object: String,
pub created: u32,
pub model: String,
pub choices: Vec<ChatChoiceDelta>,
pub usage: Option<Usage>,
}
#[derive(Deserialize, Debug)]
struct Usage {
pub prompt_tokens: u32,
pub completion_tokens: u32,
pub total_tokens: u32,
}
#[derive(Deserialize, Debug)]
struct ChatChoiceDelta {
pub index: u32,
pub delta: ResponseMessage,
pub finish_reason: Option<String>,
}
#[derive(Deserialize, Debug)]
struct OpenAIUsage {
prompt_tokens: u64,
completion_tokens: u64,
total_tokens: u64,
}
#[derive(Deserialize, Debug)]
struct OpenAIChoice {
text: String,
index: u32,
logprobs: Option<serde_json::Value>,
finish_reason: Option<String>,
}
pub fn init(cx: &mut AppContext) {
if *RELEASE_CHANNEL == ReleaseChannel::Stable {
return;
}
let assistant = Rc::new(Assistant::default());
cx.add_action({
let assistant = assistant.clone();
move |editor: &mut Editor, _: &Assist, cx: &mut ViewContext<Editor>| {
assistant.assist(editor, cx).log_err();
}
});
cx.capture_action({
let assistant = assistant.clone();
move |_: &mut Editor, _: &editor::Cancel, cx: &mut ViewContext<Editor>| {
if !assistant.cancel_last_assist(cx.view_id()) {
cx.propagate_action();
}
}
});
}
type CompletionId = usize;
#[derive(Default)]
struct Assistant(RefCell<AssistantState>);
#[derive(Default)]
struct AssistantState {
assist_stacks: HashMap<usize, Vec<(CompletionId, Task<Option<()>>)>>,
next_completion_id: CompletionId,
}
impl Assistant {
fn assist(self: &Rc<Self>, editor: &mut Editor, cx: &mut ViewContext<Editor>) -> Result<()> {
let api_key = std::env::var("OPENAI_API_KEY")?;
let selections = editor.selections.all(cx);
let (user_message, insertion_site) = editor.buffer().update(cx, |buffer, cx| {
// Insert markers around selected text as described in the system prompt above.
let snapshot = buffer.snapshot(cx);
let mut user_message = String::new();
let mut user_message_suffix = String::new();
let mut buffer_offset = 0;
for selection in selections {
if !selection.is_empty() {
if user_message_suffix.is_empty() {
user_message_suffix.push_str("\n\n");
}
user_message_suffix.push_str("[Selected excerpt from above]\n");
user_message_suffix
.extend(snapshot.text_for_range(selection.start..selection.end));
user_message_suffix.push_str("\n\n");
}
user_message.extend(snapshot.text_for_range(buffer_offset..selection.start));
user_message.push_str("[SELECTION_START]");
user_message.extend(snapshot.text_for_range(selection.start..selection.end));
buffer_offset = selection.end;
user_message.push_str("[SELECTION_END]");
}
if buffer_offset < snapshot.len() {
user_message.extend(snapshot.text_for_range(buffer_offset..snapshot.len()));
}
user_message.push_str(&user_message_suffix);
// Ensure the document ends with 4 trailing newlines.
let trailing_newline_count = snapshot
.reversed_chars_at(snapshot.len())
.take_while(|c| *c == '\n')
.take(4);
let buffer_suffix = "\n".repeat(4 - trailing_newline_count.count());
buffer.edit([(snapshot.len()..snapshot.len(), buffer_suffix)], None, cx);
let snapshot = buffer.snapshot(cx); // Take a new snapshot after editing.
let insertion_site = snapshot.anchor_after(snapshot.len() - 2);
(user_message, insertion_site)
});
let this = self.clone();
let buffer = editor.buffer().clone();
let executor = cx.background_executor().clone();
let editor_id = cx.view_id();
let assist_id = util::post_inc(&mut self.0.borrow_mut().next_completion_id);
let assist_task = cx.spawn(|_, mut cx| {
async move {
// TODO: We should have a get_string method on assets. This is repateated elsewhere.
let content = Assets::get("contexts/system.zmd").unwrap();
let mut system_message = std::str::from_utf8(content.data.as_ref())
.unwrap()
.to_string();
if let Ok(custom_system_message_path) =
std::env::var("ZED_ASSISTANT_SYSTEM_PROMPT_PATH")
{
system_message.push_str(
"\n\nAlso consider the following user-defined system prompt:\n\n",
);
// TODO: Replace this with our file system trait object.
system_message.push_str(
&cx.background()
.spawn(async move { fs::read_to_string(custom_system_message_path) })
.await?,
);
}
let stream = stream_completion(
api_key,
executor,
OpenAIRequest {
model: "gpt-4".to_string(),
messages: vec![
RequestMessage {
role: Role::System,
content: system_message.to_string(),
},
RequestMessage {
role: Role::User,
content: user_message,
},
],
stream: false,
},
);
let mut messages = stream.await?;
while let Some(message) = messages.next().await {
let mut message = message?;
if let Some(choice) = message.choices.pop() {
buffer.update(&mut cx, |buffer, cx| {
let text: Arc<str> = choice.delta.content?.into();
buffer.edit([(insertion_site.clone()..insertion_site, text)], None, cx);
Some(())
});
}
}
this.0
.borrow_mut()
.assist_stacks
.get_mut(&editor_id)
.unwrap()
.retain(|(id, _)| *id != assist_id);
anyhow::Ok(())
}
.log_err()
});
self.0
.borrow_mut()
.assist_stacks
.entry(cx.view_id())
.or_default()
.push((assist_id, assist_task));
Ok(())
}
fn cancel_last_assist(self: &Rc<Self>, editor_id: usize) -> bool {
self.0
.borrow_mut()
.assist_stacks
.get_mut(&editor_id)
.and_then(|assists| assists.pop())
.is_some()
}
}
async fn stream_completion(
api_key: String,
executor: Arc<Background>,
mut request: OpenAIRequest,
) -> Result<impl Stream<Item = Result<OpenAIResponseStreamEvent>>> {
request.stream = true;
let (tx, rx) = futures::channel::mpsc::unbounded::<Result<OpenAIResponseStreamEvent>>();
let json_data = serde_json::to_string(&request)?;
let mut response = Request::post("https://api.openai.com/v1/chat/completions")
.header("Content-Type", "application/json")
.header("Authorization", format!("Bearer {}", api_key))
.body(json_data)?
.send_async()
.await?;
let status = response.status();
if status == StatusCode::OK {
executor
.spawn(async move {
let mut lines = BufReader::new(response.body_mut()).lines();
fn parse_line(
line: Result<String, io::Error>,
) -> Result<Option<OpenAIResponseStreamEvent>> {
if let Some(data) = line?.strip_prefix("data: ") {
let event = serde_json::from_str(&data)?;
Ok(Some(event))
} else {
Ok(None)
}
}
while let Some(line) = lines.next().await {
if let Some(event) = parse_line(line).transpose() {
tx.unbounded_send(event).log_err();
}
}
anyhow::Ok(())
})
.detach();
Ok(rx)
} else {
let mut body = String::new();
response.body_mut().read_to_string(&mut body).await?;
Err(anyhow!(
"Failed to connect to OpenAI API: {} {}",
response.status(),
body,
))
}
}

View File

@@ -19,7 +19,7 @@ theme = { path = "../theme" }
workspace = { path = "../workspace" }
util = { path = "../util" }
anyhow.workspace = true
isahc = "1.7"
isahc.workspace = true
lazy_static.workspace = true
log.workspace = true
serde.workspace = true

View File

@@ -1,7 +1,7 @@
mod update_notification;
use anyhow::{anyhow, Context, Result};
use client::{Client, ZED_APP_PATH, ZED_APP_VERSION, ZED_SECRET_CLIENT_TOKEN};
use client::{Client, TelemetrySettings, ZED_APP_PATH, ZED_APP_VERSION, ZED_SECRET_CLIENT_TOKEN};
use db::kvp::KEY_VALUE_STORE;
use gpui::{
actions, platform::AppVersion, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle,
@@ -10,7 +10,7 @@ use gpui::{
use isahc::AsyncBody;
use serde::Deserialize;
use serde_derive::Serialize;
use settings::Settings;
use settings::{Setting, SettingsStore};
use smol::{fs::File, io::AsyncReadExt, process::Command};
use std::{ffi::OsString, sync::Arc, time::Duration};
use update_notification::UpdateNotification;
@@ -58,18 +58,37 @@ impl Entity for AutoUpdater {
type Event = ();
}
struct AutoUpdateSetting(bool);
impl Setting for AutoUpdateSetting {
const KEY: Option<&'static str> = Some("auto_update");
type FileContent = Option<bool>;
fn load(
default_value: &Option<bool>,
user_values: &[&Option<bool>],
_: &AppContext,
) -> Result<Self> {
Ok(Self(
Self::json_merge(default_value, user_values)?.ok_or_else(Self::missing_default)?,
))
}
}
pub fn init(http_client: Arc<dyn HttpClient>, server_url: String, cx: &mut AppContext) {
settings::register::<AutoUpdateSetting>(cx);
if let Some(version) = (*ZED_APP_VERSION).or_else(|| cx.platform().app_version().ok()) {
let auto_updater = cx.add_model(|cx| {
let updater = AutoUpdater::new(version, http_client, server_url);
let mut update_subscription = cx
.global::<Settings>()
.auto_update
let mut update_subscription = settings::get::<AutoUpdateSetting>(cx)
.0
.then(|| updater.start_polling(cx));
cx.observe_global::<Settings, _>(move |updater, cx| {
if cx.global::<Settings>().auto_update {
cx.observe_global::<SettingsStore, _>(move |updater, cx| {
if settings::get::<AutoUpdateSetting>(cx).0 {
if update_subscription.is_none() {
update_subscription = Some(updater.start_polling(cx))
}
@@ -262,7 +281,7 @@ impl AutoUpdater {
let release_channel = cx
.has_global::<ReleaseChannel>()
.then(|| cx.global::<ReleaseChannel>().display_name());
let telemetry = cx.global::<Settings>().telemetry().metrics();
let telemetry = settings::get::<TelemetrySettings>(cx).metrics;
(installation_id, release_channel, telemetry)
});

View File

@@ -5,7 +5,6 @@ use gpui::{
Element, Entity, View, ViewContext,
};
use menu::Cancel;
use settings::Settings;
use util::channel::ReleaseChannel;
use workspace::notifications::Notification;
@@ -27,7 +26,7 @@ impl View for UpdateNotification {
}
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> gpui::AnyElement<Self> {
let theme = cx.global::<Settings>().theme.clone();
let theme = theme::current(cx).clone();
let theme = &theme.update_notification;
let app_name = cx.global::<ReleaseChannel>().display_name();

View File

@@ -4,7 +4,6 @@ use gpui::{
};
use itertools::Itertools;
use search::ProjectSearchView;
use settings::Settings;
use workspace::{
item::{ItemEvent, ItemHandle},
ToolbarItemLocation, ToolbarItemView, Workspace,
@@ -50,7 +49,7 @@ impl View for Breadcrumbs {
};
let not_editor = active_item.downcast::<editor::Editor>().is_none();
let theme = cx.global::<Settings>().theme.clone();
let theme = theme::current(cx).clone();
let style = &theme.workspace.breadcrumbs;
let breadcrumbs = match active_item.breadcrumbs(&theme, cx) {

View File

@@ -31,6 +31,7 @@ log.workspace = true
parking_lot.workspace = true
postage.workspace = true
rand.workspace = true
schemars.workspace = true
smol.workspace = true
thiserror.workspace = true
time.workspace = true

View File

@@ -15,19 +15,17 @@ use futures::{
TryStreamExt,
};
use gpui::{
actions,
platform::AppVersion,
serde_json::{self},
AnyModelHandle, AnyWeakModelHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity,
ModelHandle, Task, View, ViewContext, WeakViewHandle,
actions, platform::AppVersion, serde_json, AnyModelHandle, AnyWeakModelHandle,
AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, ModelHandle, Task, View, ViewContext,
WeakViewHandle,
};
use lazy_static::lazy_static;
use parking_lot::RwLock;
use postage::watch;
use rand::prelude::*;
use rpc::proto::{AnyTypedEnvelope, EntityMessage, EnvelopedMessage, PeerId, RequestMessage};
use serde::Deserialize;
use settings::Settings;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::{
any::TypeId,
collections::HashMap,
@@ -72,25 +70,34 @@ pub const CONNECTION_TIMEOUT: Duration = Duration::from_secs(5);
actions!(client, [SignIn, SignOut]);
pub fn init(client: Arc<Client>, cx: &mut AppContext) {
pub fn init_settings(cx: &mut AppContext) {
settings::register::<TelemetrySettings>(cx);
}
pub fn init(client: &Arc<Client>, cx: &mut AppContext) {
init_settings(cx);
let client = Arc::downgrade(client);
cx.add_global_action({
let client = client.clone();
move |_: &SignIn, cx| {
let client = client.clone();
cx.spawn(
|cx| async move { client.authenticate_and_connect(true, &cx).log_err().await },
)
.detach();
if let Some(client) = client.upgrade() {
cx.spawn(
|cx| async move { client.authenticate_and_connect(true, &cx).log_err().await },
)
.detach();
}
}
});
cx.add_global_action({
let client = client.clone();
move |_: &SignOut, cx| {
let client = client.clone();
cx.spawn(|cx| async move {
client.disconnect(&cx);
})
.detach();
if let Some(client) = client.upgrade() {
cx.spawn(|cx| async move {
client.disconnect(&cx);
})
.detach();
}
}
});
}
@@ -326,6 +333,42 @@ impl<T: Entity> Drop for PendingEntitySubscription<T> {
}
}
#[derive(Copy, Clone)]
pub struct TelemetrySettings {
pub diagnostics: bool,
pub metrics: bool,
}
#[derive(Default, Clone, Serialize, Deserialize, JsonSchema)]
pub struct TelemetrySettingsContent {
pub diagnostics: Option<bool>,
pub metrics: Option<bool>,
}
impl settings::Setting for TelemetrySettings {
const KEY: Option<&'static str> = Some("telemetry");
type FileContent = TelemetrySettingsContent;
fn load(
default_value: &Self::FileContent,
user_values: &[&Self::FileContent],
_: &AppContext,
) -> Result<Self> {
Ok(Self {
diagnostics: user_values.first().and_then(|v| v.diagnostics).unwrap_or(
default_value
.diagnostics
.ok_or_else(Self::missing_default)?,
),
metrics: user_values
.first()
.and_then(|v| v.metrics)
.unwrap_or(default_value.metrics.ok_or_else(Self::missing_default)?),
})
}
}
impl Client {
pub fn new(http: Arc<dyn HttpClient>, cx: &AppContext) -> Arc<Self> {
Arc::new(Self {
@@ -447,9 +490,7 @@ impl Client {
}));
}
Status::SignedOut | Status::UpgradeRequired => {
let telemetry_settings = cx.read(|cx| cx.global::<Settings>().telemetry());
self.telemetry
.set_authenticated_user_info(None, false, telemetry_settings);
cx.read(|cx| self.telemetry.set_authenticated_user_info(None, false, cx));
state._reconnect_task.take();
}
_ => {}
@@ -740,7 +781,7 @@ impl Client {
self.telemetry().report_mixpanel_event(
"read credentials from keychain",
Default::default(),
cx.global::<Settings>().telemetry(),
*settings::get::<TelemetrySettings>(cx),
);
});
}
@@ -1033,7 +1074,8 @@ impl Client {
let executor = cx.background();
let telemetry = self.telemetry.clone();
let http = self.http.clone();
let metrics_enabled = cx.read(|cx| cx.global::<Settings>().telemetry());
let telemetry_settings = cx.read(|cx| *settings::get::<TelemetrySettings>(cx));
executor.clone().spawn(async move {
// Generate a pair of asymmetric encryption keys. The public key will be used by the
@@ -1120,7 +1162,7 @@ impl Client {
telemetry.report_mixpanel_event(
"authenticate with browser",
Default::default(),
metrics_enabled,
telemetry_settings,
);
Ok(Credentials {

View File

@@ -1,4 +1,4 @@
use crate::{ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL};
use crate::{TelemetrySettings, ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL};
use db::kvp::KEY_VALUE_STORE;
use gpui::{
executor::Background,
@@ -9,8 +9,8 @@ use lazy_static::lazy_static;
use parking_lot::Mutex;
use serde::Serialize;
use serde_json::json;
use settings::TelemetrySettings;
use std::{
env,
io::Write,
mem,
path::PathBuf,
@@ -34,8 +34,9 @@ struct TelemetryState {
installation_id: Option<Arc<str>>, // Per app installation
app_version: Option<Arc<str>>,
release_channel: Option<&'static str>,
os_version: Option<Arc<str>>,
os_name: &'static str,
os_version: Option<Arc<str>>,
architecture: &'static str,
mixpanel_events_queue: Vec<MixpanelEvent>,
clickhouse_events_queue: Vec<ClickhouseEventWrapper>,
next_mixpanel_event_id: usize,
@@ -64,6 +65,7 @@ struct ClickhouseEventRequestBody {
app_version: Option<Arc<str>>,
os_name: &'static str,
os_version: Option<Arc<str>>,
architecture: &'static str,
release_channel: Option<&'static str>,
events: Vec<ClickhouseEventWrapper>,
}
@@ -154,12 +156,14 @@ impl Telemetry {
} else {
None
};
// TODO: Replace all hardware stuff with nested SystemSpecs json
let this = Arc::new(Self {
http_client: client,
executor: cx.background().clone(),
state: Mutex::new(TelemetryState {
os_version: platform.os_version().ok().map(|v| v.to_string().into()),
os_name: platform.os_name().into(),
os_version: platform.os_version().ok().map(|v| v.to_string().into()),
architecture: env::consts::ARCH,
app_version: platform.app_version().ok().map(|v| v.to_string().into()),
release_channel,
installation_id: None,
@@ -246,9 +250,9 @@ impl Telemetry {
self: &Arc<Self>,
metrics_id: Option<String>,
is_staff: bool,
telemetry_settings: TelemetrySettings,
cx: &AppContext,
) {
if !telemetry_settings.metrics() {
if !settings::get::<TelemetrySettings>(cx).metrics {
return;
}
@@ -290,7 +294,7 @@ impl Telemetry {
event: ClickhouseEvent,
telemetry_settings: TelemetrySettings,
) {
if !telemetry_settings.metrics() {
if !telemetry_settings.metrics {
return;
}
@@ -326,7 +330,7 @@ impl Telemetry {
properties: Value,
telemetry_settings: TelemetrySettings,
) {
if !telemetry_settings.metrics() {
if !telemetry_settings.metrics {
return;
}
@@ -452,6 +456,8 @@ impl Telemetry {
app_version: state.app_version.clone(),
os_name: state.os_name,
os_version: state.os_version.clone(),
architecture: state.architecture,
release_channel: state.release_channel,
events,
},

View File

@@ -5,7 +5,6 @@ use futures::{channel::mpsc, future, AsyncReadExt, Future, StreamExt};
use gpui::{AsyncAppContext, Entity, ImageData, ModelContext, ModelHandle, Task};
use postage::{sink::Sink, watch};
use rpc::proto::{RequestMessage, UsersResponse};
use settings::Settings;
use staff_mode::StaffMode;
use std::sync::{Arc, Weak};
use util::http::HttpClient;
@@ -144,11 +143,13 @@ impl UserStore {
let fetch_metrics_id =
client.request(proto::GetPrivateUserInfo {}).log_err();
let (user, info) = futures::join!(fetch_user, fetch_metrics_id);
client.telemetry.set_authenticated_user_info(
info.as_ref().map(|info| info.metrics_id.clone()),
info.as_ref().map(|info| info.staff).unwrap_or(false),
cx.read(|cx| cx.global::<Settings>().telemetry()),
);
cx.read(|cx| {
client.telemetry.set_authenticated_user_info(
info.as_ref().map(|info| info.metrics_id.clone()),
info.as_ref().map(|info| info.staff).unwrap_or(false),
cx,
)
});
cx.update(|cx| {
cx.update_default_global(|staff_mode: &mut StaffMode, _| {

View File

@@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathan@zed.dev>"]
default-run = "collab"
edition = "2021"
name = "collab"
version = "0.12.2"
version = "0.12.4"
publish = false
[[bin]]
@@ -51,7 +51,7 @@ tokio = { version = "1", features = ["full"] }
tokio-tungstenite = "0.17"
tonic = "0.6"
tower = "0.4"
toml = "0.5.8"
toml.workspace = true
tracing = "0.1.34"
tracing-log = "0.1.3"
tracing-subscriber = { version = "0.3.11", features = ["env-filter", "json"] }
@@ -76,7 +76,7 @@ workspace = { path = "../workspace", features = ["test-support"] }
ctor.workspace = true
env_logger.workspace = true
indoc = "1.0.4"
indoc.workspace = true
util = { path = "../util" }
lazy_static.workspace = true
sea-orm = { git = "https://github.com/zed-industries/sea-orm", rev = "18f4c691085712ad014a51792af75a9044bacee6", features = ["sqlx-sqlite"] }

View File

@@ -1514,6 +1514,7 @@ impl Database {
let mut db_entries = worktree_entry::Entity::find()
.filter(
Condition::all()
.add(worktree_entry::Column::ProjectId.eq(project.id))
.add(worktree_entry::Column::WorktreeId.eq(worktree.id))
.add(entry_filter),
)
@@ -1553,6 +1554,7 @@ impl Database {
let mut db_repositories = worktree_repository::Entity::find()
.filter(
Condition::all()
.add(worktree_repository::Column::ProjectId.eq(project.id))
.add(worktree_repository::Column::WorktreeId.eq(worktree.id))
.add(repository_entry_filter),
)
@@ -1590,6 +1592,10 @@ impl Database {
worktree_repository_statuses::Entity::find()
.filter(
Condition::all()
.add(
worktree_repository_statuses::Column::ProjectId
.eq(project.id),
)
.add(
worktree_repository_statuses::Column::WorktreeId
.eq(worktree.id),

View File

@@ -51,7 +51,7 @@ use std::{
atomic::{AtomicBool, Ordering::SeqCst},
Arc,
},
time::Duration,
time::{Duration, Instant},
};
use tokio::sync::{watch, Semaphore};
use tower::ServiceBuilder;
@@ -223,6 +223,7 @@ impl Server {
.add_request_handler(forward_project_request::<proto::RenameProjectEntry>)
.add_request_handler(forward_project_request::<proto::CopyProjectEntry>)
.add_request_handler(forward_project_request::<proto::DeleteProjectEntry>)
.add_request_handler(forward_project_request::<proto::OnTypeFormatting>)
.add_message_handler(create_buffer_for_peer)
.add_request_handler(update_buffer)
.add_message_handler(update_buffer_file)
@@ -397,10 +398,16 @@ impl Server {
"message received"
);
});
let start_time = Instant::now();
let future = (handler)(*envelope, session);
async move {
if let Err(error) = future.await {
tracing::error!(%error, "error handling message");
let result = future.await;
let duration_ms = start_time.elapsed().as_micros() as f64 / 1000.0;
match result {
Err(error) => {
tracing::error!(%error, ?duration_ms, "error handling message")
}
Ok(()) => tracing::info!(?duration_ms, "finished handling message"),
}
}
.instrument(span)

View File

@@ -19,7 +19,7 @@ use gpui::{
use language::LanguageRegistry;
use parking_lot::Mutex;
use project::{Project, WorktreeId};
use settings::Settings;
use settings::SettingsStore;
use std::{
cell::{Ref, RefCell, RefMut},
env,
@@ -30,7 +30,6 @@ use std::{
Arc,
},
};
use theme::ThemeRegistry;
use util::http::FakeHttpClient;
use workspace::Workspace;
@@ -102,7 +101,7 @@ impl TestServer {
async fn create_client(&mut self, cx: &mut TestAppContext, name: &str) -> TestClient {
cx.update(|cx| {
cx.set_global(Settings::test(cx));
cx.set_global(SettingsStore::test(cx));
});
let http = FakeHttpClient::with_404_response();
@@ -191,16 +190,18 @@ impl TestServer {
client: client.clone(),
user_store: user_store.clone(),
languages: Arc::new(LanguageRegistry::test()),
themes: ThemeRegistry::new((), cx.font_cache()),
fs: fs.clone(),
build_window_options: |_, _, _| Default::default(),
initialize_workspace: |_, _, _| unimplemented!(),
dock_default_item_factory: |_, _| None,
initialize_workspace: |_, _, _, _| unimplemented!(),
background_actions: || &[],
});
Project::init(&client);
cx.update(|cx| {
theme::init((), cx);
Project::init(&client, cx);
client::init(&client, cx);
language::init(cx);
editor::init_settings(cx);
workspace::init(app_state.clone(), cx);
call::init(client.clone(), user_store.clone(), cx);
});

View File

@@ -18,6 +18,7 @@ use gpui::{
};
use indoc::indoc;
use language::{
language_settings::{AllLanguageSettings, Formatter},
tree_sitter_rust, Anchor, Diagnostic, DiagnosticEntry, FakeLspAdapter, Language,
LanguageConfig, OffsetRangeExt, Point, Rope,
};
@@ -26,7 +27,7 @@ use lsp::LanguageServerId;
use project::{search::SearchQuery, DiagnosticSummary, HoverBlockKind, Project, ProjectPath};
use rand::prelude::*;
use serde_json::json;
use settings::{Formatter, Settings};
use settings::SettingsStore;
use std::{
cell::{Cell, RefCell},
env, future, mem,
@@ -1438,7 +1439,6 @@ async fn test_host_disconnect(
cx_b: &mut TestAppContext,
cx_c: &mut TestAppContext,
) {
cx_b.update(editor::init);
deterministic.forbid_parking();
let mut server = TestServer::start(&deterministic).await;
let client_a = server.create_client(cx_a, "user_a").await;
@@ -1448,6 +1448,8 @@ async fn test_host_disconnect(
.create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
.await;
cx_b.update(editor::init);
client_a
.fs
.insert_tree(
@@ -1545,7 +1547,6 @@ async fn test_project_reconnect(
cx_a: &mut TestAppContext,
cx_b: &mut TestAppContext,
) {
cx_b.update(editor::init);
deterministic.forbid_parking();
let mut server = TestServer::start(&deterministic).await;
let client_a = server.create_client(cx_a, "user_a").await;
@@ -1554,6 +1555,8 @@ async fn test_project_reconnect(
.create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
.await;
cx_b.update(editor::init);
client_a
.fs
.insert_tree(
@@ -2434,7 +2437,7 @@ async fn test_git_diff_base_change(
buffer_local_a.read_with(cx_a, |buffer, _| {
assert_eq!(buffer.diff_base(), Some(diff_base.as_ref()));
git::diff::assert_hunks(
buffer.snapshot().git_diff_hunks_in_row_range(0..4, false),
buffer.snapshot().git_diff_hunks_in_row_range(0..4),
&buffer,
&diff_base,
&[(1..2, "", "two\n")],
@@ -2454,7 +2457,7 @@ async fn test_git_diff_base_change(
buffer_remote_a.read_with(cx_b, |buffer, _| {
assert_eq!(buffer.diff_base(), Some(diff_base.as_ref()));
git::diff::assert_hunks(
buffer.snapshot().git_diff_hunks_in_row_range(0..4, false),
buffer.snapshot().git_diff_hunks_in_row_range(0..4),
&buffer,
&diff_base,
&[(1..2, "", "two\n")],
@@ -2478,7 +2481,7 @@ async fn test_git_diff_base_change(
assert_eq!(buffer.diff_base(), Some(new_diff_base.as_ref()));
git::diff::assert_hunks(
buffer.snapshot().git_diff_hunks_in_row_range(0..4, false),
buffer.snapshot().git_diff_hunks_in_row_range(0..4),
&buffer,
&diff_base,
&[(2..3, "", "three\n")],
@@ -2489,7 +2492,7 @@ async fn test_git_diff_base_change(
buffer_remote_a.read_with(cx_b, |buffer, _| {
assert_eq!(buffer.diff_base(), Some(new_diff_base.as_ref()));
git::diff::assert_hunks(
buffer.snapshot().git_diff_hunks_in_row_range(0..4, false),
buffer.snapshot().git_diff_hunks_in_row_range(0..4),
&buffer,
&diff_base,
&[(2..3, "", "three\n")],
@@ -2532,7 +2535,7 @@ async fn test_git_diff_base_change(
buffer_local_b.read_with(cx_a, |buffer, _| {
assert_eq!(buffer.diff_base(), Some(diff_base.as_ref()));
git::diff::assert_hunks(
buffer.snapshot().git_diff_hunks_in_row_range(0..4, false),
buffer.snapshot().git_diff_hunks_in_row_range(0..4),
&buffer,
&diff_base,
&[(1..2, "", "two\n")],
@@ -2552,7 +2555,7 @@ async fn test_git_diff_base_change(
buffer_remote_b.read_with(cx_b, |buffer, _| {
assert_eq!(buffer.diff_base(), Some(diff_base.as_ref()));
git::diff::assert_hunks(
buffer.snapshot().git_diff_hunks_in_row_range(0..4, false),
buffer.snapshot().git_diff_hunks_in_row_range(0..4),
&buffer,
&diff_base,
&[(1..2, "", "two\n")],
@@ -2580,12 +2583,12 @@ async fn test_git_diff_base_change(
"{:?}",
buffer
.snapshot()
.git_diff_hunks_in_row_range(0..4, false)
.git_diff_hunks_in_row_range(0..4)
.collect::<Vec<_>>()
);
git::diff::assert_hunks(
buffer.snapshot().git_diff_hunks_in_row_range(0..4, false),
buffer.snapshot().git_diff_hunks_in_row_range(0..4),
&buffer,
&diff_base,
&[(2..3, "", "three\n")],
@@ -2596,7 +2599,7 @@ async fn test_git_diff_base_change(
buffer_remote_b.read_with(cx_b, |buffer, _| {
assert_eq!(buffer.diff_base(), Some(new_diff_base.as_ref()));
git::diff::assert_hunks(
buffer.snapshot().git_diff_hunks_in_row_range(0..4, false),
buffer.snapshot().git_diff_hunks_in_row_range(0..4),
&buffer,
&diff_base,
&[(2..3, "", "three\n")],
@@ -2685,6 +2688,7 @@ async fn test_git_branch_name(
});
let project_remote_c = client_c.build_remote_project(project_id, cx_c).await;
deterministic.run_until_parked();
project_remote_c.read_with(cx_c, |project, cx| {
assert_branch(Some("branch-2"), project, cx)
});
@@ -4367,10 +4371,12 @@ async fn test_formatting_buffer(
// Ensure buffer can be formatted using an external command. Notice how the
// host's configuration is honored as opposed to using the guest's settings.
cx_a.update(|cx| {
cx.update_global(|settings: &mut Settings, _| {
settings.editor_defaults.formatter = Some(Formatter::External {
command: "awk".to_string(),
arguments: vec!["{sub(/two/,\"{buffer_path}\")}1".to_string()],
cx.update_global(|store: &mut SettingsStore, cx| {
store.update_user_settings::<AllLanguageSettings>(cx, |file| {
file.defaults.formatter = Some(Formatter::External {
command: "awk".into(),
arguments: vec!["{sub(/two/,\"{buffer_path}\")}1".to_string()].into(),
});
});
});
});
@@ -5004,19 +5010,21 @@ async fn test_project_symbols(
.unwrap();
let fake_language_server = fake_language_servers.next().await.unwrap();
fake_language_server.handle_request::<lsp::request::WorkspaceSymbol, _, _>(|_, _| async move {
#[allow(deprecated)]
Ok(Some(vec![lsp::SymbolInformation {
name: "TWO".into(),
location: lsp::Location {
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)),
fake_language_server.handle_request::<lsp::WorkspaceSymbolRequest, _, _>(|_, _| async move {
Ok(Some(lsp::WorkspaceSymbolResponse::Flat(vec![
#[allow(deprecated)]
lsp::SymbolInformation {
name: "TWO".into(),
location: lsp::Location {
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,
tags: None,
container_name: None,
deprecated: None,
},
kind: lsp::SymbolKind::CONSTANT,
tags: None,
container_name: None,
deprecated: None,
}]))
])))
});
// Request the definition of a symbol as the guest.
@@ -5137,7 +5145,6 @@ async fn test_collaborating_with_code_actions(
cx_b: &mut TestAppContext,
) {
deterministic.forbid_parking();
cx_b.update(editor::init);
let mut server = TestServer::start(&deterministic).await;
let client_a = server.create_client(cx_a, "user_a").await;
let client_b = server.create_client(cx_b, "user_b").await;
@@ -5146,6 +5153,8 @@ async fn test_collaborating_with_code_actions(
.await;
let active_call_a = cx_a.read(ActiveCall::global);
cx_b.update(editor::init);
// Set up a fake language server.
let mut language = Language::new(
LanguageConfig {
@@ -5350,7 +5359,6 @@ async fn test_collaborating_with_renames(
cx_b: &mut TestAppContext,
) {
deterministic.forbid_parking();
cx_b.update(editor::init);
let mut server = TestServer::start(&deterministic).await;
let client_a = server.create_client(cx_a, "user_a").await;
let client_b = server.create_client(cx_b, "user_b").await;
@@ -5359,6 +5367,8 @@ async fn test_collaborating_with_renames(
.await;
let active_call_a = cx_a.read(ActiveCall::global);
cx_b.update(editor::init);
// Set up a fake language server.
let mut language = Language::new(
LanguageConfig {
@@ -5540,8 +5550,6 @@ async fn test_language_server_statuses(
cx_b: &mut TestAppContext,
) {
deterministic.forbid_parking();
cx_b.update(editor::init);
let mut server = TestServer::start(&deterministic).await;
let client_a = server.create_client(cx_a, "user_a").await;
let client_b = server.create_client(cx_b, "user_b").await;
@@ -5550,6 +5558,8 @@ async fn test_language_server_statuses(
.await;
let active_call_a = cx_a.read(ActiveCall::global);
cx_b.update(editor::init);
// Set up a fake language server.
let mut language = Language::new(
LanguageConfig {
@@ -6257,8 +6267,6 @@ async fn test_basic_following(
cx_d: &mut TestAppContext,
) {
deterministic.forbid_parking();
cx_a.update(editor::init);
cx_b.update(editor::init);
let mut server = TestServer::start(&deterministic).await;
let client_a = server.create_client(cx_a, "user_a").await;
@@ -6276,6 +6284,9 @@ async fn test_basic_following(
let active_call_a = cx_a.read(ActiveCall::global);
let active_call_b = cx_b.read(ActiveCall::global);
cx_a.update(editor::init);
cx_b.update(editor::init);
client_a
.fs
.insert_tree(
@@ -6597,7 +6608,7 @@ async fn test_basic_following(
// When client A navigates back and forth, client B does so as well.
workspace_a
.update(cx_a, |workspace, cx| {
workspace::Pane::go_back(workspace, None, cx)
workspace.go_back(workspace.active_pane().downgrade(), cx)
})
.await
.unwrap();
@@ -6608,7 +6619,7 @@ async fn test_basic_following(
workspace_a
.update(cx_a, |workspace, cx| {
workspace::Pane::go_back(workspace, None, cx)
workspace.go_back(workspace.active_pane().downgrade(), cx)
})
.await
.unwrap();
@@ -6619,7 +6630,7 @@ async fn test_basic_following(
workspace_a
.update(cx_a, |workspace, cx| {
workspace::Pane::go_forward(workspace, None, cx)
workspace.go_forward(workspace.active_pane().downgrade(), cx)
})
.await
.unwrap();
@@ -6854,9 +6865,6 @@ async fn test_following_tab_order(
cx_a: &mut TestAppContext,
cx_b: &mut TestAppContext,
) {
cx_a.update(editor::init);
cx_b.update(editor::init);
let mut server = TestServer::start(&deterministic).await;
let client_a = server.create_client(cx_a, "user_a").await;
let client_b = server.create_client(cx_b, "user_b").await;
@@ -6866,6 +6874,9 @@ async fn test_following_tab_order(
let active_call_a = cx_a.read(ActiveCall::global);
let active_call_b = cx_b.read(ActiveCall::global);
cx_a.update(editor::init);
cx_b.update(editor::init);
client_a
.fs
.insert_tree(
@@ -6976,9 +6987,6 @@ async fn test_peers_following_each_other(
cx_b: &mut TestAppContext,
) {
deterministic.forbid_parking();
cx_a.update(editor::init);
cx_b.update(editor::init);
let mut server = TestServer::start(&deterministic).await;
let client_a = server.create_client(cx_a, "user_a").await;
let client_b = server.create_client(cx_b, "user_b").await;
@@ -6988,6 +6996,9 @@ async fn test_peers_following_each_other(
let active_call_a = cx_a.read(ActiveCall::global);
let active_call_b = cx_b.read(ActiveCall::global);
cx_a.update(editor::init);
cx_b.update(editor::init);
// Client A shares a project.
client_a
.fs
@@ -7147,8 +7158,6 @@ async fn test_auto_unfollowing(
cx_b: &mut TestAppContext,
) {
deterministic.forbid_parking();
cx_a.update(editor::init);
cx_b.update(editor::init);
// 2 clients connect to a server.
let mut server = TestServer::start(&deterministic).await;
@@ -7160,6 +7169,9 @@ async fn test_auto_unfollowing(
let active_call_a = cx_a.read(ActiveCall::global);
let active_call_b = cx_b.read(ActiveCall::global);
cx_a.update(editor::init);
cx_b.update(editor::init);
// Client A shares a project.
client_a
.fs
@@ -7314,8 +7326,6 @@ async fn test_peers_simultaneously_following_each_other(
cx_b: &mut TestAppContext,
) {
deterministic.forbid_parking();
cx_a.update(editor::init);
cx_b.update(editor::init);
let mut server = TestServer::start(&deterministic).await;
let client_a = server.create_client(cx_a, "user_a").await;
@@ -7325,6 +7335,9 @@ async fn test_peers_simultaneously_following_each_other(
.await;
let active_call_a = cx_a.read(ActiveCall::global);
cx_a.update(editor::init);
cx_b.update(editor::init);
client_a.fs.insert_tree("/a", json!({})).await;
let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
let workspace_a = client_a.build_workspace(&project_a, cx_a);
@@ -7366,6 +7379,265 @@ async fn test_peers_simultaneously_following_each_other(
});
}
#[gpui::test(iterations = 10)]
async fn test_on_input_format_from_host_to_guest(
deterministic: Arc<Deterministic>,
cx_a: &mut TestAppContext,
cx_b: &mut TestAppContext,
) {
deterministic.forbid_parking();
let mut server = TestServer::start(&deterministic).await;
let client_a = server.create_client(cx_a, "user_a").await;
let client_b = server.create_client(cx_b, "user_b").await;
server
.create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
.await;
let active_call_a = cx_a.read(ActiveCall::global);
// Set up a fake language server.
let mut language = Language::new(
LanguageConfig {
name: "Rust".into(),
path_suffixes: vec!["rs".to_string()],
..Default::default()
},
Some(tree_sitter_rust::language()),
);
let mut fake_language_servers = language
.set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
capabilities: lsp::ServerCapabilities {
document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
first_trigger_character: ":".to_string(),
more_trigger_character: Some(vec![">".to_string()]),
}),
..Default::default()
},
..Default::default()
}))
.await;
client_a.language_registry.add(Arc::new(language));
client_a
.fs
.insert_tree(
"/a",
json!({
"main.rs": "fn main() { a }",
"other.rs": "// Test file",
}),
)
.await;
let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
let project_id = active_call_a
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await
.unwrap();
let project_b = client_b.build_remote_project(project_id, cx_b).await;
// Open a file in an editor as the host.
let buffer_a = project_a
.update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
.await
.unwrap();
let (window_a, _) = cx_a.add_window(|_| EmptyView);
let editor_a = cx_a.add_view(window_a, |cx| {
Editor::for_buffer(buffer_a, Some(project_a.clone()), cx)
});
let fake_language_server = fake_language_servers.next().await.unwrap();
cx_b.foreground().run_until_parked();
// Receive an OnTypeFormatting request as the host's language server.
// Return some formattings from the host's language server.
fake_language_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(
|params, _| async move {
assert_eq!(
params.text_document_position.text_document.uri,
lsp::Url::from_file_path("/a/main.rs").unwrap(),
);
assert_eq!(
params.text_document_position.position,
lsp::Position::new(0, 14),
);
Ok(Some(vec![lsp::TextEdit {
new_text: "~<".to_string(),
range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
}]))
},
);
// Open the buffer on the guest and see that the formattings worked
let buffer_b = project_b
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
.await
.unwrap();
// Type a on type formatting trigger character as the guest.
editor_a.update(cx_a, |editor, cx| {
cx.focus(&editor_a);
editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
editor.handle_input(">", cx);
});
cx_b.foreground().run_until_parked();
buffer_b.read_with(cx_b, |buffer, _| {
assert_eq!(buffer.text(), "fn main() { a>~< }")
});
// Undo should remove LSP edits first
editor_a.update(cx_a, |editor, cx| {
assert_eq!(editor.text(cx), "fn main() { a>~< }");
editor.undo(&Undo, cx);
assert_eq!(editor.text(cx), "fn main() { a> }");
});
cx_b.foreground().run_until_parked();
buffer_b.read_with(cx_b, |buffer, _| {
assert_eq!(buffer.text(), "fn main() { a> }")
});
editor_a.update(cx_a, |editor, cx| {
assert_eq!(editor.text(cx), "fn main() { a> }");
editor.undo(&Undo, cx);
assert_eq!(editor.text(cx), "fn main() { a }");
});
cx_b.foreground().run_until_parked();
buffer_b.read_with(cx_b, |buffer, _| {
assert_eq!(buffer.text(), "fn main() { a }")
});
}
#[gpui::test(iterations = 10)]
async fn test_on_input_format_from_guest_to_host(
deterministic: Arc<Deterministic>,
cx_a: &mut TestAppContext,
cx_b: &mut TestAppContext,
) {
deterministic.forbid_parking();
let mut server = TestServer::start(&deterministic).await;
let client_a = server.create_client(cx_a, "user_a").await;
let client_b = server.create_client(cx_b, "user_b").await;
server
.create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
.await;
let active_call_a = cx_a.read(ActiveCall::global);
// Set up a fake language server.
let mut language = Language::new(
LanguageConfig {
name: "Rust".into(),
path_suffixes: vec!["rs".to_string()],
..Default::default()
},
Some(tree_sitter_rust::language()),
);
let mut fake_language_servers = language
.set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
capabilities: lsp::ServerCapabilities {
document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
first_trigger_character: ":".to_string(),
more_trigger_character: Some(vec![">".to_string()]),
}),
..Default::default()
},
..Default::default()
}))
.await;
client_a.language_registry.add(Arc::new(language));
client_a
.fs
.insert_tree(
"/a",
json!({
"main.rs": "fn main() { a }",
"other.rs": "// Test file",
}),
)
.await;
let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
let project_id = active_call_a
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await
.unwrap();
let project_b = client_b.build_remote_project(project_id, cx_b).await;
// Open a file in an editor as the guest.
let buffer_b = project_b
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
.await
.unwrap();
let (window_b, _) = cx_b.add_window(|_| EmptyView);
let editor_b = cx_b.add_view(window_b, |cx| {
Editor::for_buffer(buffer_b, Some(project_b.clone()), cx)
});
let fake_language_server = fake_language_servers.next().await.unwrap();
cx_a.foreground().run_until_parked();
// Type a on type formatting trigger character as the guest.
editor_b.update(cx_b, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
editor.handle_input(":", cx);
cx.focus(&editor_b);
});
// Receive an OnTypeFormatting request as the host's language server.
// Return some formattings from the host's language server.
cx_a.foreground().start_waiting();
fake_language_server
.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
assert_eq!(
params.text_document_position.text_document.uri,
lsp::Url::from_file_path("/a/main.rs").unwrap(),
);
assert_eq!(
params.text_document_position.position,
lsp::Position::new(0, 14),
);
Ok(Some(vec![lsp::TextEdit {
new_text: "~:".to_string(),
range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
}]))
})
.next()
.await
.unwrap();
cx_a.foreground().finish_waiting();
// Open the buffer on the host and see that the formattings worked
let buffer_a = project_a
.update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
.await
.unwrap();
cx_a.foreground().run_until_parked();
buffer_a.read_with(cx_a, |buffer, _| {
assert_eq!(buffer.text(), "fn main() { a:~: }")
});
// Undo should remove LSP edits first
editor_b.update(cx_b, |editor, cx| {
assert_eq!(editor.text(cx), "fn main() { a:~: }");
editor.undo(&Undo, cx);
assert_eq!(editor.text(cx), "fn main() { a: }");
});
cx_a.foreground().run_until_parked();
buffer_a.read_with(cx_a, |buffer, _| {
assert_eq!(buffer.text(), "fn main() { a: }")
});
editor_b.update(cx_b, |editor, cx| {
assert_eq!(editor.text(cx), "fn main() { a: }");
editor.undo(&Undo, cx);
assert_eq!(editor.text(cx), "fn main() { a }");
});
cx_a.foreground().run_until_parked();
buffer_a.read_with(cx_a, |buffer, _| {
assert_eq!(buffer.text(), "fn main() { a }")
});
}
#[derive(Debug, Eq, PartialEq)]
struct RoomParticipants {
remote: Vec<String>,

View File

@@ -21,7 +21,7 @@ use rand::{
prelude::*,
};
use serde::{Deserialize, Serialize};
use settings::Settings;
use settings::SettingsStore;
use std::{
env,
ops::Range,
@@ -149,8 +149,9 @@ async fn test_random_collaboration(
for (client, mut cx) in clients {
cx.update(|cx| {
let store = cx.remove_global::<SettingsStore>();
cx.clear_globals();
cx.set_global(Settings::test(cx));
cx.set_global(store);
drop(client);
});
}

View File

@@ -18,7 +18,6 @@ use gpui::{
ViewContext, ViewHandle, WeakViewHandle,
};
use project::Project;
use settings::Settings;
use std::{ops::Range, sync::Arc};
use theme::{AvatarStyle, Theme};
use util::ResultExt;
@@ -70,7 +69,7 @@ impl View for CollabTitlebarItem {
};
let project = self.project.read(cx);
let theme = cx.global::<Settings>().theme.clone();
let theme = theme::current(cx).clone();
let mut left_container = Flex::row();
let mut right_container = Flex::row().align_children_center();
@@ -298,7 +297,7 @@ impl CollabTitlebarItem {
}
pub fn toggle_user_menu(&mut self, _: &ToggleUserMenu, cx: &mut ViewContext<Self>) {
let theme = cx.global::<Settings>().theme.clone();
let theme = theme::current(cx).clone();
let avatar_style = theme.workspace.titlebar.leader_avatar.clone();
let item_style = theme.context_menu.item.disabled_style().clone();
self.user_menu.update(cx, |user_menu, cx| {
@@ -866,7 +865,7 @@ impl CollabTitlebarItem {
) -> Option<AnyElement<Self>> {
enum ConnectionStatusButton {}
let theme = &cx.global::<Settings>().theme.clone();
let theme = &theme::current(cx).clone();
match status {
client::Status::ConnectionError
| client::Status::ConnectionLost

View File

@@ -1,7 +1,6 @@
use client::{ContactRequestStatus, User, UserStore};
use gpui::{elements::*, AppContext, ModelHandle, MouseState, Task, ViewContext};
use picker::{Picker, PickerDelegate, PickerEvent};
use settings::Settings;
use std::sync::Arc;
use util::TryFutureExt;
@@ -98,7 +97,7 @@ impl PickerDelegate for ContactFinderDelegate {
selected: bool,
cx: &gpui::AppContext,
) -> AnyElement<Picker<Self>> {
let theme = &cx.global::<Settings>().theme;
let theme = &theme::current(cx);
let user = &self.potential_contacts[ix];
let request_status = self.user_store.read(cx).contact_request_status(user);

View File

@@ -14,7 +14,6 @@ use gpui::{
use menu::{Confirm, SelectNext, SelectPrev};
use project::Project;
use serde::Deserialize;
use settings::Settings;
use std::{mem, sync::Arc};
use theme::IconButton;
use workspace::Workspace;
@@ -192,7 +191,7 @@ impl ContactList {
.detach();
let list_state = ListState::<Self>::new(0, Orientation::Top, 1000., move |this, ix, cx| {
let theme = cx.global::<Settings>().theme.clone();
let theme = theme::current(cx).clone();
let is_selected = this.selection == Some(ix);
let current_project_id = this.project.read(cx).remote_id();
@@ -1313,7 +1312,7 @@ impl View for ContactList {
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
enum AddContact {}
let theme = cx.global::<Settings>().theme.clone();
let theme = theme::current(cx).clone();
Flex::column()
.with_child(

View File

@@ -9,7 +9,6 @@ use gpui::{
};
use picker::PickerEvent;
use project::Project;
use settings::Settings;
use workspace::Workspace;
actions!(contacts_popover, [ToggleContactFinder]);
@@ -108,7 +107,7 @@ impl View for ContactsPopover {
}
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
let theme = cx.global::<Settings>().theme.clone();
let theme = theme::current(cx).clone();
let child = match &self.child {
Child::ContactList(child) => ChildView::new(child, cx),
Child::ContactFinder(child) => ChildView::new(child, cx),

View File

@@ -9,7 +9,6 @@ use gpui::{
platform::{CursorStyle, MouseButton, WindowBounds, WindowKind, WindowOptions},
AnyElement, AppContext, Entity, View, ViewContext,
};
use settings::Settings;
use util::ResultExt;
use workspace::AppState;
@@ -26,7 +25,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
if let Some(incoming_call) = incoming_call {
const PADDING: f32 = 16.;
let window_size = cx.read(|cx| {
let theme = &cx.global::<Settings>().theme.incoming_call_notification;
let theme = &theme::current(cx).incoming_call_notification;
vec2f(theme.window_width, theme.window_height)
});
@@ -42,6 +41,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
titlebar: None,
center: false,
focus: false,
show: true,
kind: WindowKind::PopUp,
is_movable: false,
screen: Some(screen),
@@ -106,7 +106,7 @@ impl IncomingCallNotification {
}
fn render_caller(&self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
let theme = &cx.global::<Settings>().theme.incoming_call_notification;
let theme = &theme::current(cx).incoming_call_notification;
let default_project = proto::ParticipantProject::default();
let initial_project = self
.call
@@ -170,10 +170,11 @@ impl IncomingCallNotification {
enum Accept {}
enum Decline {}
let theme = theme::current(cx);
Flex::column()
.with_child(
MouseEventHandler::<Accept, Self>::new(0, cx, |_, cx| {
let theme = &cx.global::<Settings>().theme.incoming_call_notification;
MouseEventHandler::<Accept, Self>::new(0, cx, |_, _| {
let theme = &theme.incoming_call_notification;
Label::new("Accept", theme.accept_button.text.clone())
.aligned()
.contained()
@@ -186,8 +187,8 @@ impl IncomingCallNotification {
.flex(1., true),
)
.with_child(
MouseEventHandler::<Decline, Self>::new(0, cx, |_, cx| {
let theme = &cx.global::<Settings>().theme.incoming_call_notification;
MouseEventHandler::<Decline, Self>::new(0, cx, |_, _| {
let theme = &theme.incoming_call_notification;
Label::new("Decline", theme.decline_button.text.clone())
.aligned()
.contained()
@@ -200,12 +201,7 @@ impl IncomingCallNotification {
.flex(1., true),
)
.constrained()
.with_width(
cx.global::<Settings>()
.theme
.incoming_call_notification
.button_width,
)
.with_width(theme.incoming_call_notification.button_width)
.into_any()
}
}
@@ -220,12 +216,7 @@ impl View for IncomingCallNotification {
}
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
let background = cx
.global::<Settings>()
.theme
.incoming_call_notification
.background;
let background = theme::current(cx).incoming_call_notification.background;
Flex::row()
.with_child(self.render_caller(cx))
.with_child(self.render_buttons(cx))

View File

@@ -4,7 +4,6 @@ use gpui::{
platform::{CursorStyle, MouseButton},
AnyElement, Element, View, ViewContext,
};
use settings::Settings;
use std::sync::Arc;
enum Dismiss {}
@@ -22,7 +21,7 @@ where
F: 'static + Fn(&mut V, &mut ViewContext<V>),
V: View,
{
let theme = cx.global::<Settings>().theme.clone();
let theme = theme::current(cx).clone();
let theme = &theme.contact_notification;
Flex::column()

View File

@@ -7,7 +7,6 @@ use gpui::{
platform::{CursorStyle, MouseButton, WindowBounds, WindowKind, WindowOptions},
AppContext, Entity, View, ViewContext,
};
use settings::Settings;
use std::sync::{Arc, Weak};
use workspace::AppState;
@@ -22,7 +21,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
worktree_root_names,
} => {
const PADDING: f32 = 16.;
let theme = &cx.global::<Settings>().theme.project_shared_notification;
let theme = &theme::current(cx).project_shared_notification;
let window_size = vec2f(theme.window_width, theme.window_height);
for screen in cx.platform().screens() {
@@ -36,6 +35,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
titlebar: None,
center: false,
focus: false,
show: true,
kind: WindowKind::PopUp,
is_movable: false,
screen: Some(screen),
@@ -109,7 +109,7 @@ impl ProjectSharedNotification {
}
fn render_owner(&self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
let theme = &cx.global::<Settings>().theme.project_shared_notification;
let theme = &theme::current(cx).project_shared_notification;
Flex::row()
.with_children(self.owner.avatar.clone().map(|avatar| {
Image::from_data(avatar)
@@ -167,10 +167,11 @@ impl ProjectSharedNotification {
enum Open {}
enum Dismiss {}
let theme = theme::current(cx);
Flex::column()
.with_child(
MouseEventHandler::<Open, Self>::new(0, cx, |_, cx| {
let theme = &cx.global::<Settings>().theme.project_shared_notification;
MouseEventHandler::<Open, Self>::new(0, cx, |_, _| {
let theme = &theme.project_shared_notification;
Label::new("Open", theme.open_button.text.clone())
.aligned()
.contained()
@@ -181,8 +182,8 @@ impl ProjectSharedNotification {
.flex(1., true),
)
.with_child(
MouseEventHandler::<Dismiss, Self>::new(0, cx, |_, cx| {
let theme = &cx.global::<Settings>().theme.project_shared_notification;
MouseEventHandler::<Dismiss, Self>::new(0, cx, |_, _| {
let theme = &theme.project_shared_notification;
Label::new("Dismiss", theme.dismiss_button.text.clone())
.aligned()
.contained()
@@ -195,12 +196,7 @@ impl ProjectSharedNotification {
.flex(1., true),
)
.constrained()
.with_width(
cx.global::<Settings>()
.theme
.project_shared_notification
.button_width,
)
.with_width(theme.project_shared_notification.button_width)
.into_any()
}
}
@@ -215,11 +211,7 @@ impl View for ProjectSharedNotification {
}
fn render(&mut self, cx: &mut ViewContext<Self>) -> gpui::AnyElement<Self> {
let background = cx
.global::<Settings>()
.theme
.project_shared_notification
.background;
let background = theme::current(cx).project_shared_notification.background;
Flex::row()
.with_child(self.render_owner(cx))
.with_child(self.render_buttons(cx))

View File

@@ -6,7 +6,7 @@ use gpui::{
platform::{Appearance, MouseButton},
AnyElement, AppContext, Element, Entity, View, ViewContext,
};
use settings::Settings;
use workspace::WorkspaceSettings;
pub fn init(cx: &mut AppContext) {
let active_call = ActiveCall::global(cx);
@@ -15,7 +15,9 @@ pub fn init(cx: &mut AppContext) {
cx.observe(&active_call, move |call, cx| {
if let Some(room) = call.read(cx).room() {
if room.read(cx).is_screen_sharing() {
if status_indicator.is_none() && cx.global::<Settings>().show_call_status_icon {
if status_indicator.is_none()
&& settings::get::<WorkspaceSettings>(cx).show_call_status_icon
{
status_indicator = Some(cx.add_status_bar_item(|_| SharingStatusIndicator));
}
} else if let Some((window_id, _)) = status_indicator.take() {

View File

@@ -23,6 +23,7 @@ workspace = { path = "../workspace" }
[dev-dependencies]
gpui = { path = "../gpui", features = ["test-support"] }
editor = { path = "../editor", features = ["test-support"] }
language = { path = "../language", features = ["test-support"] }
project = { path = "../project", features = ["test-support"] }
serde_json.workspace = true
workspace = { path = "../workspace", features = ["test-support"] }

View File

@@ -5,7 +5,6 @@ use gpui::{
ViewContext,
};
use picker::{Picker, PickerDelegate, PickerEvent};
use settings::Settings;
use std::cmp;
use util::ResultExt;
use workspace::Workspace;
@@ -185,8 +184,7 @@ impl PickerDelegate for CommandPaletteDelegate {
) -> AnyElement<Picker<Self>> {
let mat = &self.matches[ix];
let command = &self.actions[mat.candidate_id];
let settings = cx.global::<Settings>();
let theme = &settings.theme;
let theme = theme::current(cx);
let style = theme.picker.item.style_for(mouse_state, selected);
let key_style = &theme.command_palette.key.style_for(mouse_state, selected);
let keystroke_spacing = theme.command_palette.keystroke_spacing;
@@ -294,14 +292,7 @@ mod tests {
#[gpui::test]
async fn test_command_palette(deterministic: Arc<Deterministic>, cx: &mut TestAppContext) {
deterministic.forbid_parking();
let app_state = cx.update(AppState::test);
cx.update(|cx| {
editor::init(cx);
workspace::init(app_state.clone(), cx);
init(cx);
});
let app_state = init_test(cx);
let project = Project::test(app_state.fs.clone(), [], cx).await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
@@ -369,4 +360,16 @@ mod tests {
assert!(palette.delegate().matches.is_empty())
});
}
fn init_test(cx: &mut TestAppContext) -> Arc<AppState> {
cx.update(|cx| {
let app_state = AppState::test(cx);
theme::init((), cx);
language::init(cx);
editor::init(cx);
workspace::init(app_state.clone(), cx);
init(cx);
app_state
})
}
}

View File

@@ -8,7 +8,6 @@ use gpui::{
View, ViewContext,
};
use menu::*;
use settings::Settings;
use std::{any::TypeId, borrow::Cow, sync::Arc, time::Duration};
pub fn init(cx: &mut AppContext) {
@@ -323,7 +322,7 @@ impl ContextMenu {
}
fn render_menu_for_measurement(&self, cx: &mut ViewContext<Self>) -> impl Element<ContextMenu> {
let style = cx.global::<Settings>().theme.context_menu.clone();
let style = theme::current(cx).context_menu.clone();
Flex::row()
.with_child(
Flex::column().with_children(self.items.iter().enumerate().map(|(ix, item)| {
@@ -403,7 +402,7 @@ impl ContextMenu {
enum Menu {}
enum MenuItem {}
let style = cx.global::<Settings>().theme.context_menu.clone();
let style = theme::current(cx).context_menu.clone();
MouseEventHandler::<Menu, ContextMenu>::new(0, cx, |_, cx| {
Flex::column()

View File

@@ -10,6 +10,7 @@ use gpui::{
actions, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, Task, WeakModelHandle,
};
use language::{
language_settings::{all_language_settings, language_settings},
point_from_lsp, point_to_lsp, Anchor, Bias, Buffer, BufferSnapshot, Language, PointUtf16,
ToPointUtf16,
};
@@ -17,7 +18,7 @@ use log::{debug, error};
use lsp::{LanguageServer, LanguageServerId};
use node_runtime::NodeRuntime;
use request::{LogMessage, StatusNotification};
use settings::Settings;
use settings::SettingsStore;
use smol::{fs, io::BufReader, stream::StreamExt};
use std::{
ffi::OsString,
@@ -302,56 +303,34 @@ impl Copilot {
node_runtime: Arc<NodeRuntime>,
cx: &mut ModelContext<Self>,
) -> Self {
cx.observe_global::<Settings, _>({
let http = http.clone();
let node_runtime = node_runtime.clone();
move |this, cx| {
if cx.global::<Settings>().features.copilot {
if matches!(this.server, CopilotServer::Disabled) {
let start_task = cx
.spawn({
let http = http.clone();
let node_runtime = node_runtime.clone();
move |this, cx| {
Self::start_language_server(http, node_runtime, this, cx)
}
})
.shared();
this.server = CopilotServer::Starting { task: start_task };
cx.notify();
}
} else {
this.server = CopilotServer::Disabled;
cx.notify();
}
}
})
.detach();
let mut this = Self {
http,
node_runtime,
server: CopilotServer::Disabled,
buffers: Default::default(),
};
this.enable_or_disable_copilot(cx);
cx.observe_global::<SettingsStore, _>(move |this, cx| this.enable_or_disable_copilot(cx))
.detach();
this
}
if cx.global::<Settings>().features.copilot {
let start_task = cx
.spawn({
let http = http.clone();
let node_runtime = node_runtime.clone();
move |this, cx| async {
Self::start_language_server(http, node_runtime, this, cx).await
}
})
.shared();
Self {
http,
node_runtime,
server: CopilotServer::Starting { task: start_task },
buffers: Default::default(),
fn enable_or_disable_copilot(&mut self, cx: &mut ModelContext<Copilot>) {
let http = self.http.clone();
let node_runtime = self.node_runtime.clone();
if all_language_settings(cx).copilot_enabled(None, None) {
if matches!(self.server, CopilotServer::Disabled) {
let start_task = cx
.spawn({
move |this, cx| Self::start_language_server(http, node_runtime, this, cx)
})
.shared();
self.server = CopilotServer::Starting { task: start_task };
cx.notify();
}
} else {
Self {
http,
node_runtime,
server: CopilotServer::Disabled,
buffers: Default::default(),
}
self.server = CopilotServer::Disabled;
cx.notify();
}
}
@@ -805,13 +784,13 @@ impl Copilot {
let snapshot = registered_buffer.report_changes(buffer, cx);
let buffer = buffer.read(cx);
let uri = registered_buffer.uri.clone();
let settings = cx.global::<Settings>();
let position = position.to_point_utf16(buffer);
let language = buffer.language_at(position);
let language_name = language.map(|language| language.name());
let language_name = language_name.as_deref();
let tab_size = settings.tab_size(language_name);
let hard_tabs = settings.hard_tabs(language_name);
let settings = language_settings(
buffer.language_at(position).map(|l| l.name()).as_deref(),
cx,
);
let tab_size = settings.tab_size;
let hard_tabs = settings.hard_tabs;
let relative_path = buffer
.file()
.map(|file| file.path().to_path_buf())

View File

@@ -6,7 +6,6 @@ use gpui::{
AnyElement, AnyViewHandle, AppContext, ClipboardItem, Element, Entity, View, ViewContext,
ViewHandle,
};
use settings::Settings;
use theme::ui::modal;
#[derive(PartialEq, Eq, Debug, Clone)]
@@ -68,12 +67,13 @@ fn create_copilot_auth_window(
cx: &mut AppContext,
status: &Status,
) -> ViewHandle<CopilotCodeVerification> {
let window_size = cx.global::<Settings>().theme.copilot.modal.dimensions();
let window_size = theme::current(cx).copilot.modal.dimensions();
let window_options = WindowOptions {
bounds: WindowBounds::Fixed(RectF::new(Default::default(), window_size)),
titlebar: None,
center: true,
focus: true,
show: true,
kind: WindowKind::Normal,
is_movable: true,
screen: None,
@@ -338,7 +338,7 @@ impl View for CopilotCodeVerification {
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
enum ConnectModal {}
let style = cx.global::<Settings>().theme.clone();
let style = theme::current(cx).clone();
modal::<ConnectModal, _, _, _, _>(
"Connect Copilot to Zed",

View File

@@ -12,8 +12,10 @@ doctest = false
assets = { path = "../assets" }
copilot = { path = "../copilot" }
editor = { path = "../editor" }
fs = { path = "../fs" }
context_menu = { path = "../context_menu" }
gpui = { path = "../gpui" }
language = { path = "../language" }
settings = { path = "../settings" }
theme = { path = "../theme" }
util = { path = "../util" }
@@ -21,3 +23,6 @@ workspace = { path = "../workspace" }
anyhow.workspace = true
smol.workspace = true
futures.workspace = true
[dev-dependencies]
editor = { path = "../editor", features = ["test-support"] }

View File

@@ -2,13 +2,15 @@ use anyhow::Result;
use context_menu::{ContextMenu, ContextMenuItem};
use copilot::{Copilot, SignOut, Status};
use editor::{scroll::autoscroll::Autoscroll, Editor};
use fs::Fs;
use gpui::{
elements::*,
platform::{CursorStyle, MouseButton},
AnyElement, AppContext, AsyncAppContext, Element, Entity, MouseState, Subscription, View,
ViewContext, ViewHandle, WeakViewHandle, WindowContext,
};
use settings::{settings_file::SettingsFile, Settings};
use language::language_settings::{self, all_language_settings, AllLanguageSettings};
use settings::{update_settings_file, SettingsStore};
use std::{path::Path, sync::Arc};
use util::{paths, ResultExt};
use workspace::{
@@ -26,6 +28,7 @@ pub struct CopilotButton {
editor_enabled: Option<bool>,
language: Option<Arc<str>>,
path: Option<Arc<Path>>,
fs: Arc<dyn Fs>,
}
impl Entity for CopilotButton {
@@ -38,13 +41,12 @@ impl View for CopilotButton {
}
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
let settings = cx.global::<Settings>();
if !settings.features.copilot {
let all_language_settings = &all_language_settings(cx);
if !all_language_settings.copilot.feature_enabled {
return Empty::new().into_any();
}
let theme = settings.theme.clone();
let theme = theme::current(cx).clone();
let active = self.popup_menu.read(cx).visible();
let Some(copilot) = Copilot::global(cx) else {
return Empty::new().into_any();
@@ -53,7 +55,7 @@ impl View for CopilotButton {
let enabled = self
.editor_enabled
.unwrap_or(settings.show_copilot_suggestions(None, None));
.unwrap_or_else(|| all_language_settings.copilot_enabled(None, None));
Stack::new()
.with_child(
@@ -64,8 +66,8 @@ impl View for CopilotButton {
let style = theme
.workspace
.status_bar
.sidebar_buttons
.item
.panel_buttons
.button
.style_for(state, active);
Flex::row()
@@ -143,7 +145,7 @@ impl View for CopilotButton {
}
impl CopilotButton {
pub fn new(cx: &mut ViewContext<Self>) -> Self {
pub fn new(fs: Arc<dyn Fs>, cx: &mut ViewContext<Self>) -> Self {
let button_view_id = cx.view_id();
let menu = cx.add_view(|cx| {
let mut menu = ContextMenu::new(button_view_id, cx);
@@ -155,7 +157,7 @@ impl CopilotButton {
Copilot::global(cx).map(|copilot| cx.observe(&copilot, |_, _, cx| cx.notify()).detach());
cx.observe_global::<Settings, _>(move |_, cx| cx.notify())
cx.observe_global::<SettingsStore, _>(move |_, cx| cx.notify())
.detach();
Self {
@@ -164,17 +166,19 @@ impl CopilotButton {
editor_enabled: None,
language: None,
path: None,
fs,
}
}
pub fn deploy_copilot_start_menu(&mut self, cx: &mut ViewContext<Self>) {
let mut menu_options = Vec::with_capacity(2);
let fs = self.fs.clone();
menu_options.push(ContextMenuItem::handler("Sign In", |cx| {
initiate_sign_in(cx)
}));
menu_options.push(ContextMenuItem::handler("Disable Copilot", |cx| {
hide_copilot(cx)
menu_options.push(ContextMenuItem::handler("Disable Copilot", move |cx| {
hide_copilot(fs.clone(), cx)
}));
self.popup_menu.update(cx, |menu, cx| {
@@ -188,22 +192,26 @@ impl CopilotButton {
}
pub fn deploy_copilot_menu(&mut self, cx: &mut ViewContext<Self>) {
let settings = cx.global::<Settings>();
let fs = self.fs.clone();
let mut menu_options = Vec::with_capacity(8);
if let Some(language) = self.language.clone() {
let language_enabled = settings.copilot_enabled_for_language(Some(language.as_ref()));
let fs = fs.clone();
let language_enabled =
language_settings::language_settings(Some(language.as_ref()), cx)
.show_copilot_suggestions;
menu_options.push(ContextMenuItem::handler(
format!(
"{} Suggestions for {}",
if language_enabled { "Hide" } else { "Show" },
language
),
move |cx| toggle_copilot_for_language(language.clone(), cx),
move |cx| toggle_copilot_for_language(language.clone(), fs.clone(), cx),
));
}
let settings = settings::get::<AllLanguageSettings>(cx);
if let Some(path) = self.path.as_ref() {
let path_enabled = settings.copilot_enabled_for_path(path);
let path = path.clone();
@@ -228,19 +236,19 @@ impl CopilotButton {
));
}
let globally_enabled = cx.global::<Settings>().features.copilot;
let globally_enabled = settings.copilot_enabled(None, None);
menu_options.push(ContextMenuItem::handler(
if globally_enabled {
"Hide Suggestions for All Files"
} else {
"Show Suggestions for All Files"
},
|cx| toggle_copilot_globally(cx),
move |cx| toggle_copilot_globally(fs.clone(), cx),
));
menu_options.push(ContextMenuItem::Separator);
let icon_style = settings.theme.copilot.out_link_icon.clone();
let icon_style = theme::current(cx).copilot.out_link_icon.clone();
menu_options.push(ContextMenuItem::action(
move |state: &mut MouseState, style: &theme::ContextMenuItem| {
Flex::row()
@@ -266,22 +274,19 @@ impl CopilotButton {
pub fn update_enabled(&mut self, editor: ViewHandle<Editor>, cx: &mut ViewContext<Self>) {
let editor = editor.read(cx);
let snapshot = editor.buffer().read(cx).snapshot(cx);
let settings = cx.global::<Settings>();
let suggestion_anchor = editor.selections.newest_anchor().start;
let language_name = snapshot
.language_at(suggestion_anchor)
.map(|language| language.name());
let path = snapshot
.file_at(suggestion_anchor)
.map(|file| file.path().clone());
let path = snapshot.file_at(suggestion_anchor).map(|file| file.path());
self.editor_enabled =
Some(settings.show_copilot_suggestions(language_name.as_deref(), path.as_deref()));
self.editor_enabled = Some(
all_language_settings(cx)
.copilot_enabled(language_name.as_deref(), path.map(|p| p.as_ref())),
);
self.language = language_name;
self.path = path;
self.path = path.cloned();
cx.notify()
}
@@ -310,7 +315,7 @@ async fn configure_disabled_globs(
let settings_editor = workspace
.update(&mut cx, |_, cx| {
create_and_open_local_file(&paths::SETTINGS, cx, || {
Settings::initial_user_settings_content(&assets::Assets)
settings::initial_user_settings_content(&assets::Assets)
.as_ref()
.into()
})
@@ -322,14 +327,16 @@ async fn configure_disabled_globs(
settings_editor.downgrade().update(&mut cx, |item, cx| {
let text = item.buffer().read(cx).snapshot(cx).text();
let edits = SettingsFile::update_unsaved(&text, cx, |file| {
let settings = cx.global::<SettingsStore>();
let edits = settings.edits_for_update::<AllLanguageSettings>(&text, |file| {
let copilot = file.copilot.get_or_insert_with(Default::default);
let globs = copilot.disabled_globs.get_or_insert_with(|| {
cx.global::<Settings>()
settings
.get::<AllLanguageSettings>(None)
.copilot
.disabled_globs
.iter()
.map(|glob| glob.as_str().to_string())
.map(|glob| glob.glob().to_string())
.collect()
});
@@ -355,32 +362,26 @@ async fn configure_disabled_globs(
anyhow::Ok(())
}
fn toggle_copilot_globally(cx: &mut AppContext) {
let show_copilot_suggestions = cx.global::<Settings>().show_copilot_suggestions(None, None);
SettingsFile::update(cx, move |file_contents| {
file_contents.editor.show_copilot_suggestions = Some((!show_copilot_suggestions).into())
fn toggle_copilot_globally(fs: Arc<dyn Fs>, cx: &mut AppContext) {
let show_copilot_suggestions = all_language_settings(cx).copilot_enabled(None, None);
update_settings_file::<AllLanguageSettings>(fs, cx, move |file| {
file.defaults.show_copilot_suggestions = Some((!show_copilot_suggestions).into())
});
}
fn toggle_copilot_for_language(language: Arc<str>, cx: &mut AppContext) {
let show_copilot_suggestions = cx
.global::<Settings>()
.show_copilot_suggestions(Some(&language), None);
SettingsFile::update(cx, move |file_contents| {
file_contents.languages.insert(
language,
settings::EditorSettings {
show_copilot_suggestions: Some((!show_copilot_suggestions).into()),
..Default::default()
},
);
fn toggle_copilot_for_language(language: Arc<str>, fs: Arc<dyn Fs>, cx: &mut AppContext) {
let show_copilot_suggestions = all_language_settings(cx).copilot_enabled(Some(&language), None);
update_settings_file::<AllLanguageSettings>(fs, cx, move |file| {
file.languages
.entry(language)
.or_default()
.show_copilot_suggestions = Some(!show_copilot_suggestions);
});
}
fn hide_copilot(cx: &mut AppContext) {
SettingsFile::update(cx, move |file_contents| {
file_contents.features.copilot = Some(false)
fn hide_copilot(fs: Arc<dyn Fs>, cx: &mut AppContext) {
update_settings_file::<AllLanguageSettings>(fs, cx, move |file| {
file.features.get_or_insert(Default::default()).copilot = Some(false);
});
}

View File

@@ -18,7 +18,7 @@ sqlez = { path = "../sqlez" }
sqlez_macros = { path = "../sqlez_macros" }
util = { path = "../util" }
anyhow.workspace = true
indoc = "1.0.4"
indoc.workspace = true
async-trait.workspace = true
lazy_static.workspace = true
log.workspace = true

View File

@@ -31,6 +31,7 @@ language = { path = "../language", features = ["test-support"] }
lsp = { path = "../lsp", features = ["test-support"] }
gpui = { path = "../gpui", features = ["test-support"] }
workspace = { path = "../workspace", features = ["test-support"] }
theme = { path = "../theme", features = ["test-support"] }
serde_json.workspace = true
unindent.workspace = true

View File

@@ -20,7 +20,6 @@ use language::{
use lsp::LanguageServerId;
use project::{DiagnosticSummary, Project, ProjectPath};
use serde_json::json;
use settings::Settings;
use smallvec::SmallVec;
use std::{
any::{Any, TypeId},
@@ -30,10 +29,11 @@ use std::{
path::PathBuf,
sync::Arc,
};
use theme::ThemeSettings;
use util::TryFutureExt;
use workspace::{
item::{BreadcrumbText, Item, ItemEvent, ItemHandle},
ItemNavHistory, Pane, ToolbarItemLocation, Workspace,
ItemNavHistory, Pane, PaneBackdrop, ToolbarItemLocation, Workspace,
};
actions!(diagnostics, [Deploy]);
@@ -89,12 +89,16 @@ impl View for ProjectDiagnosticsEditor {
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
if self.path_states.is_empty() {
let theme = &cx.global::<Settings>().theme.project_diagnostics;
Label::new("No problems in workspace", theme.empty_message.clone())
.aligned()
.contained()
.with_style(theme.container)
.into_any()
let theme = &theme::current(cx).project_diagnostics;
PaneBackdrop::new(
cx.view_id(),
Label::new("No problems in workspace", theme.empty_message.clone())
.aligned()
.contained()
.with_style(theme.container)
.into_any(),
)
.into_any()
} else {
ChildView::new(&self.editor, cx).into_any()
}
@@ -161,8 +165,13 @@ impl ProjectDiagnosticsEditor {
editor.set_vertical_scroll_margin(5, cx);
editor
});
cx.subscribe(&editor, |_, _, event, cx| cx.emit(event.clone()))
.detach();
cx.subscribe(&editor, |this, _, event, cx| {
cx.emit(event.clone());
if event == &editor::Event::Focused && this.path_states.is_empty() {
cx.focus_self()
}
})
.detach();
let project = project_handle.read(cx);
let paths_to_update = project
@@ -537,7 +546,7 @@ impl Item for ProjectDiagnosticsEditor {
render_summary(
&self.summary,
&style.label.text,
&cx.global::<Settings>().theme.project_diagnostics,
&theme::current(cx).project_diagnostics,
)
}
@@ -600,15 +609,6 @@ impl Item for ProjectDiagnosticsEditor {
unreachable!()
}
fn git_diff_recalc(
&mut self,
project: ModelHandle<Project>,
cx: &mut ViewContext<Self>,
) -> Task<Result<()>> {
self.editor
.update(cx, |editor, cx| editor.git_diff_recalc(project, cx))
}
fn to_item_events(event: &Self::Event) -> SmallVec<[ItemEvent; 2]> {
Editor::to_item_events(event)
}
@@ -679,10 +679,10 @@ impl Item for ProjectDiagnosticsEditor {
fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock {
let (message, highlights) = highlight_diagnostic_message(Vec::new(), &diagnostic.message);
Arc::new(move |cx| {
let settings = cx.global::<Settings>();
let settings = settings::get::<ThemeSettings>(cx);
let theme = &settings.theme.editor;
let style = theme.diagnostic_header.clone();
let font_size = (style.text_scale_factor * settings.buffer_font_size).round();
let font_size = (style.text_scale_factor * settings.buffer_font_size(cx)).round();
let icon_width = cx.em_width * style.icon_width_factor;
let icon = if diagnostic.severity == DiagnosticSeverity::ERROR {
Svg::new("icons/circle_x_mark_12.svg")
@@ -818,33 +818,35 @@ mod tests {
use language::{Diagnostic, DiagnosticEntry, DiagnosticSeverity, PointUtf16, Unclipped};
use project::FakeFs;
use serde_json::json;
use settings::SettingsStore;
use unindent::Unindent as _;
#[gpui::test]
async fn test_diagnostics(cx: &mut TestAppContext) {
Settings::test_async(cx);
init_test(cx);
let fs = FakeFs::new(cx.background());
fs.insert_tree(
"/test",
json!({
"consts.rs": "
const a: i32 = 'a';
const b: i32 = c;
"
const a: i32 = 'a';
const b: i32 = c;
"
.unindent(),
"main.rs": "
fn main() {
let x = vec![];
let y = vec![];
a(x);
b(y);
// comment 1
// comment 2
c(y);
d(x);
}
"
fn main() {
let x = vec![];
let y = vec![];
a(x);
b(y);
// comment 1
// comment 2
c(y);
d(x);
}
"
.unindent(),
}),
)
@@ -1225,7 +1227,8 @@ mod tests {
#[gpui::test]
async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
Settings::test_async(cx);
init_test(cx);
let fs = FakeFs::new(cx.background());
fs.insert_tree(
"/test",
@@ -1489,6 +1492,17 @@ mod tests {
});
}
fn init_test(cx: &mut TestAppContext) {
cx.update(|cx| {
cx.set_global(SettingsStore::test(cx));
theme::init((), cx);
language::init(cx);
client::init_settings(cx);
workspace::init_settings(cx);
Project::init_settings(cx);
});
}
fn editor_blocks(editor: &ViewHandle<Editor>, cx: &mut WindowContext) -> Vec<(u32, String)> {
editor.update(cx, |editor, cx| {
let snapshot = editor.snapshot(cx);

View File

@@ -7,7 +7,6 @@ use gpui::{
};
use language::Diagnostic;
use lsp::LanguageServerId;
use settings::Settings;
use workspace::{item::ItemHandle, StatusItemView, Workspace};
use crate::ProjectDiagnosticsEditor;
@@ -92,13 +91,12 @@ impl View for DiagnosticIndicator {
enum Summary {}
enum Message {}
let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
let tooltip_style = theme::current(cx).tooltip.clone();
let in_progress = !self.in_progress_checks.is_empty();
let mut element = Flex::row().with_child(
MouseEventHandler::<Summary, _>::new(0, cx, |state, cx| {
let style = cx
.global::<Settings>()
.theme
let theme = theme::current(cx);
let style = theme
.workspace
.status_bar
.diagnostic_summary
@@ -184,7 +182,7 @@ impl View for DiagnosticIndicator {
.into_any(),
);
let style = &cx.global::<Settings>().theme.workspace.status_bar;
let style = &theme::current(cx).workspace.status_bar;
let item_spacing = style.item_spacing;
if in_progress {

View File

@@ -58,6 +58,7 @@ parking_lot.workspace = true
postage.workspace = true
pulldown-cmark = { version = "0.9.2", default-features = false }
rand = { workspace = true, optional = true }
schemars.workspace = true
serde.workspace = true
serde_derive.workspace = true
smallvec.workspace = true
@@ -80,7 +81,6 @@ workspace = { path = "../workspace", features = ["test-support"] }
ctor.workspace = true
env_logger.workspace = true
glob.workspace = true
rand.workspace = true
unindent.workspace = true
tree-sitter = "0.20"

View File

@@ -1,8 +1,8 @@
use std::time::Duration;
use crate::EditorSettings;
use gpui::{Entity, ModelContext};
use settings::Settings;
use settings::SettingsStore;
use smol::Timer;
use std::time::Duration;
pub struct BlinkManager {
blink_interval: Duration,
@@ -15,8 +15,8 @@ pub struct BlinkManager {
impl BlinkManager {
pub fn new(blink_interval: Duration, cx: &mut ModelContext<Self>) -> Self {
cx.observe_global::<Settings, _>(move |this, cx| {
// Make sure we blink the cursors if the setting is re-enabled
// Make sure we blink the cursors if the setting is re-enabled
cx.observe_global::<SettingsStore, _>(move |this, cx| {
this.blink_cursors(this.blink_epoch, cx)
})
.detach();
@@ -64,7 +64,7 @@ impl BlinkManager {
}
fn blink_cursors(&mut self, epoch: usize, cx: &mut ModelContext<Self>) {
if cx.global::<Settings>().cursor_blink {
if settings::get::<EditorSettings>(cx).cursor_blink {
if epoch == self.blink_epoch && self.enabled && !self.blinking_paused {
self.visible = !self.visible;
cx.notify();

View File

@@ -13,8 +13,9 @@ use gpui::{
fonts::{FontId, HighlightStyle},
Entity, ModelContext, ModelHandle,
};
use language::{OffsetUtf16, Point, Subscription as BufferSubscription};
use settings::Settings;
use language::{
language_settings::language_settings, OffsetUtf16, Point, Subscription as BufferSubscription,
};
use std::{any::TypeId, fmt::Debug, num::NonZeroU32, ops::Range, sync::Arc};
pub use suggestion_map::Suggestion;
use suggestion_map::SuggestionMap;
@@ -276,8 +277,7 @@ impl DisplayMap {
.as_singleton()
.and_then(|buffer| buffer.read(cx).language())
.map(|language| language.name());
cx.global::<Settings>().tab_size(language_name.as_deref())
language_settings(language_name.as_deref(), cx).tab_size
}
#[cfg(test)]
@@ -844,8 +844,12 @@ pub mod tests {
use super::*;
use crate::{movement, test::marked_display_snapshot};
use gpui::{color::Color, elements::*, test::observe, AppContext};
use language::{Buffer, Language, LanguageConfig, SelectionGoal};
use language::{
language_settings::{AllLanguageSettings, AllLanguageSettingsContent},
Buffer, Language, LanguageConfig, SelectionGoal,
};
use rand::{prelude::*, Rng};
use settings::SettingsStore;
use smol::stream::StreamExt;
use std::{env, sync::Arc};
use theme::SyntaxTheme;
@@ -882,9 +886,7 @@ pub mod tests {
log::info!("wrap width: {:?}", wrap_width);
cx.update(|cx| {
let mut settings = Settings::test(cx);
settings.editor_overrides.tab_size = NonZeroU32::new(tab_size);
cx.set_global(settings)
init_test(cx, |s| s.defaults.tab_size = NonZeroU32::new(tab_size));
});
let buffer = cx.update(|cx| {
@@ -939,9 +941,11 @@ pub mod tests {
tab_size = *tab_sizes.choose(&mut rng).unwrap();
log::info!("setting tab size to {:?}", tab_size);
cx.update(|cx| {
let mut settings = Settings::test(cx);
settings.editor_overrides.tab_size = NonZeroU32::new(tab_size);
cx.set_global(settings)
cx.update_global::<SettingsStore, _, _>(|store, cx| {
store.update_user_settings::<AllLanguageSettings>(cx, |s| {
s.defaults.tab_size = NonZeroU32::new(tab_size);
});
});
});
}
30..=44 => {
@@ -1119,7 +1123,7 @@ pub mod tests {
#[gpui::test(retries = 5)]
fn test_soft_wraps(cx: &mut AppContext) {
cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
cx.foreground().forbid_parking();
init_test(cx, |_| {});
let font_cache = cx.font_cache();
@@ -1131,7 +1135,6 @@ pub mod tests {
.unwrap();
let font_size = 12.0;
let wrap_width = Some(64.);
cx.set_global(Settings::test(cx));
let text = "one two three four five\nsix seven eight";
let buffer = MultiBuffer::build_simple(text, cx);
@@ -1211,7 +1214,8 @@ pub mod tests {
#[gpui::test]
fn test_text_chunks(cx: &mut gpui::AppContext) {
cx.set_global(Settings::test(cx));
init_test(cx, |_| {});
let text = sample_text(6, 6, 'a');
let buffer = MultiBuffer::build_simple(&text, cx);
let family_id = cx
@@ -1225,6 +1229,7 @@ pub mod tests {
let font_size = 14.0;
let map =
cx.add_model(|cx| DisplayMap::new(buffer.clone(), font_id, font_size, None, 1, 1, cx));
buffer.update(cx, |buffer, cx| {
buffer.edit(
vec![
@@ -1289,11 +1294,8 @@ pub mod tests {
.unwrap(),
);
language.set_theme(&theme);
cx.update(|cx| {
let mut settings = Settings::test(cx);
settings.editor_defaults.tab_size = Some(2.try_into().unwrap());
cx.set_global(settings);
});
cx.update(|cx| init_test(cx, |s| s.defaults.tab_size = Some(2.try_into().unwrap())));
let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
buffer.condition(cx, |buf, _| !buf.is_parsing()).await;
@@ -1382,7 +1384,7 @@ pub mod tests {
);
language.set_theme(&theme);
cx.update(|cx| cx.set_global(Settings::test(cx)));
cx.update(|cx| init_test(cx, |_| {}));
let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
buffer.condition(cx, |buf, _| !buf.is_parsing()).await;
@@ -1429,9 +1431,8 @@ pub mod tests {
#[gpui::test]
async fn test_chunks_with_text_highlights(cx: &mut gpui::TestAppContext) {
cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
cx.update(|cx| init_test(cx, |_| {}));
cx.update(|cx| cx.set_global(Settings::test(cx)));
let theme = SyntaxTheme::new(vec![
("operator".to_string(), Color::red().into()),
("string".to_string(), Color::green().into()),
@@ -1510,7 +1511,8 @@ pub mod tests {
#[gpui::test]
fn test_clip_point(cx: &mut gpui::AppContext) {
cx.set_global(Settings::test(cx));
init_test(cx, |_| {});
fn assert(text: &str, shift_right: bool, bias: Bias, cx: &mut gpui::AppContext) {
let (unmarked_snapshot, mut markers) = marked_display_snapshot(text, cx);
@@ -1559,7 +1561,7 @@ pub mod tests {
#[gpui::test]
fn test_clip_at_line_ends(cx: &mut gpui::AppContext) {
cx.set_global(Settings::test(cx));
init_test(cx, |_| {});
fn assert(text: &str, cx: &mut gpui::AppContext) {
let (mut unmarked_snapshot, markers) = marked_display_snapshot(text, cx);
@@ -1578,7 +1580,8 @@ pub mod tests {
#[gpui::test]
fn test_tabs_with_multibyte_chars(cx: &mut gpui::AppContext) {
cx.set_global(Settings::test(cx));
init_test(cx, |_| {});
let text = "\t\tα\nβ\t\n🏀β\t\tγ";
let buffer = MultiBuffer::build_simple(text, cx);
let font_cache = cx.font_cache();
@@ -1639,7 +1642,8 @@ pub mod tests {
#[gpui::test]
fn test_max_point(cx: &mut gpui::AppContext) {
cx.set_global(Settings::test(cx));
init_test(cx, |_| {});
let buffer = MultiBuffer::build_simple("aaa\n\t\tbbb", cx);
let font_cache = cx.font_cache();
let family_id = font_cache
@@ -1718,4 +1722,13 @@ pub mod tests {
}
chunks
}
fn init_test(cx: &mut AppContext, f: impl Fn(&mut AllLanguageSettingsContent)) {
cx.foreground().forbid_parking();
cx.set_global(SettingsStore::test(cx));
language::init(cx);
cx.update_global::<SettingsStore, _, _>(|store, cx| {
store.update_user_settings::<AllLanguageSettings>(cx, f);
});
}
}

View File

@@ -993,7 +993,7 @@ mod tests {
use crate::multi_buffer::MultiBuffer;
use gpui::{elements::Empty, Element};
use rand::prelude::*;
use settings::Settings;
use settings::SettingsStore;
use std::env;
use util::RandomCharIter;
@@ -1013,7 +1013,7 @@ mod tests {
#[gpui::test]
fn test_basic_blocks(cx: &mut gpui::AppContext) {
cx.set_global(Settings::test(cx));
init_test(cx);
let family_id = cx
.font_cache()
@@ -1189,7 +1189,7 @@ mod tests {
#[gpui::test]
fn test_blocks_on_wrapped_lines(cx: &mut gpui::AppContext) {
cx.set_global(Settings::test(cx));
init_test(cx);
let family_id = cx
.font_cache()
@@ -1239,7 +1239,7 @@ mod tests {
#[gpui::test(iterations = 100)]
fn test_random_blocks(cx: &mut gpui::AppContext, mut rng: StdRng) {
cx.set_global(Settings::test(cx));
init_test(cx);
let operations = env::var("OPERATIONS")
.map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
@@ -1647,6 +1647,11 @@ mod tests {
}
}
fn init_test(cx: &mut gpui::AppContext) {
cx.set_global(SettingsStore::test(cx));
theme::init((), cx);
}
impl TransformBlock {
fn as_custom(&self) -> Option<&Block> {
match self {

View File

@@ -1204,7 +1204,7 @@ mod tests {
use crate::{MultiBuffer, ToPoint};
use collections::HashSet;
use rand::prelude::*;
use settings::Settings;
use settings::SettingsStore;
use std::{cmp::Reverse, env, mem, sync::Arc};
use sum_tree::TreeMap;
use util::test::sample_text;
@@ -1213,7 +1213,7 @@ mod tests {
#[gpui::test]
fn test_basic_folds(cx: &mut gpui::AppContext) {
cx.set_global(Settings::test(cx));
init_test(cx);
let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx);
let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
let buffer_snapshot = buffer.read(cx).snapshot(cx);
@@ -1286,7 +1286,7 @@ mod tests {
#[gpui::test]
fn test_adjacent_folds(cx: &mut gpui::AppContext) {
cx.set_global(Settings::test(cx));
init_test(cx);
let buffer = MultiBuffer::build_simple("abcdefghijkl", cx);
let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
let buffer_snapshot = buffer.read(cx).snapshot(cx);
@@ -1349,7 +1349,7 @@ mod tests {
#[gpui::test]
fn test_merging_folds_via_edit(cx: &mut gpui::AppContext) {
cx.set_global(Settings::test(cx));
init_test(cx);
let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx);
let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
let buffer_snapshot = buffer.read(cx).snapshot(cx);
@@ -1400,7 +1400,7 @@ mod tests {
#[gpui::test(iterations = 100)]
fn test_random_folds(cx: &mut gpui::AppContext, mut rng: StdRng) {
cx.set_global(Settings::test(cx));
init_test(cx);
let operations = env::var("OPERATIONS")
.map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
.unwrap_or(10);
@@ -1676,6 +1676,10 @@ mod tests {
assert_eq!(snapshot.buffer_rows(3).collect::<Vec<_>>(), [Some(6)]);
}
fn init_test(cx: &mut gpui::AppContext) {
cx.set_global(SettingsStore::test(cx));
}
impl FoldMap {
fn merged_fold_ranges(&self) -> Vec<Range<usize>> {
let buffer = self.buffer.lock().clone();

View File

@@ -578,7 +578,7 @@ mod tests {
use crate::{display_map::fold_map::FoldMap, MultiBuffer};
use gpui::AppContext;
use rand::{prelude::StdRng, Rng};
use settings::Settings;
use settings::SettingsStore;
use std::{
env,
ops::{Bound, RangeBounds},
@@ -631,7 +631,8 @@ mod tests {
#[gpui::test(iterations = 100)]
fn test_random_suggestions(cx: &mut AppContext, mut rng: StdRng) {
cx.set_global(Settings::test(cx));
init_test(cx);
let operations = env::var("OPERATIONS")
.map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
.unwrap_or(10);
@@ -834,6 +835,11 @@ mod tests {
}
}
fn init_test(cx: &mut AppContext) {
cx.set_global(SettingsStore::test(cx));
theme::init((), cx);
}
impl SuggestionMap {
pub fn randomly_mutate(
&self,

View File

@@ -1043,16 +1043,16 @@ mod tests {
};
use gpui::test::observe;
use rand::prelude::*;
use settings::Settings;
use settings::SettingsStore;
use smol::stream::StreamExt;
use std::{cmp, env, num::NonZeroU32};
use text::Rope;
#[gpui::test(iterations = 100)]
async fn test_random_wraps(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
cx.update(|cx| cx.set_global(Settings::test(cx)));
init_test(cx);
cx.foreground().set_block_on_ticks(0..=50);
cx.foreground().forbid_parking();
let operations = env::var("OPERATIONS")
.map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
.unwrap_or(10);
@@ -1287,6 +1287,14 @@ mod tests {
wrap_map.read_with(cx, |map, _| assert!(map.pending_edits.is_empty()));
}
fn init_test(cx: &mut gpui::TestAppContext) {
cx.foreground().forbid_parking();
cx.update(|cx| {
cx.set_global(SettingsStore::test(cx));
theme::init((), cx);
});
}
fn wrap_text(
unwrapped_text: &str,
wrap_width: Option<f32>,

View File

@@ -1,5 +1,6 @@
mod blink_manager;
pub mod display_map;
mod editor_settings;
mod element;
mod git;
@@ -19,15 +20,17 @@ mod editor_tests;
#[cfg(any(test, feature = "test-support"))]
pub mod test;
use ::git::diff::DiffHunk;
use aho_corasick::AhoCorasick;
use anyhow::{anyhow, Result};
use blink_manager::BlinkManager;
use client::ClickhouseEvent;
use client::{ClickhouseEvent, TelemetrySettings};
use clock::ReplicaId;
use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque};
use copilot::Copilot;
pub use display_map::DisplayPoint;
use display_map::*;
pub use editor_settings::EditorSettings;
pub use element::*;
use futures::FutureExt;
use fuzzy::{StringMatch, StringMatchCandidate};
@@ -51,6 +54,7 @@ pub use items::MAX_TAB_TITLE_LEN;
use itertools::Itertools;
pub use language::{char_kind, CharKind};
use language::{
language_settings::{self, all_language_settings},
AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion, CursorShape,
Diagnostic, DiagnosticSeverity, File, IndentKind, IndentSize, Language, OffsetRangeExt,
OffsetUtf16, Point, Selection, SelectionGoal, TransactionId,
@@ -70,7 +74,7 @@ use scroll::{
};
use selections_collection::{resolve_multiple, MutableSelectionsCollection, SelectionsCollection};
use serde::{Deserialize, Serialize};
use settings::Settings;
use settings::SettingsStore;
use smallvec::SmallVec;
use snippet::Snippet;
use std::{
@@ -85,7 +89,7 @@ use std::{
time::{Duration, Instant},
};
pub use sum_tree::Bias;
use theme::{DiagnosticStyle, Theme};
use theme::{DiagnosticStyle, Theme, ThemeSettings};
use util::{post_inc, RangeExt, ResultExt, TryFutureExt};
use workspace::{ItemNavHistory, ViewId, Workspace};
@@ -212,6 +216,8 @@ actions!(
MoveToNextSubwordEnd,
MoveToBeginningOfLine,
MoveToEndOfLine,
MoveToStartOfParagraph,
MoveToEndOfParagraph,
MoveToBeginning,
MoveToEnd,
SelectUp,
@@ -222,6 +228,8 @@ actions!(
SelectToPreviousSubwordStart,
SelectToNextWordEnd,
SelectToNextSubwordEnd,
SelectToStartOfParagraph,
SelectToEndOfParagraph,
SelectToBeginning,
SelectToEnd,
SelectAll,
@@ -286,7 +294,12 @@ pub enum Direction {
Next,
}
pub fn init_settings(cx: &mut AppContext) {
settings::register::<EditorSettings>(cx);
}
pub fn init(cx: &mut AppContext) {
init_settings(cx);
cx.add_action(Editor::new_file);
cx.add_action(Editor::cancel);
cx.add_action(Editor::newline);
@@ -328,6 +341,8 @@ pub fn init(cx: &mut AppContext) {
cx.add_action(Editor::move_to_next_subword_end);
cx.add_action(Editor::move_to_beginning_of_line);
cx.add_action(Editor::move_to_end_of_line);
cx.add_action(Editor::move_to_start_of_paragraph);
cx.add_action(Editor::move_to_end_of_paragraph);
cx.add_action(Editor::move_to_beginning);
cx.add_action(Editor::move_to_end);
cx.add_action(Editor::select_up);
@@ -340,6 +355,8 @@ pub fn init(cx: &mut AppContext) {
cx.add_action(Editor::select_to_next_subword_end);
cx.add_action(Editor::select_to_beginning_of_line);
cx.add_action(Editor::select_to_end_of_line);
cx.add_action(Editor::select_to_start_of_paragraph);
cx.add_action(Editor::select_to_end_of_paragraph);
cx.add_action(Editor::select_to_beginning);
cx.add_action(Editor::select_to_end);
cx.add_action(Editor::select_all);
@@ -436,7 +453,7 @@ pub enum EditorMode {
Full,
}
#[derive(Clone)]
#[derive(Clone, Debug)]
pub enum SoftWrap {
None,
EditorWidth,
@@ -471,7 +488,7 @@ pub struct Editor {
select_larger_syntax_node_stack: Vec<Box<[Selection<usize>]>>,
ime_transaction: Option<TransactionId>,
active_diagnostics: Option<ActiveDiagnosticGroup>,
soft_wrap_mode_override: Option<settings::SoftWrap>,
soft_wrap_mode_override: Option<language_settings::SoftWrap>,
get_field_editor_theme: Option<Arc<GetFieldEditorTheme>>,
override_text_style: Option<Box<OverrideTextStyle>>,
project: Option<ModelHandle<Project>>,
@@ -516,15 +533,6 @@ pub struct EditorSnapshot {
ongoing_scroll: OngoingScroll,
}
impl EditorSnapshot {
fn has_scrollbar_info(&self) -> bool {
self.buffer_snapshot
.git_diff_hunks_in_range(0..self.max_point().row(), false)
.next()
.is_some()
}
}
#[derive(Clone, Debug)]
struct SelectionHistoryEntry {
selections: Arc<[Selection<Anchor>]>,
@@ -1238,8 +1246,8 @@ impl Editor {
) -> Self {
let editor_view_id = cx.view_id();
let display_map = cx.add_model(|cx| {
let settings = cx.global::<Settings>();
let style = build_style(&*settings, get_field_editor_theme.as_deref(), None, cx);
let settings = settings::get::<ThemeSettings>(cx);
let style = build_style(settings, get_field_editor_theme.as_deref(), None, cx);
DisplayMap::new(
buffer.clone(),
style.text.font_id,
@@ -1256,7 +1264,7 @@ impl Editor {
let blink_manager = cx.add_model(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
let soft_wrap_mode_override =
(mode == EditorMode::SingleLine).then(|| settings::SoftWrap::None);
(mode == EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
let mut project_subscription = None;
if mode == EditorMode::Full && buffer.read(cx).is_singleton() {
@@ -1320,7 +1328,7 @@ impl Editor {
cx.subscribe(&buffer, Self::on_buffer_event),
cx.observe(&display_map, Self::on_display_map_changed),
cx.observe(&blink_manager, |_, _, cx| cx.notify()),
cx.observe_global::<Settings, _>(Self::settings_changed),
cx.observe_global::<SettingsStore, _>(Self::settings_changed),
],
};
@@ -1419,7 +1427,7 @@ impl Editor {
fn style(&self, cx: &AppContext) -> EditorStyle {
build_style(
cx.global::<Settings>(),
settings::get::<ThemeSettings>(cx),
self.get_field_editor_theme.as_deref(),
self.override_text_style.as_deref(),
cx,
@@ -2114,6 +2122,15 @@ impl Editor {
let had_active_copilot_suggestion = this.has_active_copilot_suggestion(cx);
this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(new_selections));
// When buffer contents is updated and caret is moved, try triggering on type formatting.
if settings::get::<EditorSettings>(cx).use_on_type_format {
if let Some(on_type_format_task) =
this.trigger_on_type_formatting(text.to_string(), cx)
{
on_type_format_task.detach_and_log_err(cx);
}
}
if had_active_copilot_suggestion {
this.refresh_copilot_suggestions(true, cx);
if !this.has_active_copilot_suggestion(cx) {
@@ -2377,7 +2394,7 @@ impl Editor {
}
fn trigger_completion_on_input(&mut self, text: &str, cx: &mut ViewContext<Self>) {
if !cx.global::<Settings>().show_completions_on_input {
if !settings::get::<EditorSettings>(cx).show_completions_on_input {
return;
}
@@ -2492,6 +2509,52 @@ impl Editor {
}
}
fn trigger_on_type_formatting(
&self,
input: String,
cx: &mut ViewContext<Self>,
) -> Option<Task<Result<()>>> {
if input.len() != 1 {
return None;
}
let project = self.project.as_ref()?;
let position = self.selections.newest_anchor().head();
let (buffer, buffer_position) = self
.buffer
.read(cx)
.text_anchor_for_position(position.clone(), cx)?;
// OnTypeFormatting retuns a list of edits, no need to pass them between Zed instances,
// hence we do LSP request & edit on host side only — add formats to host's history.
let push_to_lsp_host_history = true;
// If this is not the host, append its history with new edits.
let push_to_client_history = project.read(cx).is_remote();
let on_type_formatting = project.update(cx, |project, cx| {
project.on_type_format(
buffer.clone(),
buffer_position,
input,
push_to_lsp_host_history,
cx,
)
});
Some(cx.spawn(|editor, mut cx| async move {
if let Some(transaction) = on_type_formatting.await? {
if push_to_client_history {
buffer.update(&mut cx, |buffer, _| {
buffer.push_transaction(transaction, Instant::now());
});
}
editor.update(&mut cx, |editor, cx| {
editor.refresh_document_highlights(cx);
})?;
}
Ok(())
}))
}
fn show_completions(&mut self, _: &ShowCompletions, cx: &mut ViewContext<Self>) {
if self.pending_rename.is_some() {
return;
@@ -3144,17 +3207,12 @@ impl Editor {
snapshot: &MultiBufferSnapshot,
cx: &mut ViewContext<Self>,
) -> bool {
let settings = cx.global::<Settings>();
let path = snapshot.file_at(location).map(|file| file.path());
let path = snapshot.file_at(location).map(|file| file.path().as_ref());
let language_name = snapshot
.language_at(location)
.map(|language| language.name());
if !settings.show_copilot_suggestions(language_name.as_deref(), path.map(|p| p.as_ref())) {
return false;
}
true
let settings = all_language_settings(cx);
settings.copilot_enabled(language_name.as_deref(), path)
}
fn has_active_copilot_suggestion(&self, cx: &AppContext) -> bool {
@@ -3455,12 +3513,9 @@ impl Editor {
{
let indent_size =
buffer.indent_size_for_line(line_buffer_range.start.row);
let language_name = buffer
.language_at(line_buffer_range.start)
.map(|language| language.name());
let indent_len = match indent_size.kind {
IndentKind::Space => {
cx.global::<Settings>().tab_size(language_name.as_deref())
buffer.settings_at(line_buffer_range.start, cx).tab_size
}
IndentKind::Tab => NonZeroU32::new(1).unwrap(),
};
@@ -3572,12 +3627,11 @@ impl Editor {
}
// Otherwise, insert a hard or soft tab.
let settings = cx.global::<Settings>();
let language_name = buffer.language_at(cursor, cx).map(|l| l.name());
let tab_size = if settings.hard_tabs(language_name.as_deref()) {
let settings = buffer.settings_at(cursor, cx);
let tab_size = if settings.hard_tabs {
IndentSize::tab()
} else {
let tab_size = settings.tab_size(language_name.as_deref()).get();
let tab_size = settings.tab_size.get();
let char_column = snapshot
.text_for_range(Point::new(cursor.row, 0)..cursor)
.flat_map(str::chars)
@@ -3630,10 +3684,9 @@ impl Editor {
delta_for_start_row: u32,
cx: &AppContext,
) -> u32 {
let language_name = buffer.language_at(selection.start, cx).map(|l| l.name());
let settings = cx.global::<Settings>();
let tab_size = settings.tab_size(language_name.as_deref()).get();
let indent_kind = if settings.hard_tabs(language_name.as_deref()) {
let settings = buffer.settings_at(selection.start, cx);
let tab_size = settings.tab_size.get();
let indent_kind = if settings.hard_tabs {
IndentKind::Tab
} else {
IndentKind::Space
@@ -3702,11 +3755,8 @@ impl Editor {
let buffer = self.buffer.read(cx);
let snapshot = buffer.snapshot(cx);
for selection in &selections {
let language_name = buffer.language_at(selection.start, cx).map(|l| l.name());
let tab_size = cx
.global::<Settings>()
.tab_size(language_name.as_deref())
.get();
let settings = buffer.settings_at(selection.start, cx);
let tab_size = settings.tab_size.get();
let mut rows = selection.spanned_rows(false, &display_map);
// Avoid re-outdenting a row that has already been outdented by a
@@ -4766,6 +4816,80 @@ impl Editor {
});
}
pub fn move_to_start_of_paragraph(
&mut self,
_: &MoveToStartOfParagraph,
cx: &mut ViewContext<Self>,
) {
if matches!(self.mode, EditorMode::SingleLine) {
cx.propagate_action();
return;
}
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.move_with(|map, selection| {
selection.collapse_to(
movement::start_of_paragraph(map, selection.head()),
SelectionGoal::None,
)
});
})
}
pub fn move_to_end_of_paragraph(
&mut self,
_: &MoveToEndOfParagraph,
cx: &mut ViewContext<Self>,
) {
if matches!(self.mode, EditorMode::SingleLine) {
cx.propagate_action();
return;
}
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.move_with(|map, selection| {
selection.collapse_to(
movement::end_of_paragraph(map, selection.head()),
SelectionGoal::None,
)
});
})
}
pub fn select_to_start_of_paragraph(
&mut self,
_: &SelectToStartOfParagraph,
cx: &mut ViewContext<Self>,
) {
if matches!(self.mode, EditorMode::SingleLine) {
cx.propagate_action();
return;
}
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.move_heads_with(|map, head, _| {
(movement::start_of_paragraph(map, head), SelectionGoal::None)
});
})
}
pub fn select_to_end_of_paragraph(
&mut self,
_: &SelectToEndOfParagraph,
cx: &mut ViewContext<Self>,
) {
if matches!(self.mode, EditorMode::SingleLine) {
cx.propagate_action();
return;
}
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.move_heads_with(|map, head, _| {
(movement::end_of_paragraph(map, head), SelectionGoal::None)
});
})
}
pub fn move_to_beginning(&mut self, _: &MoveToBeginning, cx: &mut ViewContext<Self>) {
if matches!(self.mode, EditorMode::SingleLine) {
cx.propagate_action();
@@ -4807,12 +4931,12 @@ impl Editor {
}
fn push_to_nav_history(
&self,
&mut self,
cursor_anchor: Anchor,
new_position: Option<Point>,
cx: &mut ViewContext<Self>,
) {
if let Some(nav_history) = &self.nav_history {
if let Some(nav_history) = self.nav_history.as_mut() {
let buffer = self.buffer.read(cx).read(cx);
let cursor_position = cursor_anchor.to_point(&buffer);
let scroll_state = self.scroll_manager.anchor();
@@ -5574,68 +5698,91 @@ impl Editor {
}
fn go_to_hunk(&mut self, _: &GoToHunk, cx: &mut ViewContext<Self>) {
self.go_to_hunk_impl(Direction::Next, cx)
}
fn go_to_prev_hunk(&mut self, _: &GoToPrevHunk, cx: &mut ViewContext<Self>) {
self.go_to_hunk_impl(Direction::Prev, cx)
}
pub fn go_to_hunk_impl(&mut self, direction: Direction, cx: &mut ViewContext<Self>) {
let snapshot = self
.display_map
.update(cx, |display_map, cx| display_map.snapshot(cx));
let selection = self.selections.newest::<Point>(cx);
fn seek_in_direction(
this: &mut Editor,
snapshot: &DisplaySnapshot,
initial_point: Point,
is_wrapped: bool,
direction: Direction,
cx: &mut ViewContext<Editor>,
) -> bool {
let hunks = if direction == Direction::Next {
if !self.seek_in_direction(
&snapshot,
selection.head(),
false,
snapshot
.buffer_snapshot
.git_diff_hunks_in_range((selection.head().row + 1)..u32::MAX),
cx,
) {
let wrapped_point = Point::zero();
self.seek_in_direction(
&snapshot,
wrapped_point,
true,
snapshot
.buffer_snapshot
.git_diff_hunks_in_range(initial_point.row..u32::MAX, false)
} else {
snapshot
.buffer_snapshot
.git_diff_hunks_in_range(0..initial_point.row, true)
};
let display_point = initial_point.to_display_point(snapshot);
let mut hunks = hunks
.map(|hunk| diff_hunk_to_display(hunk, &snapshot))
.skip_while(|hunk| {
if is_wrapped {
false
} else {
hunk.contains_display_row(display_point.row())
}
})
.dedup();
if let Some(hunk) = hunks.next() {
this.change_selections(Some(Autoscroll::fit()), cx, |s| {
let row = hunk.start_display_row();
let point = DisplayPoint::new(row, 0);
s.select_display_ranges([point..point]);
});
true
} else {
false
}
.git_diff_hunks_in_range((wrapped_point.row + 1)..u32::MAX),
cx,
);
}
}
if !seek_in_direction(self, &snapshot, selection.head(), false, direction, cx) {
let wrapped_point = match direction {
Direction::Next => Point::zero(),
Direction::Prev => snapshot.buffer_snapshot.max_point(),
};
seek_in_direction(self, &snapshot, wrapped_point, true, direction, cx);
fn go_to_prev_hunk(&mut self, _: &GoToPrevHunk, cx: &mut ViewContext<Self>) {
let snapshot = self
.display_map
.update(cx, |display_map, cx| display_map.snapshot(cx));
let selection = self.selections.newest::<Point>(cx);
if !self.seek_in_direction(
&snapshot,
selection.head(),
false,
snapshot
.buffer_snapshot
.git_diff_hunks_in_range_rev(0..selection.head().row),
cx,
) {
let wrapped_point = snapshot.buffer_snapshot.max_point();
self.seek_in_direction(
&snapshot,
wrapped_point,
true,
snapshot
.buffer_snapshot
.git_diff_hunks_in_range_rev(0..wrapped_point.row),
cx,
);
}
}
fn seek_in_direction(
&mut self,
snapshot: &DisplaySnapshot,
initial_point: Point,
is_wrapped: bool,
hunks: impl Iterator<Item = DiffHunk<u32>>,
cx: &mut ViewContext<Editor>,
) -> bool {
let display_point = initial_point.to_display_point(snapshot);
let mut hunks = hunks
.map(|hunk| diff_hunk_to_display(hunk, &snapshot))
.skip_while(|hunk| {
if is_wrapped {
false
} else {
hunk.contains_display_row(display_point.row())
}
})
.dedup();
if let Some(hunk) = hunks.next() {
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
let row = hunk.start_display_row();
let point = DisplayPoint::new(row, 0);
s.select_display_ranges([point..point]);
});
true
} else {
false
}
}
@@ -6467,27 +6614,24 @@ impl Editor {
}
pub fn soft_wrap_mode(&self, cx: &AppContext) -> SoftWrap {
let language_name = self
.buffer
.read(cx)
.as_singleton()
.and_then(|singleton_buffer| singleton_buffer.read(cx).language())
.map(|l| l.name());
let settings = cx.global::<Settings>();
let settings = self.buffer.read(cx).settings_at(0, cx);
let mode = self
.soft_wrap_mode_override
.unwrap_or_else(|| settings.soft_wrap(language_name.as_deref()));
.unwrap_or_else(|| settings.soft_wrap);
match mode {
settings::SoftWrap::None => SoftWrap::None,
settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
settings::SoftWrap::PreferredLineLength => {
SoftWrap::Column(settings.preferred_line_length(language_name.as_deref()))
language_settings::SoftWrap::None => SoftWrap::None,
language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
language_settings::SoftWrap::PreferredLineLength => {
SoftWrap::Column(settings.preferred_line_length)
}
}
}
pub fn set_soft_wrap_mode(&mut self, mode: settings::SoftWrap, cx: &mut ViewContext<Self>) {
pub fn set_soft_wrap_mode(
&mut self,
mode: language_settings::SoftWrap,
cx: &mut ViewContext<Self>,
) {
self.soft_wrap_mode_override = Some(mode);
cx.notify();
}
@@ -6502,8 +6646,8 @@ impl Editor {
self.soft_wrap_mode_override.take();
} else {
let soft_wrap = match self.soft_wrap_mode(cx) {
SoftWrap::None => settings::SoftWrap::EditorWidth,
SoftWrap::EditorWidth | SoftWrap::Column(_) => settings::SoftWrap::None,
SoftWrap::None => language_settings::SoftWrap::EditorWidth,
SoftWrap::EditorWidth | SoftWrap::Column(_) => language_settings::SoftWrap::None,
};
self.soft_wrap_mode_override = Some(soft_wrap);
}
@@ -6578,8 +6722,8 @@ impl Editor {
let buffer = &snapshot.buffer_snapshot;
let start = buffer.anchor_before(0);
let end = buffer.anchor_after(buffer.len());
let theme = cx.global::<Settings>().theme.as_ref();
self.background_highlights_in_range(start..end, &snapshot, theme)
let theme = theme::current(cx);
self.background_highlights_in_range(start..end, &snapshot, theme.as_ref())
}
fn document_highlights_for_position<'a>(
@@ -6729,6 +6873,7 @@ impl Editor {
multi_buffer::Event::Saved => cx.emit(Event::Saved),
multi_buffer::Event::FileHandleChanged => cx.emit(Event::TitleChanged),
multi_buffer::Event::Reloaded => cx.emit(Event::TitleChanged),
multi_buffer::Event::DiffBaseChanged => cx.emit(Event::DiffBaseChanged),
multi_buffer::Event::Closed => cx.emit(Event::Closed),
multi_buffer::Event::DiagnosticsUpdated => {
self.refresh_active_diagnostics(cx);
@@ -6910,7 +7055,7 @@ impl Editor {
.map(|a| a.to_string());
let telemetry = project.read(cx).client().telemetry().clone();
let telemetry_settings = cx.global::<Settings>().telemetry();
let telemetry_settings = *settings::get::<TelemetrySettings>(cx);
let event = ClickhouseEvent::Copilot {
suggestion_id,
@@ -6940,33 +7085,37 @@ impl Editor {
.and_then(|e| e.to_str())
.map(|a| a.to_string()));
let settings = cx.global::<Settings>();
let vim_mode = cx
.global::<SettingsStore>()
.untyped_user_settings()
.get("vim_mode")
== Some(&serde_json::Value::Bool(true));
let telemetry_settings = *settings::get::<TelemetrySettings>(cx);
let copilot_enabled = all_language_settings(cx).copilot_enabled(None, None);
let copilot_enabled_for_language = self
.buffer
.read(cx)
.settings_at(0, cx)
.show_copilot_suggestions;
let telemetry = project.read(cx).client().telemetry().clone();
telemetry.report_mixpanel_event(
match name {
"open" => "open editor",
"save" => "save editor",
_ => name,
},
json!({ "File Extension": file_extension, "Vim Mode": settings.vim_mode, "In Clickhouse": true }),
settings.telemetry(),
);
match name {
"open" => "open editor",
"save" => "save editor",
_ => name,
},
json!({ "File Extension": file_extension, "Vim Mode": vim_mode, "In Clickhouse": true }),
telemetry_settings,
);
let event = ClickhouseEvent::Editor {
file_extension,
vim_mode: settings.vim_mode,
vim_mode,
operation: name,
copilot_enabled: settings.features.copilot,
copilot_enabled_for_language: settings.show_copilot_suggestions(
self.language_at(0, cx)
.map(|language| language.name())
.as_deref(),
self.file_at(0, cx)
.map(|file| file.path().clone())
.as_deref(),
),
copilot_enabled,
copilot_enabled_for_language,
};
telemetry.report_clickhouse_event(event, settings.telemetry())
telemetry.report_clickhouse_event(event, telemetry_settings)
}
/// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
@@ -6998,7 +7147,7 @@ impl Editor {
let mut lines = Vec::new();
let mut line: VecDeque<Chunk> = VecDeque::new();
let theme = &cx.global::<Settings>().theme.editor.syntax;
let theme = &theme::current(cx).editor.syntax;
for chunk in chunks {
let highlight = chunk.syntax_highlight_id.and_then(|id| id.name(theme));
@@ -7108,10 +7257,12 @@ pub enum Event {
BufferEdited,
Edited,
Reparsed,
Focused,
Blurred,
DirtyChanged,
Saved,
TitleChanged,
DiffBaseChanged,
SelectionsChanged {
local: bool,
},
@@ -7161,6 +7312,7 @@ impl View for Editor {
fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
if cx.is_self_focused() {
let focused_event = EditorFocused(cx.handle());
cx.emit(Event::Focused);
cx.emit_global(focused_event);
}
if let Some(rename) = self.pending_rename.as_ref() {
@@ -7235,7 +7387,7 @@ impl View for Editor {
false
}
fn update_keymap_context(&self, keymap: &mut KeymapContext, _: &AppContext) {
fn update_keymap_context(&self, keymap: &mut KeymapContext, cx: &AppContext) {
Self::reset_to_default_keymap_context(keymap);
let mode = match self.mode {
EditorMode::SingleLine => "single_line",
@@ -7251,10 +7403,18 @@ impl View for Editor {
Some(ContextMenu::CodeActions(_)) => keymap.add_identifier("showing_code_actions"),
None => {}
}
for layer in self.keymap_context_layers.values() {
keymap.extend(layer);
}
if let Some(extension) = self
.buffer
.read(cx)
.as_singleton()
.and_then(|buffer| buffer.read(cx).file()?.path().extension()?.to_str())
{
keymap.add_key("extension", extension.to_string());
}
}
fn text_for_range(&self, range_utf16: Range<usize>, cx: &AppContext) -> Option<String> {
@@ -7420,7 +7580,7 @@ impl View for Editor {
}
fn build_style(
settings: &Settings,
settings: &ThemeSettings,
get_field_editor_theme: Option<&GetFieldEditorTheme>,
override_text_style: Option<&OverrideTextStyle>,
cx: &AppContext,
@@ -7450,7 +7610,7 @@ fn build_style(
let font_id = font_cache
.select_font(font_family_id, &font_properties)
.unwrap();
let font_size = settings.buffer_font_size;
let font_size = settings.buffer_font_size(cx);
EditorStyle {
text: TextStyle {
color: settings.theme.editor.text_color,
@@ -7620,10 +7780,10 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, is_valid: bool) -> Rend
}
Arc::new(move |cx: &mut BlockContext| {
let settings = cx.global::<Settings>();
let settings = settings::get::<ThemeSettings>(cx);
let theme = &settings.theme.editor;
let style = diagnostic_style(diagnostic.severity, is_valid, theme);
let font_size = (style.text_scale_factor * settings.buffer_font_size).round();
let font_size = (style.text_scale_factor * settings.buffer_font_size(cx)).round();
Flex::column()
.with_children(highlighted_lines.iter().map(|(line, highlights)| {
Label::new(

View File

@@ -0,0 +1,56 @@
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::Setting;
#[derive(Deserialize)]
pub struct EditorSettings {
pub cursor_blink: bool,
pub hover_popover_enabled: bool,
pub show_completions_on_input: bool,
pub use_on_type_format: bool,
pub scrollbar: Scrollbar,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
pub struct Scrollbar {
pub show: ShowScrollbar,
pub git_diff: bool,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ShowScrollbar {
Auto,
System,
Always,
Never,
}
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
pub struct EditorSettingsContent {
pub cursor_blink: Option<bool>,
pub hover_popover_enabled: Option<bool>,
pub show_completions_on_input: Option<bool>,
pub use_on_type_format: Option<bool>,
pub scrollbar: Option<ScrollbarContent>,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
pub struct ScrollbarContent {
pub show: Option<ShowScrollbar>,
pub git_diff: Option<bool>,
}
impl Setting for EditorSettings {
const KEY: Option<&'static str> = None;
type FileContent = EditorSettingsContent;
fn load(
default_value: &Self::FileContent,
user_values: &[&Self::FileContent],
_: &gpui::AppContext,
) -> anyhow::Result<Self> {
Self::load_via_json_merge(default_value, user_values)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -5,6 +5,7 @@ use super::{
};
use crate::{
display_map::{BlockStyle, DisplaySnapshot, FoldStatus, TransformBlock},
editor_settings::ShowScrollbar,
git::{diff_hunk_to_display, DisplayDiffHunk},
hover_popover::{
hide_hover, hover_at, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH,
@@ -13,7 +14,7 @@ use crate::{
link_go_to_definition::{
go_to_fetched_definition, go_to_fetched_type_definition, update_go_to_definition_link,
},
mouse_context_menu, EditorStyle, GutterHover, UnfoldAt,
mouse_context_menu, EditorSettings, EditorStyle, GutterHover, UnfoldAt,
};
use clock::ReplicaId;
use collections::{BTreeMap, HashMap};
@@ -35,9 +36,14 @@ use gpui::{
};
use itertools::Itertools;
use json::json;
use language::{Bias, CursorShape, DiagnosticSeverity, OffsetUtf16, Selection};
use project::ProjectPath;
use settings::{GitGutter, Settings, ShowWhitespaces};
use language::{
language_settings::ShowWhitespaceSetting, Bias, CursorShape, DiagnosticSeverity, OffsetUtf16,
Selection,
};
use project::{
project_settings::{GitGutterSetting, ProjectSettings},
ProjectPath,
};
use smallvec::SmallVec;
use std::{
borrow::Cow,
@@ -47,6 +53,7 @@ use std::{
ops::Range,
sync::Arc,
};
use text::Point;
use workspace::item::Item;
enum FoldMarkers {}
@@ -547,11 +554,8 @@ impl EditorElement {
let scroll_top = scroll_position.y() * line_height;
let show_gutter = matches!(
&cx.global::<Settings>()
.git_overrides
.git_gutter
.unwrap_or_default(),
GitGutter::TrackedFiles
settings::get::<ProjectSettings>(cx).git.git_gutter,
Some(GitGutterSetting::TrackedFiles)
);
if show_gutter {
@@ -608,7 +612,7 @@ impl EditorElement {
layout: &mut LayoutState,
cx: &mut ViewContext<Editor>,
) {
let diff_style = &cx.global::<Settings>().theme.editor.diff.clone();
let diff_style = &theme::current(cx).editor.diff.clone();
let line_height = layout.position_map.line_height;
let scroll_position = layout.position_map.snapshot.scroll_position();
@@ -648,7 +652,7 @@ impl EditorElement {
//TODO: This rendering is entirely a horrible hack
DiffHunkStatus::Removed => {
let row = *display_row_range.start();
let row = display_row_range.start;
let offset = line_height / 2.;
let start_y = row as f32 * line_height - offset - scroll_top;
@@ -670,11 +674,11 @@ impl EditorElement {
}
};
let start_row = *display_row_range.start();
let end_row = *display_row_range.end();
let start_row = display_row_range.start;
let end_row = display_row_range.end;
let start_y = start_row as f32 * line_height - scroll_top;
let end_y = end_row as f32 * line_height - scroll_top + line_height;
let end_y = end_row as f32 * line_height - scroll_top;
let width = diff_style.width_em * line_height;
let highlight_origin = bounds.origin() + vec2f(-width, start_y);
@@ -708,6 +712,7 @@ impl EditorElement {
let scroll_left = scroll_position.x() * max_glyph_width;
let content_origin = bounds.origin() + vec2f(layout.gutter_margin, 0.);
let line_end_overshoot = 0.15 * layout.position_map.line_height;
let whitespace_setting = editor.buffer.read(cx).settings_at(0, cx).show_whitespaces;
scene.push_layer(Some(bounds));
@@ -882,9 +887,10 @@ impl EditorElement {
content_origin,
scroll_left,
visible_text_bounds,
cx,
whitespace_setting,
&invisible_display_ranges,
visible_bounds,
cx,
)
}
}
@@ -1046,47 +1052,53 @@ impl EditorElement {
..Default::default()
});
let diff_style = cx.global::<Settings>().theme.editor.diff.clone();
for hunk in layout
.position_map
.snapshot
.buffer_snapshot
.git_diff_hunks_in_range(0..(max_row.floor() as u32), false)
{
let start_y = y_for_row(hunk.buffer_range.start as f32);
let mut end_y = if hunk.buffer_range.start == hunk.buffer_range.end {
y_for_row((hunk.buffer_range.end + 1) as f32)
} else {
y_for_row((hunk.buffer_range.end) as f32)
};
if layout.is_singleton && settings::get::<EditorSettings>(cx).scrollbar.git_diff {
let diff_style = theme::current(cx).editor.scrollbar.git.clone();
for hunk in layout
.position_map
.snapshot
.buffer_snapshot
.git_diff_hunks_in_range(0..(max_row.floor() as u32))
{
let start_display = Point::new(hunk.buffer_range.start, 0)
.to_display_point(&layout.position_map.snapshot.display_snapshot);
let end_display = Point::new(hunk.buffer_range.end, 0)
.to_display_point(&layout.position_map.snapshot.display_snapshot);
let start_y = y_for_row(start_display.row() as f32);
let mut end_y = if hunk.buffer_range.start == hunk.buffer_range.end {
y_for_row((end_display.row() + 1) as f32)
} else {
y_for_row((end_display.row()) as f32)
};
if end_y - start_y < 1. {
end_y = start_y + 1.;
if end_y - start_y < 1. {
end_y = start_y + 1.;
}
let bounds = RectF::from_points(vec2f(left, start_y), vec2f(right, end_y));
let color = match hunk.status() {
DiffHunkStatus::Added => diff_style.inserted,
DiffHunkStatus::Modified => diff_style.modified,
DiffHunkStatus::Removed => diff_style.deleted,
};
let border = Border {
width: 1.,
color: style.thumb.border.color,
overlay: false,
top: false,
right: true,
bottom: false,
left: true,
};
scene.push_quad(Quad {
bounds,
background: Some(color),
border,
corner_radius: style.thumb.corner_radius,
})
}
let bounds = RectF::from_points(vec2f(left, start_y), vec2f(right, end_y));
let color = match hunk.status() {
DiffHunkStatus::Added => diff_style.inserted,
DiffHunkStatus::Modified => diff_style.modified,
DiffHunkStatus::Removed => diff_style.deleted,
};
let border = Border {
width: 1.,
color: style.thumb.border.color,
overlay: false,
top: false,
right: true,
bottom: false,
left: true,
};
scene.push_quad(Quad {
bounds,
background: Some(color),
border,
corner_radius: style.thumb.corner_radius,
})
}
scene.push_quad(Quad {
@@ -1264,7 +1276,7 @@ impl EditorElement {
.row;
buffer_snapshot
.git_diff_hunks_in_range(buffer_start_row..buffer_end_row, false)
.git_diff_hunks_in_range(buffer_start_row..buffer_end_row)
.map(|hunk| diff_hunk_to_display(hunk, snapshot))
.dedup()
.collect()
@@ -1457,7 +1469,7 @@ impl EditorElement {
editor: &mut Editor,
cx: &mut LayoutContext<Editor>,
) -> (f32, Vec<BlockLayout>) {
let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
let tooltip_style = theme::current(cx).tooltip.clone();
let scroll_x = snapshot.scroll_anchor.offset.x();
let (fixed_blocks, non_fixed_blocks) = snapshot
.blocks_in_range(rows.clone())
@@ -1783,9 +1795,10 @@ impl LineWithInvisibles {
content_origin: Vector2F,
scroll_left: f32,
visible_text_bounds: RectF,
cx: &mut ViewContext<Editor>,
whitespace_setting: ShowWhitespaceSetting,
selection_ranges: &[Range<DisplayPoint>],
visible_bounds: RectF,
cx: &mut ViewContext<Editor>,
) {
let line_height = layout.position_map.line_height;
let line_y = row as f32 * line_height - scroll_top;
@@ -1799,7 +1812,6 @@ impl LineWithInvisibles {
);
self.draw_invisibles(
cx,
&selection_ranges,
layout,
content_origin,
@@ -1809,12 +1821,13 @@ impl LineWithInvisibles {
scene,
visible_bounds,
line_height,
whitespace_setting,
cx,
);
}
fn draw_invisibles(
&self,
cx: &mut ViewContext<Editor>,
selection_ranges: &[Range<DisplayPoint>],
layout: &LayoutState,
content_origin: Vector2F,
@@ -1824,17 +1837,13 @@ impl LineWithInvisibles {
scene: &mut SceneBuilder,
visible_bounds: RectF,
line_height: f32,
whitespace_setting: ShowWhitespaceSetting,
cx: &mut ViewContext<Editor>,
) {
let settings = cx.global::<Settings>();
let allowed_invisibles_regions = match settings
.editor_overrides
.show_whitespaces
.or(settings.editor_defaults.show_whitespaces)
.unwrap_or_default()
{
ShowWhitespaces::None => return,
ShowWhitespaces::Selection => Some(selection_ranges),
ShowWhitespaces::All => None,
let allowed_invisibles_regions = match whitespace_setting {
ShowWhitespaceSetting::None => return,
ShowWhitespaceSetting::Selection => Some(selection_ranges),
ShowWhitespaceSetting::All => None,
};
for invisible in &self.invisibles {
@@ -1979,11 +1988,11 @@ impl Element<Editor> for EditorElement {
let is_singleton = editor.is_singleton(cx);
let highlighted_rows = editor.highlighted_rows();
let theme = cx.global::<Settings>().theme.as_ref();
let theme = theme::current(cx);
let highlighted_ranges = editor.background_highlights_in_range(
start_anchor..end_anchor,
&snapshot.display_snapshot,
theme,
theme.as_ref(),
);
fold_ranges.extend(
@@ -2058,13 +2067,17 @@ impl Element<Editor> for EditorElement {
));
}
let show_scrollbars = match cx.global::<Settings>().show_scrollbars {
settings::ShowScrollbars::Auto => {
snapshot.has_scrollbar_info() || editor.scroll_manager.scrollbars_visible()
let scrollbar_settings = &settings::get::<EditorSettings>(cx).scrollbar;
let show_scrollbars = match scrollbar_settings.show {
ShowScrollbar::Auto => {
// Git
(is_singleton && scrollbar_settings.git_diff && snapshot.buffer_snapshot.has_git_diffs())
// Scrollmanager
|| editor.scroll_manager.scrollbars_visible()
}
settings::ShowScrollbars::System => editor.scroll_manager.scrollbars_visible(),
settings::ShowScrollbars::Always => true,
settings::ShowScrollbars::Never => false,
ShowScrollbar::System => editor.scroll_manager.scrollbars_visible(),
ShowScrollbar::Always => true,
ShowScrollbar::Never => false,
};
let include_root = editor
@@ -2283,6 +2296,7 @@ impl Element<Editor> for EditorElement {
text_size,
scrollbar_row_range,
show_scrollbars,
is_singleton,
max_row,
gutter_margin,
active_rows,
@@ -2438,6 +2452,7 @@ pub struct LayoutState {
selections: Vec<(ReplicaId, Vec<SelectionLayout>)>,
scrollbar_row_range: Range<f32>,
show_scrollbars: bool,
is_singleton: bool,
max_row: u32,
context_menu: Option<(DisplayPoint, AnyElement<Editor>)>,
code_actions_indicator: Option<(u32, AnyElement<Editor>)>,
@@ -2826,17 +2841,19 @@ mod tests {
use super::*;
use crate::{
display_map::{BlockDisposition, BlockProperties},
editor_tests::{init_test, update_test_settings},
Editor, MultiBuffer,
};
use gpui::TestAppContext;
use language::language_settings;
use log::info;
use settings::Settings;
use std::{num::NonZeroU32, sync::Arc};
use util::test::sample_text;
#[gpui::test]
fn test_layout_line_numbers(cx: &mut TestAppContext) {
cx.update(|cx| cx.set_global(Settings::test(cx)));
init_test(cx, |_| {});
let (_, editor) = cx.add_window(|cx| {
let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx);
Editor::new(EditorMode::Full, buffer, None, None, cx)
@@ -2854,7 +2871,8 @@ mod tests {
#[gpui::test]
fn test_layout_with_placeholder_text_and_blocks(cx: &mut TestAppContext) {
cx.update(|cx| cx.set_global(Settings::test(cx)));
init_test(cx, |_| {});
let (_, editor) = cx.add_window(|cx| {
let buffer = MultiBuffer::build_simple("", cx);
Editor::new(EditorMode::Full, buffer, None, None, cx)
@@ -2914,26 +2932,27 @@ mod tests {
#[gpui::test]
fn test_all_invisibles_drawing(cx: &mut TestAppContext) {
let tab_size = 4;
const TAB_SIZE: u32 = 4;
let input_text = "\t \t|\t| a b";
let expected_invisibles = vec![
Invisible::Tab {
line_start_offset: 0,
},
Invisible::Whitespace {
line_offset: tab_size as usize,
line_offset: TAB_SIZE as usize,
},
Invisible::Tab {
line_start_offset: tab_size as usize + 1,
line_start_offset: TAB_SIZE as usize + 1,
},
Invisible::Tab {
line_start_offset: tab_size as usize * 2 + 1,
line_start_offset: TAB_SIZE as usize * 2 + 1,
},
Invisible::Whitespace {
line_offset: tab_size as usize * 3 + 1,
line_offset: TAB_SIZE as usize * 3 + 1,
},
Invisible::Whitespace {
line_offset: tab_size as usize * 3 + 3,
line_offset: TAB_SIZE as usize * 3 + 3,
},
];
assert_eq!(
@@ -2945,12 +2964,11 @@ mod tests {
"Hardcoded expected invisibles differ from the actual ones in '{input_text}'"
);
cx.update(|cx| {
let mut test_settings = Settings::test(cx);
test_settings.editor_defaults.show_whitespaces = Some(ShowWhitespaces::All);
test_settings.editor_defaults.tab_size = Some(NonZeroU32::new(tab_size).unwrap());
cx.set_global(test_settings);
init_test(cx, |s| {
s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
s.defaults.tab_size = NonZeroU32::new(TAB_SIZE);
});
let actual_invisibles =
collect_invisibles_from_new_editor(cx, EditorMode::Full, &input_text, 500.0);
@@ -2959,11 +2977,9 @@ mod tests {
#[gpui::test]
fn test_invisibles_dont_appear_in_certain_editors(cx: &mut TestAppContext) {
cx.update(|cx| {
let mut test_settings = Settings::test(cx);
test_settings.editor_defaults.show_whitespaces = Some(ShowWhitespaces::All);
test_settings.editor_defaults.tab_size = Some(NonZeroU32::new(4).unwrap());
cx.set_global(test_settings);
init_test(cx, |s| {
s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
s.defaults.tab_size = NonZeroU32::new(4);
});
for editor_mode_without_invisibles in [
@@ -3014,19 +3030,18 @@ mod tests {
);
info!("Expected invisibles: {expected_invisibles:?}");
init_test(cx, |_| {});
// Put the same string with repeating whitespace pattern into editors of various size,
// take deliberately small steps during resizing, to put all whitespace kinds near the wrap point.
let resize_step = 10.0;
let mut editor_width = 200.0;
while editor_width <= 1000.0 {
cx.update(|cx| {
let mut test_settings = Settings::test(cx);
test_settings.editor_defaults.tab_size = Some(NonZeroU32::new(tab_size).unwrap());
test_settings.editor_defaults.show_whitespaces = Some(ShowWhitespaces::All);
test_settings.editor_defaults.preferred_line_length = Some(editor_width as u32);
test_settings.editor_defaults.soft_wrap =
Some(settings::SoftWrap::PreferredLineLength);
cx.set_global(test_settings);
update_test_settings(cx, |s| {
s.defaults.tab_size = NonZeroU32::new(tab_size);
s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
s.defaults.preferred_line_length = Some(editor_width as u32);
s.defaults.soft_wrap = Some(language_settings::SoftWrap::PreferredLineLength);
});
let actual_invisibles =
@@ -3074,7 +3089,7 @@ mod tests {
let mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
let (_, layout_state) = editor.update(cx, |editor, cx| {
editor.set_soft_wrap_mode(settings::SoftWrap::EditorWidth, cx);
editor.set_soft_wrap_mode(language_settings::SoftWrap::EditorWidth, cx);
editor.set_wrap_width(Some(editor_width), cx);
let mut new_parents = Default::default();

View File

@@ -1,4 +1,4 @@
use std::ops::RangeInclusive;
use std::ops::Range;
use git::diff::{DiffHunk, DiffHunkStatus};
use language::Point;
@@ -15,7 +15,7 @@ pub enum DisplayDiffHunk {
},
Unfolded {
display_row_range: RangeInclusive<u32>,
display_row_range: Range<u32>,
status: DiffHunkStatus,
},
}
@@ -26,7 +26,7 @@ impl DisplayDiffHunk {
&DisplayDiffHunk::Folded { display_row } => display_row,
DisplayDiffHunk::Unfolded {
display_row_range, ..
} => *display_row_range.start(),
} => display_row_range.start,
}
}
@@ -36,7 +36,7 @@ impl DisplayDiffHunk {
DisplayDiffHunk::Unfolded {
display_row_range, ..
} => display_row_range.clone(),
} => display_row_range.start..=display_row_range.end - 1,
};
range.contains(&display_row)
@@ -77,16 +77,12 @@ pub fn diff_hunk_to_display(hunk: DiffHunk<u32>, snapshot: &DisplaySnapshot) ->
} else {
let start = hunk_start_point.to_display_point(snapshot).row();
let hunk_end_row_inclusive = hunk
.buffer_range
.end
.saturating_sub(1)
.max(hunk.buffer_range.start);
let hunk_end_row_inclusive = hunk.buffer_range.end.max(hunk.buffer_range.start);
let hunk_end_point = Point::new(hunk_end_row_inclusive, 0);
let end = hunk_end_point.to_display_point(snapshot).row();
DisplayDiffHunk::Unfolded {
display_row_range: start..=end,
display_row_range: start..end,
status: hunk.status(),
}
}

View File

@@ -33,12 +33,14 @@ pub fn refresh_matching_bracket_highlights(editor: &mut Editor, cx: &mut ViewCon
#[cfg(test)]
mod tests {
use super::*;
use crate::test::editor_lsp_test_context::EditorLspTestContext;
use crate::{editor_tests::init_test, test::editor_lsp_test_context::EditorLspTestContext};
use indoc::indoc;
use language::{BracketPair, BracketPairConfig, Language, LanguageConfig};
#[gpui::test]
async fn test_matching_bracket_highlights(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorLspTestContext::new(
Language::new(
LanguageConfig {

View File

@@ -1,6 +1,6 @@
use crate::{
display_map::ToDisplayPoint, Anchor, AnchorRangeExt, DisplayPoint, Editor, EditorSnapshot,
EditorStyle, RangeToAnchorExt,
display_map::ToDisplayPoint, Anchor, AnchorRangeExt, DisplayPoint, Editor, EditorSettings,
EditorSnapshot, EditorStyle, RangeToAnchorExt,
};
use futures::FutureExt;
use gpui::{
@@ -12,7 +12,6 @@ use gpui::{
};
use language::{Bias, DiagnosticEntry, DiagnosticSeverity, Language, LanguageRegistry};
use project::{HoverBlock, HoverBlockKind, Project};
use settings::Settings;
use std::{ops::Range, sync::Arc, time::Duration};
use util::TryFutureExt;
@@ -38,7 +37,7 @@ pub fn hover(editor: &mut Editor, _: &Hover, cx: &mut ViewContext<Editor>) {
/// The internal hover action dispatches between `show_hover` or `hide_hover`
/// depending on whether a point to hover over is provided.
pub fn hover_at(editor: &mut Editor, point: Option<DisplayPoint>, cx: &mut ViewContext<Editor>) {
if cx.global::<Settings>().hover_popover_enabled {
if settings::get::<EditorSettings>(cx).hover_popover_enabled {
if let Some(point) = point {
show_hover(editor, point, false, cx);
} else {
@@ -654,7 +653,7 @@ impl DiagnosticPopover {
_ => style.hover_popover.container,
};
let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
let tooltip_style = theme::current(cx).tooltip.clone();
MouseEventHandler::<DiagnosticPopover, _>::new(0, cx, |_, _| {
text.with_soft_wrap(true)
@@ -694,7 +693,7 @@ impl DiagnosticPopover {
#[cfg(test)]
mod tests {
use super::*;
use crate::test::editor_lsp_test_context::EditorLspTestContext;
use crate::{editor_tests::init_test, test::editor_lsp_test_context::EditorLspTestContext};
use gpui::fonts::Weight;
use indoc::indoc;
use language::{Diagnostic, DiagnosticSet};
@@ -706,6 +705,8 @@ mod tests {
#[gpui::test]
async fn test_mouse_hover_info_popover(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorLspTestContext::new_rust(
lsp::ServerCapabilities {
hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
@@ -773,6 +774,8 @@ mod tests {
#[gpui::test]
async fn test_keyboard_hover_info_popover(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorLspTestContext::new_rust(
lsp::ServerCapabilities {
hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
@@ -816,6 +819,8 @@ mod tests {
#[gpui::test]
async fn test_hover_diagnostic_and_info_popovers(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorLspTestContext::new_rust(
lsp::ServerCapabilities {
hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
@@ -882,7 +887,8 @@ mod tests {
#[gpui::test]
fn test_render_blocks(cx: &mut gpui::TestAppContext) {
Settings::test_async(cx);
init_test(cx, |_| {});
cx.add_window(|cx| {
let editor = Editor::single_line(None, cx);
let style = editor.style(cx);

View File

@@ -16,7 +16,6 @@ use language::{
};
use project::{FormatTrigger, Item as _, Project, ProjectPath};
use rpc::proto::{self, update_view};
use settings::Settings;
use smallvec::SmallVec;
use std::{
borrow::Cow,
@@ -721,17 +720,6 @@ impl Item for Editor {
})
}
fn git_diff_recalc(
&mut self,
_project: ModelHandle<Project>,
cx: &mut ViewContext<Self>,
) -> Task<Result<()>> {
self.buffer().update(cx, |multibuffer, cx| {
multibuffer.git_diff_recalc(cx);
});
Task::ready(Ok(()))
}
fn to_item_events(event: &Self::Event) -> SmallVec<[ItemEvent; 2]> {
let mut result = SmallVec::new();
match event {
@@ -1116,7 +1104,7 @@ impl View for CursorPosition {
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
if let Some(position) = self.position {
let theme = &cx.global::<Settings>().theme.workspace.status_bar;
let theme = &theme::current(cx).workspace.status_bar;
let mut text = format!(
"{}{FILE_ROW_COLUMN_DELIMITER}{}",
position.row + 1,
@@ -1232,27 +1220,27 @@ mod tests {
}
fn as_local(&self) -> Option<&dyn language::LocalFile> {
todo!()
unimplemented!()
}
fn mtime(&self) -> SystemTime {
todo!()
unimplemented!()
}
fn file_name<'a>(&'a self, _: &'a gpui::AppContext) -> &'a std::ffi::OsStr {
todo!()
unimplemented!()
}
fn is_deleted(&self) -> bool {
todo!()
unimplemented!()
}
fn as_any(&self) -> &dyn std::any::Any {
todo!()
unimplemented!()
}
fn to_proto(&self) -> rpc::proto::File {
todo!()
unimplemented!()
}
}
}

View File

@@ -1,10 +1,8 @@
use std::ops::Range;
use crate::{Anchor, DisplayPoint, Editor, EditorSnapshot, SelectPhase};
use gpui::{Task, ViewContext};
use language::{Bias, ToOffset};
use project::LocationLink;
use settings::Settings;
use std::ops::Range;
use util::TryFutureExt;
#[derive(Debug, Default)]
@@ -211,7 +209,7 @@ pub fn show_link_definition(
});
// Highlight symbol using theme link definition highlight style
let style = cx.global::<Settings>().theme.editor.link_definition;
let style = theme::current(cx).editor.link_definition;
this.highlight_text::<LinkGoToDefinitionState>(
vec![highlight_range],
style,
@@ -297,6 +295,8 @@ fn go_to_fetched_definition_of_kind(
#[cfg(test)]
mod tests {
use super::*;
use crate::{editor_tests::init_test, test::editor_lsp_test_context::EditorLspTestContext};
use futures::StreamExt;
use gpui::{
platform::{self, Modifiers, ModifiersChangedEvent},
@@ -305,12 +305,10 @@ mod tests {
use indoc::indoc;
use lsp::request::{GotoDefinition, GotoTypeDefinition};
use crate::test::editor_lsp_test_context::EditorLspTestContext;
use super::*;
#[gpui::test]
async fn test_link_go_to_type_definition(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorLspTestContext::new_rust(
lsp::ServerCapabilities {
hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
@@ -417,6 +415,8 @@ mod tests {
#[gpui::test]
async fn test_link_go_to_definition(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorLspTestContext::new_rust(
lsp::ServerCapabilities {
hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),

View File

@@ -57,13 +57,14 @@ pub fn deploy_context_menu(
#[cfg(test)]
mod tests {
use crate::test::editor_lsp_test_context::EditorLspTestContext;
use super::*;
use crate::{editor_tests::init_test, test::editor_lsp_test_context::EditorLspTestContext};
use indoc::indoc;
#[gpui::test]
async fn test_mouse_context_menu(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorLspTestContext::new_rust(
lsp::ServerCapabilities {
hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),

View File

@@ -193,6 +193,44 @@ pub fn next_subword_end(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPo
})
}
pub fn start_of_paragraph(map: &DisplaySnapshot, display_point: DisplayPoint) -> DisplayPoint {
let point = display_point.to_point(map);
if point.row == 0 {
return map.max_point();
}
let mut found_non_blank_line = false;
for row in (0..point.row + 1).rev() {
let blank = map.buffer_snapshot.is_line_blank(row);
if found_non_blank_line && blank {
return Point::new(row, 0).to_display_point(map);
}
found_non_blank_line |= !blank;
}
DisplayPoint::zero()
}
pub fn end_of_paragraph(map: &DisplaySnapshot, display_point: DisplayPoint) -> DisplayPoint {
let point = display_point.to_point(map);
if point.row == map.max_buffer_row() {
return DisplayPoint::zero();
}
let mut found_non_blank_line = false;
for row in point.row..map.max_buffer_row() + 1 {
let blank = map.buffer_snapshot.is_line_blank(row);
if found_non_blank_line && blank {
return Point::new(row, 0).to_display_point(map);
}
found_non_blank_line |= !blank;
}
map.max_point()
}
/// Scans for a boundary preceding the given start point `from` until a boundary is found, indicated by the
/// given predicate returning true. The predicate is called with the character to the left and right
/// of the candidate boundary location, and will be called with `\n` characters indicating the start
@@ -369,11 +407,12 @@ pub fn split_display_range_by_lines(
mod tests {
use super::*;
use crate::{test::marked_display_snapshot, Buffer, DisplayMap, ExcerptRange, MultiBuffer};
use settings::Settings;
use settings::SettingsStore;
#[gpui::test]
fn test_previous_word_start(cx: &mut gpui::AppContext) {
cx.set_global(Settings::test(cx));
init_test(cx);
fn assert(marked_text: &str, cx: &mut gpui::AppContext) {
let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
assert_eq!(
@@ -400,7 +439,8 @@ mod tests {
#[gpui::test]
fn test_previous_subword_start(cx: &mut gpui::AppContext) {
cx.set_global(Settings::test(cx));
init_test(cx);
fn assert(marked_text: &str, cx: &mut gpui::AppContext) {
let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
assert_eq!(
@@ -434,7 +474,8 @@ mod tests {
#[gpui::test]
fn test_find_preceding_boundary(cx: &mut gpui::AppContext) {
cx.set_global(Settings::test(cx));
init_test(cx);
fn assert(
marked_text: &str,
cx: &mut gpui::AppContext,
@@ -466,7 +507,8 @@ mod tests {
#[gpui::test]
fn test_next_word_end(cx: &mut gpui::AppContext) {
cx.set_global(Settings::test(cx));
init_test(cx);
fn assert(marked_text: &str, cx: &mut gpui::AppContext) {
let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
assert_eq!(
@@ -490,7 +532,8 @@ mod tests {
#[gpui::test]
fn test_next_subword_end(cx: &mut gpui::AppContext) {
cx.set_global(Settings::test(cx));
init_test(cx);
fn assert(marked_text: &str, cx: &mut gpui::AppContext) {
let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
assert_eq!(
@@ -523,7 +566,8 @@ mod tests {
#[gpui::test]
fn test_find_boundary(cx: &mut gpui::AppContext) {
cx.set_global(Settings::test(cx));
init_test(cx);
fn assert(
marked_text: &str,
cx: &mut gpui::AppContext,
@@ -555,7 +599,8 @@ mod tests {
#[gpui::test]
fn test_surrounding_word(cx: &mut gpui::AppContext) {
cx.set_global(Settings::test(cx));
init_test(cx);
fn assert(marked_text: &str, cx: &mut gpui::AppContext) {
let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
assert_eq!(
@@ -576,7 +621,8 @@ mod tests {
#[gpui::test]
fn test_move_up_and_down_with_excerpts(cx: &mut gpui::AppContext) {
cx.set_global(Settings::test(cx));
init_test(cx);
let family_id = cx
.font_cache()
.load_family(&["Helvetica"], &Default::default())
@@ -691,4 +737,11 @@ mod tests {
(DisplayPoint::new(7, 2), SelectionGoal::Column(2)),
);
}
fn init_test(cx: &mut gpui::AppContext) {
cx.set_global(SettingsStore::test(cx));
theme::init((), cx);
language::init(cx);
crate::init(cx);
}
}

View File

@@ -9,7 +9,9 @@ use git::diff::DiffHunk;
use gpui::{AppContext, Entity, ModelContext, ModelHandle, Task};
pub use language::Completion;
use language::{
char_kind, AutoindentMode, Buffer, BufferChunks, BufferSnapshot, CharKind, Chunk, CursorShape,
char_kind,
language_settings::{language_settings, LanguageSettings},
AutoindentMode, Buffer, BufferChunks, BufferSnapshot, CharKind, Chunk, CursorShape,
DiagnosticEntry, File, IndentSize, Language, LanguageScope, OffsetRangeExt, OffsetUtf16,
Outline, OutlineItem, Point, PointUtf16, Selection, TextDimension, ToOffset as _,
ToOffsetUtf16 as _, ToPoint as _, ToPointUtf16 as _, TransactionId, Unclipped,
@@ -64,6 +66,7 @@ pub enum Event {
},
Edited,
Reloaded,
DiffBaseChanged,
LanguageChanged,
Reparsed,
Saved,
@@ -341,17 +344,6 @@ impl MultiBuffer {
self.read(cx).symbols_containing(offset, theme)
}
pub fn git_diff_recalc(&mut self, cx: &mut ModelContext<Self>) {
let buffers = self.buffers.borrow();
for buffer_state in buffers.values() {
if buffer_state.buffer.read(cx).needs_git_diff_recalc() {
buffer_state
.buffer
.update(cx, |buffer, cx| buffer.git_diff_recalc(cx))
}
}
}
pub fn edit<I, S, T>(
&mut self,
edits: I,
@@ -1138,6 +1130,10 @@ impl MultiBuffer {
let mut result = Vec::new();
let mut cursor = snapshot.excerpts.cursor::<usize>();
cursor.seek(&start, Bias::Right, &());
if cursor.item().is_none() {
cursor.prev(&());
}
while let Some(excerpt) = cursor.item() {
if *cursor.start() > end {
break;
@@ -1306,6 +1302,7 @@ impl MultiBuffer {
language::Event::Saved => Event::Saved,
language::Event::FileHandleChanged => Event::FileHandleChanged,
language::Event::Reloaded => Event::Reloaded,
language::Event::DiffBaseChanged => Event::DiffBaseChanged,
language::Event::LanguageChanged => Event::LanguageChanged,
language::Event::Reparsed => Event::Reparsed,
language::Event::DiagnosticsUpdated => Event::DiagnosticsUpdated,
@@ -1375,6 +1372,15 @@ impl MultiBuffer {
.and_then(|(buffer, offset)| buffer.read(cx).language_at(offset))
}
pub fn settings_at<'a, T: ToOffset>(
&self,
point: T,
cx: &'a AppContext,
) -> &'a LanguageSettings {
let language = self.language_at(point, cx);
language_settings(language.map(|l| l.name()).as_deref(), cx)
}
pub fn for_each_buffer(&self, mut f: impl FnMut(&ModelHandle<Buffer>)) {
self.buffers
.borrow()
@@ -1535,6 +1541,13 @@ impl MultiBuffer {
cx.add_model(|cx| Self::singleton(buffer, cx))
}
pub fn build_from_buffer(
buffer: ModelHandle<Buffer>,
cx: &mut gpui::AppContext,
) -> ModelHandle<Self> {
cx.add_model(|cx| Self::singleton(buffer, cx))
}
pub fn build_random(rng: &mut impl rand::Rng, cx: &mut gpui::AppContext) -> ModelHandle<Self> {
cx.add_model(|cx| {
let mut multibuffer = MultiBuffer::new(0);
@@ -2767,6 +2780,16 @@ impl MultiBufferSnapshot {
.and_then(|(buffer, offset)| buffer.language_at(offset))
}
pub fn settings_at<'a, T: ToOffset>(
&'a self,
point: T,
cx: &'a AppContext,
) -> &'a LanguageSettings {
self.point_to_buffer_offset(point)
.map(|(buffer, offset)| buffer.settings_at(offset, cx))
.unwrap_or_else(|| language_settings(None, cx))
}
pub fn language_scope_at<'a, T: ToOffset>(&'a self, point: T) -> Option<LanguageScope> {
self.point_to_buffer_offset(point)
.and_then(|(buffer, offset)| buffer.language_scope_at(offset))
@@ -2820,20 +2843,24 @@ impl MultiBufferSnapshot {
})
}
pub fn git_diff_hunks_in_range<'a>(
pub fn has_git_diffs(&self) -> bool {
for excerpt in self.excerpts.iter() {
if !excerpt.buffer.git_diff.is_empty() {
return true;
}
}
false
}
pub fn git_diff_hunks_in_range_rev<'a>(
&'a self,
row_range: Range<u32>,
reversed: bool,
) -> impl 'a + Iterator<Item = DiffHunk<u32>> {
let mut cursor = self.excerpts.cursor::<Point>();
if reversed {
cursor.seek(&Point::new(row_range.end, 0), Bias::Left, &());
if cursor.item().is_none() {
cursor.prev(&());
}
} else {
cursor.seek(&Point::new(row_range.start, 0), Bias::Right, &());
cursor.seek(&Point::new(row_range.end, 0), Bias::Left, &());
if cursor.item().is_none() {
cursor.prev(&());
}
std::iter::from_fn(move || {
@@ -2863,7 +2890,7 @@ impl MultiBufferSnapshot {
let buffer_hunks = excerpt
.buffer
.git_diff_hunks_intersecting_range(buffer_start..buffer_end, reversed)
.git_diff_hunks_intersecting_range_rev(buffer_start..buffer_end)
.filter_map(move |hunk| {
let start = multibuffer_start.row
+ hunk
@@ -2883,12 +2910,70 @@ impl MultiBufferSnapshot {
})
});
if reversed {
cursor.prev(&());
} else {
cursor.next(&());
cursor.prev(&());
Some(buffer_hunks)
})
.flatten()
}
pub fn git_diff_hunks_in_range<'a>(
&'a self,
row_range: Range<u32>,
) -> impl 'a + Iterator<Item = DiffHunk<u32>> {
let mut cursor = self.excerpts.cursor::<Point>();
cursor.seek(&Point::new(row_range.start, 0), Bias::Right, &());
std::iter::from_fn(move || {
let excerpt = cursor.item()?;
let multibuffer_start = *cursor.start();
let multibuffer_end = multibuffer_start + excerpt.text_summary.lines;
if multibuffer_start.row >= row_range.end {
return None;
}
let mut buffer_start = excerpt.range.context.start;
let mut buffer_end = excerpt.range.context.end;
let excerpt_start_point = buffer_start.to_point(&excerpt.buffer);
let excerpt_end_point = excerpt_start_point + excerpt.text_summary.lines;
if row_range.start > multibuffer_start.row {
let buffer_start_point =
excerpt_start_point + Point::new(row_range.start - multibuffer_start.row, 0);
buffer_start = excerpt.buffer.anchor_before(buffer_start_point);
}
if row_range.end < multibuffer_end.row {
let buffer_end_point =
excerpt_start_point + Point::new(row_range.end - multibuffer_start.row, 0);
buffer_end = excerpt.buffer.anchor_before(buffer_end_point);
}
let buffer_hunks = excerpt
.buffer
.git_diff_hunks_intersecting_range(buffer_start..buffer_end)
.filter_map(move |hunk| {
let start = multibuffer_start.row
+ hunk
.buffer_range
.start
.saturating_sub(excerpt_start_point.row);
let end = multibuffer_start.row
+ hunk
.buffer_range
.end
.min(excerpt_end_point.row + 1)
.saturating_sub(excerpt_start_point.row);
Some(DiffHunk {
buffer_range: start..end,
diff_base_byte_range: hunk.diff_base_byte_range.clone(),
})
});
cursor.next(&());
Some(buffer_hunks)
})
.flatten()
@@ -3783,15 +3868,17 @@ where
#[cfg(test)]
mod tests {
use crate::editor_tests::init_test;
use super::*;
use futures::StreamExt;
use gpui::{AppContext, TestAppContext};
use language::{Buffer, Rope};
use project::{FakeFs, Project};
use rand::prelude::*;
use settings::Settings;
use settings::SettingsStore;
use std::{env, rc::Rc};
use unindent::Unindent;
use util::test::sample_text;
#[gpui::test]
@@ -4478,73 +4565,85 @@ mod tests {
#[gpui::test]
async fn test_diff_hunks_in_range(cx: &mut TestAppContext) {
use git::diff::DiffHunkStatus;
init_test(cx, |_| {});
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, [], cx).await;
// buffer has two modified hunks with two rows each
let buffer_1 = cx.add_model(|cx| {
let mut buffer = Buffer::new(
0,
"
1.zero
1.ONE
1.TWO
1.three
1.FOUR
1.FIVE
1.six
"
.unindent(),
cx,
);
let buffer_1 = project
.update(cx, |project, cx| {
project.create_buffer(
"
1.zero
1.ONE
1.TWO
1.three
1.FOUR
1.FIVE
1.six
"
.unindent()
.as_str(),
None,
cx,
)
})
.unwrap();
buffer_1.update(cx, |buffer, cx| {
buffer.set_diff_base(
Some(
"
1.zero
1.one
1.two
1.three
1.four
1.five
1.six
"
1.zero
1.one
1.two
1.three
1.four
1.five
1.six
"
.unindent(),
),
cx,
);
buffer
});
// buffer has a deletion hunk and an insertion hunk
let buffer_2 = cx.add_model(|cx| {
let mut buffer = Buffer::new(
0,
"
2.zero
2.one
2.two
2.three
2.four
2.five
2.six
"
.unindent(),
cx,
);
let buffer_2 = project
.update(cx, |project, cx| {
project.create_buffer(
"
2.zero
2.one
2.two
2.three
2.four
2.five
2.six
"
.unindent()
.as_str(),
None,
cx,
)
})
.unwrap();
buffer_2.update(cx, |buffer, cx| {
buffer.set_diff_base(
Some(
"
2.zero
2.one
2.one-and-a-half
2.two
2.three
2.four
2.six
"
2.zero
2.one
2.one-and-a-half
2.two
2.three
2.four
2.six
"
.unindent(),
),
cx,
);
buffer
});
cx.foreground().run_until_parked();
@@ -4627,7 +4726,7 @@ mod tests {
assert_eq!(
snapshot
.git_diff_hunks_in_range(0..12, false)
.git_diff_hunks_in_range(0..12)
.map(|hunk| (hunk.status(), hunk.buffer_range))
.collect::<Vec<_>>(),
&expected,
@@ -4635,7 +4734,7 @@ mod tests {
assert_eq!(
snapshot
.git_diff_hunks_in_range(0..12, true)
.git_diff_hunks_in_range_rev(0..12)
.map(|hunk| (hunk.status(), hunk.buffer_range))
.collect::<Vec<_>>(),
expected
@@ -4990,16 +5089,19 @@ mod tests {
.read(cx)
.range_to_buffer_ranges(start_ix..end_ix, cx);
let excerpted_buffers_text = excerpted_buffer_ranges
.into_iter()
.iter()
.map(|(buffer, buffer_range)| {
buffer
.read(cx)
.text_for_range(buffer_range)
.text_for_range(buffer_range.clone())
.collect::<String>()
})
.collect::<Vec<_>>()
.join("\n");
assert_eq!(excerpted_buffers_text, text_for_range);
if !expected_excerpts.is_empty() {
assert!(!excerpted_buffer_ranges.is_empty());
}
let expected_summary = TextSummary::from(&expected_text[start_ix..end_ix]);
assert_eq!(
@@ -5066,7 +5168,8 @@ mod tests {
#[gpui::test]
fn test_history(cx: &mut AppContext) {
cx.set_global(Settings::test(cx));
cx.set_global(SettingsStore::test(cx));
let buffer_1 = cx.add_model(|cx| Buffer::new(0, "1234", cx));
let buffer_2 = cx.add_model(|cx| Buffer::new(0, "5678", cx));
let multibuffer = cx.add_model(|_| MultiBuffer::new(0));

View File

@@ -34,13 +34,17 @@ impl<'a> EditorLspTestContext<'a> {
) -> EditorLspTestContext<'a> {
use json::json;
let app_state = cx.update(AppState::test);
cx.update(|cx| {
theme::init((), cx);
language::init(cx);
crate::init(cx);
pane::init(cx);
Project::init_settings(cx);
workspace::init_settings(cx);
});
let app_state = cx.update(AppState::test);
let file_name = format!(
"file.{}",
language

View File

@@ -1,19 +1,17 @@
use crate::{
display_map::ToDisplayPoint, AnchorRangeExt, Autoscroll, DisplayPoint, Editor, MultiBuffer,
};
use futures::Future;
use gpui::{
keymap_matcher::Keystroke, AppContext, ContextHandle, ModelContext, ViewContext, ViewHandle,
};
use indoc::indoc;
use language::{Buffer, BufferSnapshot};
use project::{FakeFs, Project};
use std::{
any::TypeId,
ops::{Deref, DerefMut, Range},
};
use futures::Future;
use indoc::indoc;
use crate::{
display_map::ToDisplayPoint, AnchorRangeExt, Autoscroll, DisplayPoint, Editor, MultiBuffer,
};
use gpui::{
keymap_matcher::Keystroke, AppContext, ContextHandle, ModelContext, ViewContext, ViewHandle,
};
use language::{Buffer, BufferSnapshot};
use settings::Settings;
use util::{
assert_set_eq,
test::{generate_marked_text, marked_text_ranges},
@@ -28,17 +26,17 @@ pub struct EditorTestContext<'a> {
}
impl<'a> EditorTestContext<'a> {
pub fn new(cx: &'a mut gpui::TestAppContext) -> EditorTestContext<'a> {
pub async fn new(cx: &'a mut gpui::TestAppContext) -> EditorTestContext<'a> {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, [], cx).await;
let buffer = project
.update(cx, |project, cx| project.create_buffer("", None, cx))
.unwrap();
let (window_id, editor) = cx.update(|cx| {
cx.set_global(Settings::test(cx));
crate::init(cx);
let (window_id, editor) = cx.add_window(Default::default(), |cx| {
cx.add_window(Default::default(), |cx| {
cx.focus_self();
build_editor(MultiBuffer::build_simple("", cx), cx)
});
(window_id, editor)
build_editor(MultiBuffer::build_from_buffer(buffer, cx), cx)
})
});
Self {
@@ -212,6 +210,7 @@ impl<'a> EditorTestContext<'a> {
self.assert_selections(expected_selections, marked_text.to_string())
}
#[track_caller]
pub fn assert_editor_background_highlights<Tag: 'static>(&mut self, marked_text: &str) {
let expected_ranges = self.ranges(marked_text);
let actual_ranges: Vec<Range<usize>> = self.update_editor(|editor, cx| {
@@ -228,6 +227,7 @@ impl<'a> EditorTestContext<'a> {
assert_set_eq!(actual_ranges, expected_ranges);
}
#[track_caller]
pub fn assert_editor_text_highlights<Tag: ?Sized + 'static>(&mut self, marked_text: &str) {
let expected_ranges = self.ranges(marked_text);
let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx));
@@ -241,12 +241,14 @@ impl<'a> EditorTestContext<'a> {
assert_set_eq!(actual_ranges, expected_ranges);
}
#[track_caller]
pub fn assert_editor_selections(&mut self, expected_selections: Vec<Range<usize>>) {
let expected_marked_text =
generate_marked_text(&self.buffer_text(), &expected_selections, true);
self.assert_selections(expected_selections, expected_marked_text)
}
#[track_caller]
fn assert_selections(
&mut self,
expected_selections: Vec<Range<usize>>,

View File

@@ -16,6 +16,7 @@ editor = { path = "../editor" }
language = { path = "../language" }
gpui = { path = "../gpui" }
project = { path = "../project" }
regex.workspace = true
search = { path = "../search" }
settings = { path = "../settings" }
theme = { path = "../theme" }
@@ -27,7 +28,7 @@ futures.workspace = true
anyhow.workspace = true
smallvec.workspace = true
human_bytes = "0.4.1"
isahc = "1.7"
isahc.workspace = true
lazy_static.workspace = true
postage.workspace = true
serde.workspace = true
@@ -35,3 +36,6 @@ serde_derive.workspace = true
sysinfo = "0.27.1"
tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" }
urlencoding = "2.1.2"
[dev-dependencies]
editor = { path = "../editor", features = ["test-support"] }

View File

@@ -3,7 +3,6 @@ use gpui::{
platform::{CursorStyle, MouseButton},
Entity, View, ViewContext, WeakViewHandle,
};
use settings::Settings;
use workspace::{item::ItemHandle, StatusItemView, Workspace};
use crate::feedback_editor::{FeedbackEditor, GiveFeedback};
@@ -33,15 +32,15 @@ impl View for DeployFeedbackButton {
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
let active = self.active;
let theme = cx.global::<Settings>().theme.clone();
let theme = theme::current(cx).clone();
Stack::new()
.with_child(
MouseEventHandler::<Self, Self>::new(0, cx, |state, _| {
let style = &theme
.workspace
.status_bar
.sidebar_buttons
.item
.panel_buttons
.button
.style_for(state, active);
Svg::new("icons/feedback_16.svg")

View File

@@ -14,6 +14,7 @@ use isahc::Request;
use language::Buffer;
use postage::prelude::Stream;
use project::Project;
use regex::Regex;
use serde::Serialize;
use smallvec::SmallVec;
use std::{
@@ -46,6 +47,7 @@ pub fn init(cx: &mut AppContext) {
#[derive(Serialize)]
struct FeedbackRequestBody<'a> {
feedback_text: &'a str,
email: Option<String>,
metrics_id: Option<Arc<str>>,
installation_id: Option<Arc<str>>,
system_specs: SystemSpecs,
@@ -157,8 +159,18 @@ impl FeedbackEditor {
let is_staff = telemetry.is_staff();
let http_client = zed_client.http_client();
let re = Regex::new(r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b").unwrap();
let emails: Vec<&str> = re
.captures_iter(feedback_text)
.map(|capture| capture.get(0).unwrap().as_str())
.collect();
let email = emails.first().map(|e| e.to_string());
let request = FeedbackRequestBody {
feedback_text: &feedback_text,
email,
metrics_id,
installation_id,
system_specs,

View File

@@ -3,7 +3,6 @@ use gpui::{
platform::{CursorStyle, MouseButton},
AnyElement, Element, Entity, View, ViewContext, ViewHandle,
};
use settings::Settings;
use workspace::{item::ItemHandle, ToolbarItemLocation, ToolbarItemView};
use crate::{feedback_editor::FeedbackEditor, open_zed_community_repo, OpenZedCommunityRepo};
@@ -30,12 +29,12 @@ impl View for FeedbackInfoText {
}
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
let theme = cx.global::<Settings>().theme.clone();
let theme = theme::current(cx).clone();
Flex::row()
.with_child(
Text::new(
"We read whatever you submit here. For issues and discussions, visit the ",
"Share your feedback. Include your email for replies. For issues and discussions, visit the ",
theme.feedback.info_text_default.text.clone(),
)
.with_soft_wrap(false)
@@ -61,7 +60,7 @@ impl View for FeedbackInfoText {
}),
)
.with_child(
Text::new(" on GitHub.", theme.feedback.info_text_default.text.clone())
Text::new(".", theme.feedback.info_text_default.text.clone())
.with_soft_wrap(false)
.aligned(),
)

View File

@@ -5,7 +5,6 @@ use gpui::{
platform::{CursorStyle, MouseButton},
AnyElement, AppContext, Element, Entity, Task, View, ViewContext, ViewHandle,
};
use settings::Settings;
use workspace::{item::ItemHandle, ToolbarItemLocation, ToolbarItemView};
pub fn init(cx: &mut AppContext) {
@@ -46,7 +45,7 @@ impl View for SubmitFeedbackButton {
}
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
let theme = cx.global::<Settings>().theme.clone();
let theme = theme::current(cx).clone();
enum SubmitFeedbackButton {}
MouseEventHandler::<SubmitFeedbackButton, Self>::new(0, cx, |state, _| {
let style = theme.feedback.submit_button.style_for(state, false);

View File

@@ -23,8 +23,12 @@ workspace = { path = "../workspace" }
postage.workspace = true
[dev-dependencies]
editor = { path = "../editor", features = ["test-support"] }
gpui = { path = "../gpui", features = ["test-support"] }
serde_json.workspace = true
language = { path = "../language", features = ["test-support"] }
workspace = { path = "../workspace", features = ["test-support"] }
theme = { path = "../theme", features = ["test-support"] }
serde_json.workspace = true
ctor.workspace = true
env_logger.workspace = true

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
use std::ops::Range;
use std::{iter, ops::Range};
use sum_tree::SumTree;
use text::{Anchor, BufferSnapshot, OffsetRangeExt, Point};
@@ -71,22 +71,66 @@ impl BufferDiff {
}
}
pub fn is_empty(&self) -> bool {
self.tree.is_empty()
}
pub fn hunks_in_row_range<'a>(
&'a self,
range: Range<u32>,
buffer: &'a BufferSnapshot,
reversed: bool,
) -> impl 'a + Iterator<Item = DiffHunk<u32>> {
let start = buffer.anchor_before(Point::new(range.start, 0));
let end = buffer.anchor_after(Point::new(range.end, 0));
self.hunks_intersecting_range(start..end, buffer, reversed)
self.hunks_intersecting_range(start..end, buffer)
}
pub fn hunks_intersecting_range<'a>(
&'a self,
range: Range<Anchor>,
buffer: &'a BufferSnapshot,
reversed: bool,
) -> impl 'a + Iterator<Item = DiffHunk<u32>> {
let mut cursor = self.tree.filter::<_, DiffHunkSummary>(move |summary| {
let before_start = summary.buffer_range.end.cmp(&range.start, buffer).is_lt();
let after_end = summary.buffer_range.start.cmp(&range.end, buffer).is_gt();
!before_start && !after_end
});
let anchor_iter = std::iter::from_fn(move || {
cursor.next(buffer);
cursor.item()
})
.flat_map(move |hunk| {
[
(&hunk.buffer_range.start, hunk.diff_base_byte_range.start),
(&hunk.buffer_range.end, hunk.diff_base_byte_range.end),
]
.into_iter()
});
let mut summaries = buffer.summaries_for_anchors_with_payload::<Point, _, _>(anchor_iter);
iter::from_fn(move || {
let (start_point, start_base) = summaries.next()?;
let (end_point, end_base) = summaries.next()?;
let end_row = if end_point.column > 0 {
end_point.row + 1
} else {
end_point.row
};
Some(DiffHunk {
buffer_range: start_point.row..end_row,
diff_base_byte_range: start_base..end_base,
})
})
}
pub fn hunks_intersecting_range_rev<'a>(
&'a self,
range: Range<Anchor>,
buffer: &'a BufferSnapshot,
) -> impl 'a + Iterator<Item = DiffHunk<u32>> {
let mut cursor = self.tree.filter::<_, DiffHunkSummary>(move |summary| {
let before_start = summary.buffer_range.end.cmp(&range.start, buffer).is_lt();
@@ -95,14 +139,9 @@ impl BufferDiff {
});
std::iter::from_fn(move || {
if reversed {
cursor.prev(buffer);
} else {
cursor.next(buffer);
}
cursor.prev(buffer);
let hunk = cursor.item()?;
let range = hunk.buffer_range.to_point(buffer);
let end_row = if range.end.column > 0 {
range.end.row + 1
@@ -122,13 +161,6 @@ impl BufferDiff {
self.tree = SumTree::new();
}
pub fn needs_update(&self, buffer: &text::BufferSnapshot) -> bool {
match &self.last_buffer_version {
Some(last) => buffer.version().changed_since(last),
None => true,
}
}
pub async fn update(&mut self, diff_base: &str, buffer: &text::BufferSnapshot) {
let mut tree = SumTree::new();
@@ -151,7 +183,7 @@ impl BufferDiff {
fn hunks<'a>(&'a self, text: &'a BufferSnapshot) -> impl 'a + Iterator<Item = DiffHunk<u32>> {
let start = text.anchor_before(Point::new(0, 0));
let end = text.anchor_after(Point::new(u32::MAX, u32::MAX));
self.hunks_intersecting_range(start..end, text, false)
self.hunks_intersecting_range(start..end, text)
}
fn diff<'a>(head: &'a str, current: &'a str) -> Option<GitPatch<'a>> {
@@ -279,6 +311,8 @@ pub fn assert_hunks<Iter>(
#[cfg(test)]
mod tests {
use std::assert_eq;
use super::*;
use text::Buffer;
use unindent::Unindent as _;
@@ -365,7 +399,7 @@ mod tests {
assert_eq!(diff.hunks(&buffer).count(), 8);
assert_hunks(
diff.hunks_in_row_range(7..12, &buffer, false),
diff.hunks_in_row_range(7..12, &buffer),
&buffer,
&diff_base,
&[

View File

@@ -16,4 +16,8 @@ settings = { path = "../settings" }
text = { path = "../text" }
workspace = { path = "../workspace" }
postage.workspace = true
theme = { path = "../theme" }
util = { path = "../util" }
[dev-dependencies]
editor = { path = "../editor", features = ["test-support"] }

View File

@@ -6,7 +6,6 @@ use gpui::{
View, ViewContext, ViewHandle,
};
use menu::{Cancel, Confirm};
use settings::Settings;
use text::{Bias, Point};
use util::paths::FILE_ROW_COLUMN_DELIMITER;
use workspace::{Modal, Workspace};
@@ -151,7 +150,7 @@ impl View for GoToLine {
}
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
let theme = &cx.global::<Settings>().theme.picker;
let theme = &theme::current(cx).picker;
let label = format!(
"{}{FILE_ROW_COLUMN_DELIMITER}{} of {} lines",

View File

@@ -72,7 +72,7 @@ cocoa = "0.24"
core-foundation = { version = "0.9.3", features = ["with-uuid"] }
core-graphics = "0.22.3"
core-text = "19.2"
font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "8eaf7a918eafa28b0a37dc759e2e0e7683fa24f1" }
font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "b2f77d56f450338aa4f7dd2f0197d8c9acb0cf18" }
foreign-types = "0.3"
log.workspace = true
metal = "0.21.0"

View File

@@ -1174,7 +1174,7 @@ impl AppContext {
this.notify_global(type_id);
result
} else {
panic!("No global added for {}", std::any::type_name::<T>());
panic!("no global added for {}", std::any::type_name::<T>());
}
}
@@ -1182,6 +1182,15 @@ impl AppContext {
self.globals.clear();
}
pub fn remove_global<T: 'static>(&mut self) -> T {
*self
.globals
.remove(&TypeId::of::<T>())
.unwrap_or_else(|| panic!("no global added for {}", std::any::type_name::<T>()))
.downcast()
.unwrap()
}
pub fn add_model<T, F>(&mut self, build_model: F) -> ModelHandle<T>
where
T: Entity,
@@ -1451,27 +1460,13 @@ impl AppContext {
self.views_metadata.remove(&(window_id, view_id));
let mut view = self.views.remove(&(window_id, view_id)).unwrap();
view.release(self);
let change_focus_to = self.windows.get_mut(&window_id).and_then(|window| {
if let Some(window) = self.windows.get_mut(&window_id) {
window.parents.remove(&view_id);
window
.invalidation
.get_or_insert_with(Default::default)
.removed
.push(view_id);
if window.focused_view_id == Some(view_id) {
Some(window.root_view().id())
} else {
None
}
});
if let Some(view_id) = change_focus_to {
self.pending_effects
.push_back(Effect::Focus(FocusEffect::View {
window_id,
view_id: Some(view_id),
is_forced: false,
}));
}
self.pending_effects
@@ -1708,8 +1703,69 @@ impl AppContext {
if let Some(invalidation) = invalidation {
let appearance = cx.window.platform_window.appearance();
cx.invalidate(invalidation, appearance);
if cx.layout(refreshing).log_err().is_some() {
if let Some(old_parents) = cx.layout(refreshing).log_err() {
updated_windows.insert(window_id);
if let Some(focused_view_id) = cx.focused_view_id() {
let old_ancestors = std::iter::successors(
Some(focused_view_id),
|&view_id| old_parents.get(&view_id).copied(),
)
.collect::<HashSet<_>>();
let new_ancestors =
cx.ancestors(focused_view_id).collect::<HashSet<_>>();
// Notify the old ancestors of the focused view when they don't contain it anymore.
for old_ancestor in old_ancestors.iter().copied() {
if !new_ancestors.contains(&old_ancestor) {
if let Some(mut view) =
cx.views.remove(&(window_id, old_ancestor))
{
view.focus_out(
focused_view_id,
cx,
old_ancestor,
);
cx.views
.insert((window_id, old_ancestor), view);
}
}
}
// Notify the new ancestors of the focused view if they contain it now.
for new_ancestor in new_ancestors.iter().copied() {
if !old_ancestors.contains(&new_ancestor) {
if let Some(mut view) =
cx.views.remove(&(window_id, new_ancestor))
{
view.focus_in(
focused_view_id,
cx,
new_ancestor,
);
cx.views
.insert((window_id, new_ancestor), view);
}
}
}
// When the previously-focused view has been dropped and
// there isn't any pending focus, focus the root view.
let root_view_id = cx.window.root_view().id();
if focused_view_id != root_view_id
&& !cx.views.contains_key(&(window_id, focused_view_id))
&& !focus_effects.contains_key(&window_id)
{
focus_effects.insert(
window_id,
FocusEffect::View {
window_id,
view_id: Some(root_view_id),
is_forced: false,
},
);
}
}
}
}
});
@@ -1886,9 +1942,27 @@ impl AppContext {
fn handle_focus_effect(&mut self, effect: FocusEffect) {
let window_id = effect.window_id();
self.update_window(window_id, |cx| {
// Ensure the newly-focused view still exists, otherwise focus
// the root view instead.
let focused_id = match effect {
FocusEffect::View { view_id, .. } => view_id,
FocusEffect::ViewParent { view_id, .. } => cx.ancestors(view_id).skip(1).next(),
FocusEffect::View { view_id, .. } => {
if let Some(view_id) = view_id {
if cx.views.contains_key(&(window_id, view_id)) {
Some(view_id)
} else {
Some(cx.root_view().id())
}
} else {
None
}
}
FocusEffect::ViewParent { view_id, .. } => Some(
cx.window
.parents
.get(&view_id)
.copied()
.unwrap_or(cx.root_view().id()),
),
};
let focus_changed = cx.window.focused_view_id != focused_id;
@@ -3316,15 +3390,14 @@ impl<'a, 'b, 'c, V: View> LayoutContext<'a, 'b, 'c, V> {
self.keystroke_matcher
.bindings_for_action_type(action.as_any().type_id())
.find_map(|b| {
handler_depth
.map(|highest_handler| {
if (0..=highest_handler).any(|depth| b.match_context(&contexts[depth..])) {
Some(b.keystrokes().into())
} else {
None
}
})
.flatten()
let highest_handler = handler_depth?;
if action.eq(b.action())
&& (0..=highest_handler).any(|depth| b.match_context(&contexts[depth..]))
{
Some(b.keystrokes().into())
} else {
None
}
})
}
@@ -3793,6 +3866,12 @@ impl<T> PartialEq for ViewHandle<T> {
}
}
impl<T> PartialEq<AnyViewHandle> for ViewHandle<T> {
fn eq(&self, other: &AnyViewHandle) -> bool {
self.window_id == other.window_id && self.view_id == other.view_id
}
}
impl<T> PartialEq<WeakViewHandle<T>> for ViewHandle<T> {
fn eq(&self, other: &WeakViewHandle<T>) -> bool {
self.window_id == other.window_id && self.view_id == other.view_id
@@ -3943,6 +4022,12 @@ impl Clone for AnyViewHandle {
}
}
impl PartialEq for AnyViewHandle {
fn eq(&self, other: &Self) -> bool {
self.window_id == other.window_id && self.view_id == other.view_id
}
}
impl<T> PartialEq<ViewHandle<T>> for AnyViewHandle {
fn eq(&self, other: &ViewHandle<T>) -> bool {
self.window_id == other.window_id && self.view_id == other.view_id
@@ -4189,7 +4274,7 @@ impl<T> Hash for WeakViewHandle<T> {
}
}
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct AnyWeakViewHandle {
window_id: usize,
view_id: usize,
@@ -6004,6 +6089,53 @@ mod tests {
}
}
#[crate::test(self)]
fn test_keystrokes_for_action_with_data(cx: &mut TestAppContext) {
#[derive(Clone, Debug, Deserialize, PartialEq)]
struct ActionWithArg {
#[serde(default)]
arg: bool,
}
struct View;
impl super::Entity for View {
type Event = ();
}
impl super::View for View {
fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
Empty::new().into_any()
}
fn ui_name() -> &'static str {
"View"
}
}
impl_actions!(test, [ActionWithArg]);
let (window_id, view) = cx.add_window(|_| View);
cx.update(|cx| {
cx.add_global_action(|_: &ActionWithArg, _| {});
cx.add_bindings(vec![
Binding::new("a", ActionWithArg { arg: false }, None),
Binding::new("shift-a", ActionWithArg { arg: true }, None),
]);
});
let actions = cx.available_actions(window_id, view.id());
assert_eq!(
actions[0].1.as_any().downcast_ref::<ActionWithArg>(),
Some(&ActionWithArg { arg: false })
);
assert_eq!(
actions[0]
.2
.iter()
.map(|b| b.keystrokes()[0].clone())
.collect::<Vec<_>>(),
vec![Keystroke::parse("a").unwrap()],
);
}
#[crate::test(self)]
async fn test_model_condition(cx: &mut TestAppContext) {
struct Counter(usize);

View File

@@ -270,7 +270,7 @@ impl TestAppContext {
.borrow_mut()
.pop_front()
.expect("prompt was not called");
let _ = done_tx.try_send(answer);
done_tx.try_send(answer).ok();
}
pub fn has_pending_prompt(&self, window_id: usize) -> bool {
@@ -434,7 +434,9 @@ impl<T: Entity> ModelHandle<T> {
Duration::from_secs(1)
};
let executor = cx.background().clone();
async move {
executor.start_waiting();
let notification = crate::util::timeout(duration, rx.next())
.await
.expect("next notification timed out");

View File

@@ -29,6 +29,7 @@ use sqlez::{
};
use std::{
any::TypeId,
mem,
ops::{Deref, DerefMut, Range},
};
use util::ResultExt;
@@ -393,17 +394,18 @@ impl<'a> WindowContext<'a> {
.iter()
.filter_map(move |(name, (type_id, deserialize))| {
if let Some(action_depth) = handler_depths_by_action_type.get(type_id).copied() {
Some((
*name,
deserialize("{}").ok()?,
self.keystroke_matcher
.bindings_for_action_type(*type_id)
.filter(|b| {
(0..=action_depth).any(|depth| b.match_context(&contexts[depth..]))
})
.cloned()
.collect(),
))
let action = deserialize("{}").ok()?;
let bindings = self
.keystroke_matcher
.bindings_for_action_type(*type_id)
.filter(|b| {
action.eq(b.action())
&& (0..=action_depth)
.any(|depth| b.match_context(&contexts[depth..]))
})
.cloned()
.collect();
Some((*name, action, bindings))
} else {
None
}
@@ -890,7 +892,7 @@ impl<'a> WindowContext<'a> {
Ok(element)
}
pub(crate) fn layout(&mut self, refreshing: bool) -> Result<()> {
pub(crate) fn layout(&mut self, refreshing: bool) -> Result<HashMap<usize, usize>> {
let window_size = self.window.platform_window.content_size();
let root_view_id = self.window.root_view().id();
let mut rendered_root = self.window.rendered_views.remove(&root_view_id).unwrap();
@@ -923,11 +925,11 @@ impl<'a> WindowContext<'a> {
}
}
self.window.parents = new_parents;
let old_parents = mem::replace(&mut self.window.parents, new_parents);
self.window
.rendered_views
.insert(root_view_id, rendered_root);
Ok(())
Ok(old_parents)
}
pub(crate) fn paint(&mut self) -> Result<Scene> {

View File

@@ -187,25 +187,23 @@ pub trait Element<V: View>: 'static {
Tooltip::new::<Tag, V>(id, text, action, style, self.into_any(), cx)
}
fn with_resize_handle<Tag: 'static>(
fn resizable(
self,
element_id: usize,
side: Side,
handle_size: f32,
initial_size: f32,
cx: &mut ViewContext<V>,
side: HandleSide,
size: f32,
on_resize: impl 'static + FnMut(&mut V, f32, &mut ViewContext<V>),
) -> Resizable<V>
where
Self: 'static + Sized,
{
Resizable::new::<Tag, V>(
self.into_any(),
element_id,
side,
handle_size,
initial_size,
cx,
)
Resizable::new(self.into_any(), side, size, on_resize)
}
fn mouse<Tag>(self, region_id: usize) -> MouseEventHandler<Tag, V>
where
Self: Sized,
{
MouseEventHandler::for_child(self.into_any(), region_id)
}
}

View File

@@ -990,7 +990,7 @@ mod tests {
_: &mut V,
_: &mut ViewContext<V>,
) {
todo!()
unimplemented!()
}
fn rect_for_text_range(
@@ -1003,7 +1003,7 @@ mod tests {
_: &V,
_: &ViewContext<V>,
) -> Option<RectF> {
todo!()
unimplemented!()
}
fn debug(&self, _: RectF, _: &(), _: &(), _: &V, _: &ViewContext<V>) -> serde_json::Value {

View File

@@ -32,10 +32,25 @@ pub struct MouseEventHandler<Tag: 'static, V: View> {
/// Element which provides a render_child callback with a MouseState and paints a mouse
/// region under (or above) it for easy mouse event handling.
impl<Tag, V: View> MouseEventHandler<Tag, V> {
pub fn new<D, F>(region_id: usize, cx: &mut ViewContext<V>, render_child: F) -> Self
pub fn for_child(child: impl Element<V>, region_id: usize) -> Self {
Self {
child: child.into_any(),
region_id,
cursor_style: None,
handlers: Default::default(),
notify_on_hover: false,
notify_on_click: false,
hoverable: false,
above: false,
padding: Default::default(),
_tag: PhantomData,
}
}
pub fn new<E, F>(region_id: usize, cx: &mut ViewContext<V>, render_child: F) -> Self
where
D: Element<V>,
F: FnOnce(&mut MouseState, &mut ViewContext<V>) -> D,
E: Element<V>,
F: FnOnce(&mut MouseState, &mut ViewContext<V>) -> E,
{
let mut mouse_state = cx.mouse_state::<Tag>(region_id);
let child = render_child(&mut mouse_state, cx).into_any();

View File

@@ -1,4 +1,4 @@
use std::{cell::Cell, rc::Rc};
use std::{cell::RefCell, rc::Rc};
use pathfinder_geometry::vector::{vec2f, Vector2F};
use serde_json::json;
@@ -7,25 +7,23 @@ use crate::{
geometry::rect::RectF,
platform::{CursorStyle, MouseButton},
scene::MouseDrag,
AnyElement, Axis, Element, ElementStateHandle, LayoutContext, MouseRegion, SceneBuilder, View,
AnyElement, Axis, Element, LayoutContext, MouseRegion, SceneBuilder, SizeConstraint, View,
ViewContext,
};
use super::{ConstrainedBox, Hook};
#[derive(Copy, Clone, Debug)]
pub enum Side {
pub enum HandleSide {
Top,
Bottom,
Left,
Right,
}
impl Side {
impl HandleSide {
fn axis(&self) -> Axis {
match self {
Side::Left | Side::Right => Axis::Horizontal,
Side::Top | Side::Bottom => Axis::Vertical,
HandleSide::Left | HandleSide::Right => Axis::Horizontal,
HandleSide::Top | HandleSide::Bottom => Axis::Vertical,
}
}
@@ -33,8 +31,8 @@ impl Side {
/// then top-to-bottom
fn before_content(self) -> bool {
match self {
Side::Left | Side::Top => true,
Side::Right | Side::Bottom => false,
HandleSide::Left | HandleSide::Top => true,
HandleSide::Right | HandleSide::Bottom => false,
}
}
@@ -55,14 +53,14 @@ impl Side {
fn of_rect(&self, bounds: RectF, handle_size: f32) -> RectF {
match self {
Side::Top => RectF::new(bounds.origin(), vec2f(bounds.width(), handle_size)),
Side::Left => RectF::new(bounds.origin(), vec2f(handle_size, bounds.height())),
Side::Bottom => {
HandleSide::Top => RectF::new(bounds.origin(), vec2f(bounds.width(), handle_size)),
HandleSide::Left => RectF::new(bounds.origin(), vec2f(handle_size, bounds.height())),
HandleSide::Bottom => {
let mut origin = bounds.lower_left();
origin.set_y(origin.y() - handle_size);
RectF::new(origin, vec2f(bounds.width(), handle_size))
}
Side::Right => {
HandleSide::Right => {
let mut origin = bounds.upper_right();
origin.set_x(origin.x() - handle_size);
RectF::new(origin, vec2f(handle_size, bounds.height()))
@@ -71,69 +69,44 @@ impl Side {
}
}
struct ResizeHandleState {
actual_dimension: Cell<f32>,
custom_dimension: Cell<f32>,
pub struct Resizable<V: View> {
child: AnyElement<V>,
handle_side: HandleSide,
handle_size: f32,
on_resize: Rc<RefCell<dyn FnMut(&mut V, f32, &mut ViewContext<V>)>>,
}
pub struct Resizable<V: View> {
side: Side,
handle_size: f32,
child: AnyElement<V>,
state: Rc<ResizeHandleState>,
_state_handle: ElementStateHandle<Rc<ResizeHandleState>>,
}
const DEFAULT_HANDLE_SIZE: f32 = 4.0;
impl<V: View> Resizable<V> {
pub fn new<Tag: 'static, T: View>(
pub fn new(
child: AnyElement<V>,
element_id: usize,
side: Side,
handle_size: f32,
initial_size: f32,
cx: &mut ViewContext<V>,
handle_side: HandleSide,
size: f32,
on_resize: impl 'static + FnMut(&mut V, f32, &mut ViewContext<V>),
) -> Self {
let state_handle = cx.element_state::<Tag, Rc<ResizeHandleState>>(
element_id,
Rc::new(ResizeHandleState {
actual_dimension: Cell::new(initial_size),
custom_dimension: Cell::new(initial_size),
}),
);
let state = state_handle.read(cx).clone();
let child = Hook::new({
let constrained = ConstrainedBox::new(child);
match side.axis() {
Axis::Horizontal => constrained.with_max_width(state.custom_dimension.get()),
Axis::Vertical => constrained.with_max_height(state.custom_dimension.get()),
}
})
.on_after_layout({
let state = state.clone();
move |size, _| {
state.actual_dimension.set(side.relevant_component(size));
}
})
let child = match handle_side.axis() {
Axis::Horizontal => child.constrained().with_max_width(size),
Axis::Vertical => child.constrained().with_max_height(size),
}
.into_any();
Self {
side,
child,
handle_size,
state,
_state_handle: state_handle,
handle_side,
handle_size: DEFAULT_HANDLE_SIZE,
on_resize: Rc::new(RefCell::new(on_resize)),
}
}
pub fn current_size(&self) -> f32 {
self.state.actual_dimension.get()
pub fn with_handle_size(mut self, handle_size: f32) -> Self {
self.handle_size = handle_size;
self
}
}
impl<V: View> Element<V> for Resizable<V> {
type LayoutState = ();
type LayoutState = SizeConstraint;
type PaintState = ();
fn layout(
@@ -142,7 +115,7 @@ impl<V: View> Element<V> for Resizable<V> {
view: &mut V,
cx: &mut LayoutContext<V>,
) -> (Vector2F, Self::LayoutState) {
(self.child.layout(constraint, view, cx), ())
(self.child.layout(constraint, view, cx), constraint)
}
fn paint(
@@ -150,34 +123,44 @@ impl<V: View> Element<V> for Resizable<V> {
scene: &mut SceneBuilder,
bounds: pathfinder_geometry::rect::RectF,
visible_bounds: pathfinder_geometry::rect::RectF,
_child_size: &mut Self::LayoutState,
constraint: &mut SizeConstraint,
view: &mut V,
cx: &mut ViewContext<V>,
) -> Self::PaintState {
scene.push_stacking_context(None, None);
let handle_region = self.side.of_rect(bounds, self.handle_size);
let handle_region = self.handle_side.of_rect(bounds, self.handle_size);
enum ResizeHandle {}
scene.push_mouse_region(
MouseRegion::new::<ResizeHandle>(cx.view_id(), self.side as usize, handle_region)
.on_down(MouseButton::Left, |_, _: &mut V, _| {}) // This prevents the mouse down event from being propagated elsewhere
.on_drag(MouseButton::Left, {
let state = self.state.clone();
let side = self.side;
move |e, _: &mut V, cx| {
let prev_width = state.actual_dimension.get();
state
.custom_dimension
.set(0f32.max(prev_width + side.compute_delta(e)).round());
cx.notify();
MouseRegion::new::<ResizeHandle>(
cx.view_id(),
self.handle_side as usize,
handle_region,
)
.on_down(MouseButton::Left, |_, _: &mut V, _| {}) // This prevents the mouse down event from being propagated elsewhere
.on_drag(MouseButton::Left, {
let bounds = bounds.clone();
let side = self.handle_side;
let prev_size = side.relevant_component(bounds.size());
let min_size = side.relevant_component(constraint.min);
let max_size = side.relevant_component(constraint.max);
let on_resize = self.on_resize.clone();
move |event, view: &mut V, cx| {
let new_size = min_size
.max(prev_size + side.compute_delta(event))
.min(max_size)
.round();
if new_size != prev_size {
on_resize.borrow_mut()(view, new_size, cx);
}
}),
}
}),
);
scene.push_cursor_region(crate::CursorRegion {
bounds: handle_region,
style: match self.side.axis() {
style: match self.handle_side.axis() {
Axis::Horizontal => CursorStyle::ResizeLeftRight,
Axis::Vertical => CursorStyle::ResizeUpDown,
},

View File

@@ -477,6 +477,14 @@ impl Deterministic {
state.rng = StdRng::seed_from_u64(state.seed);
}
pub fn allow_parking(&self) {
use rand::prelude::*;
let mut state = self.state.lock();
state.forbid_parking = false;
state.rng = StdRng::seed_from_u64(state.seed);
}
pub async fn simulate_random_delay(&self) {
use rand::prelude::*;
use smol::future::yield_now;
@@ -698,6 +706,14 @@ impl Foreground {
}
}
#[cfg(any(test, feature = "test-support"))]
pub fn allow_parking(&self) {
match self {
Self::Deterministic { executor, .. } => executor.allow_parking(),
_ => panic!("this method can only be called on a deterministic executor"),
}
}
#[cfg(any(test, feature = "test-support"))]
pub fn advance_clock(&self, duration: Duration) {
match self {
@@ -860,6 +876,14 @@ impl Background {
}
}
}
#[cfg(any(test, feature = "test-support"))]
pub fn start_waiting(&self) {
match self {
Self::Deterministic { executor, .. } => executor.start_waiting(),
_ => panic!("this method can only be called on a deterministic executor"),
}
}
}
impl Default for Background {
@@ -944,7 +968,7 @@ impl<T: 'static, E: 'static + Display> Task<Result<T, E>> {
pub fn detach_and_log_err(self, cx: &mut AppContext) {
cx.spawn(|_| async move {
if let Err(err) = self.await {
log::error!("{}", err);
log::error!("{:#}", err);
}
})
.detach();

View File

@@ -25,8 +25,9 @@ struct Family {
pub struct FontCache(RwLock<FontCacheState>);
pub struct FontCacheState {
fonts: Arc<dyn platform::FontSystem>,
font_system: Arc<dyn platform::FontSystem>,
families: Vec<Family>,
default_family: Option<FamilyId>,
font_selections: HashMap<FamilyId, HashMap<Properties, FontId>>,
metrics: HashMap<FontId, Metrics>,
wrapper_pool: HashMap<(FontId, OrderedFloat<f32>), Vec<LineWrapper>>,
@@ -42,8 +43,9 @@ unsafe impl Send for FontCache {}
impl FontCache {
pub fn new(fonts: Arc<dyn platform::FontSystem>) -> Self {
Self(RwLock::new(FontCacheState {
fonts,
font_system: fonts,
families: Default::default(),
default_family: None,
font_selections: Default::default(),
metrics: Default::default(),
wrapper_pool: Default::default(),
@@ -73,14 +75,14 @@ impl FontCache {
let mut state = RwLockUpgradableReadGuard::upgrade(state);
if let Ok(font_ids) = state.fonts.load_family(name, features) {
if let Ok(font_ids) = state.font_system.load_family(name, features) {
if font_ids.is_empty() {
continue;
}
let family_id = FamilyId(state.families.len());
for font_id in &font_ids {
if state.fonts.glyph_for_char(*font_id, 'm').is_none() {
if state.font_system.glyph_for_char(*font_id, 'm').is_none() {
return Err(anyhow!("font must contain a glyph for the 'm' character"));
}
}
@@ -99,6 +101,31 @@ impl FontCache {
))
}
/// Returns an arbitrary font family that is available on the system.
pub fn known_existing_family(&self) -> FamilyId {
if let Some(family_id) = self.0.read().default_family {
return family_id;
}
let default_family = self
.load_family(
&["Courier", "Helvetica", "Arial", "Verdana"],
&Default::default(),
)
.unwrap_or_else(|_| {
let all_family_names = self.0.read().font_system.all_families();
let all_family_names: Vec<_> = all_family_names
.iter()
.map(|string| string.as_str())
.collect();
self.load_family(&all_family_names, &Default::default())
.expect("could not load any default font family")
});
self.0.write().default_family = Some(default_family);
default_family
}
pub fn default_font(&self, family_id: FamilyId) -> FontId {
self.select_font(family_id, &Properties::default()).unwrap()
}
@@ -115,7 +142,7 @@ impl FontCache {
let mut inner = RwLockUpgradableReadGuard::upgrade(inner);
let family = &inner.families[family_id.0];
let font_id = inner
.fonts
.font_system
.select_font(&family.font_ids, properties)
.unwrap_or(family.font_ids[0]);
@@ -137,7 +164,7 @@ impl FontCache {
if let Some(metrics) = state.metrics.get(&font_id) {
f(metrics)
} else {
let metrics = state.fonts.font_metrics(font_id);
let metrics = state.font_system.font_metrics(font_id);
let metric = f(&metrics);
let mut state = RwLockUpgradableReadGuard::upgrade(state);
state.metrics.insert(font_id, metrics);
@@ -157,8 +184,11 @@ impl FontCache {
let bounds;
{
let state = self.0.read();
glyph_id = state.fonts.glyph_for_char(font_id, 'm').unwrap();
bounds = state.fonts.typographic_bounds(font_id, glyph_id).unwrap();
glyph_id = state.font_system.glyph_for_char(font_id, 'm').unwrap();
bounds = state
.font_system
.typographic_bounds(font_id, glyph_id)
.unwrap();
}
bounds.width() * self.em_scale(font_id, font_size)
}
@@ -168,8 +198,8 @@ impl FontCache {
let advance;
{
let state = self.0.read();
glyph_id = state.fonts.glyph_for_char(font_id, 'm').unwrap();
advance = state.fonts.advance(font_id, glyph_id).unwrap();
glyph_id = state.font_system.glyph_for_char(font_id, 'm').unwrap();
advance = state.font_system.advance(font_id, glyph_id).unwrap();
}
advance.x() * self.em_scale(font_id, font_size)
}
@@ -214,7 +244,7 @@ impl FontCache {
.or_default();
let wrapper = wrappers
.pop()
.unwrap_or_else(|| LineWrapper::new(font_id, font_size, state.fonts.clone()));
.unwrap_or_else(|| LineWrapper::new(font_id, font_size, state.font_system.clone()));
LineWrapperHandle {
wrapper: Some(wrapper),
font_cache: self.clone(),

View File

@@ -295,13 +295,14 @@ impl Default for TextStyle {
.as_ref()
.expect("TextStyle::default can only be called within a call to with_font_cache");
let font_family_name = Arc::from("Courier");
let font_family_id = font_cache
.load_family(&[&font_family_name], &Default::default())
.unwrap();
let font_family_id = font_cache.known_existing_family();
let font_id = font_cache
.select_font(font_family_id, &Default::default())
.unwrap();
.expect("did not have any font in system-provided family");
let font_family_name = font_cache
.family_name(font_family_id)
.expect("we loaded this family from the font cache, so this should work");
Self {
color: Default::default(),
font_family_name,

View File

@@ -173,6 +173,7 @@ pub struct WindowOptions<'a> {
pub titlebar: Option<TitlebarOptions<'a>>,
pub center: bool,
pub focus: bool,
pub show: bool,
pub kind: WindowKind,
pub is_movable: bool,
pub screen: Option<Rc<dyn Screen>>,
@@ -222,21 +223,21 @@ impl Bind for WindowBounds {
fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
let (region, next_index) = match self {
WindowBounds::Fullscreen => {
let next_index = statement.bind("Fullscreen", start_index)?;
let next_index = statement.bind(&"Fullscreen", start_index)?;
(None, next_index)
}
WindowBounds::Maximized => {
let next_index = statement.bind("Maximized", start_index)?;
let next_index = statement.bind(&"Maximized", start_index)?;
(None, next_index)
}
WindowBounds::Fixed(region) => {
let next_index = statement.bind("Fixed", start_index)?;
let next_index = statement.bind(&"Fixed", start_index)?;
(Some(*region), next_index)
}
};
statement.bind(
region.map(|region| {
&region.map(|region| {
(
region.min_x(),
region.min_y(),
@@ -342,6 +343,7 @@ pub enum RasterizationOptions {
pub trait FontSystem: Send + Sync {
fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> anyhow::Result<()>;
fn all_families(&self) -> Vec<String>;
fn load_family(&self, name: &str, features: &FontFeatures) -> anyhow::Result<Vec<FontId>>;
fn select_font(
&self,
@@ -376,6 +378,7 @@ impl<'a> Default for WindowOptions<'a> {
}),
center: false,
focus: true,
show: true,
kind: WindowKind::Normal,
is_movable: true,
screen: None,

View File

@@ -66,6 +66,14 @@ impl platform::FontSystem for FontSystem {
self.0.write().add_fonts(fonts)
}
fn all_families(&self) -> Vec<String> {
self.0
.read()
.system_source
.all_families()
.expect("core text should never return an error")
}
fn load_family(&self, name: &str, features: &Features) -> anyhow::Result<Vec<FontId>> {
self.0.write().load_family(name, features)
}

View File

@@ -614,7 +614,7 @@ impl Window {
}
if options.focus {
native_window.makeKeyAndOrderFront_(nil);
} else {
} else if options.show {
native_window.orderFront_(nil);
}

View File

@@ -13,9 +13,15 @@ editor = { path = "../editor" }
gpui = { path = "../gpui" }
util = { path = "../util" }
workspace = { path = "../workspace" }
settings = { path = "../settings" }
anyhow.workspace = true
chrono = "0.4"
dirs = "4.0"
serde.workspace = true
schemars.workspace = true
log.workspace = true
settings = { path = "../settings" }
shellexpand = "2.1.0"
[dev-dependencies]
editor = { path = "../editor", features = ["test-support"] }

View File

@@ -1,7 +1,9 @@
use anyhow::Result;
use chrono::{Datelike, Local, NaiveTime, Timelike};
use editor::{scroll::autoscroll::Autoscroll, Editor};
use gpui::{actions, AppContext};
use settings::{HourFormat, Settings};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::{
fs::OpenOptions,
path::{Path, PathBuf},
@@ -11,13 +13,48 @@ use workspace::AppState;
actions!(journal, [NewJournalEntry]);
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct JournalSettings {
pub path: Option<String>,
pub hour_format: Option<HourFormat>,
}
impl Default for JournalSettings {
fn default() -> Self {
Self {
path: Some("~".into()),
hour_format: Some(Default::default()),
}
}
}
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum HourFormat {
#[default]
Hour12,
Hour24,
}
impl settings::Setting for JournalSettings {
const KEY: Option<&'static str> = Some("journal");
type FileContent = Self;
fn load(default_value: &Self, user_values: &[&Self], _: &AppContext) -> Result<Self> {
Self::load_via_json_merge(default_value, user_values)
}
}
pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
settings::register::<JournalSettings>(cx);
cx.add_global_action(move |_: &NewJournalEntry, cx| new_journal_entry(app_state.clone(), cx));
}
pub fn new_journal_entry(app_state: Arc<AppState>, cx: &mut AppContext) {
let settings = cx.global::<Settings>();
let journal_dir = match journal_dir(&settings) {
let settings = settings::get::<JournalSettings>(cx);
let journal_dir = match journal_dir(settings.path.as_ref().unwrap()) {
Some(journal_dir) => journal_dir,
None => {
log::error!("Can't determine journal directory");
@@ -31,8 +68,7 @@ pub fn new_journal_entry(app_state: Arc<AppState>, cx: &mut AppContext) {
.join(format!("{:02}", now.month()));
let entry_path = month_dir.join(format!("{:02}.md", now.day()));
let now = now.time();
let hour_format = &settings.journal_overrides.hour_format;
let entry_heading = heading_entry(now, &hour_format);
let entry_heading = heading_entry(now, &settings.hour_format);
let create_entry = cx.background().spawn(async move {
std::fs::create_dir_all(month_dir)?;
@@ -76,14 +112,8 @@ pub fn new_journal_entry(app_state: Arc<AppState>, cx: &mut AppContext) {
.detach_and_log_err(cx);
}
fn journal_dir(settings: &Settings) -> Option<PathBuf> {
let journal_dir = settings
.journal_overrides
.path
.as_ref()
.unwrap_or(settings.journal_defaults.path.as_ref()?);
let expanded_journal_dir = shellexpand::full(&journal_dir) //TODO handle this better
fn journal_dir(path: &str) -> Option<PathBuf> {
let expanded_journal_dir = shellexpand::full(path) //TODO handle this better
.ok()
.map(|dir| Path::new(&dir.to_string()).to_path_buf().join("journal"));

View File

@@ -36,16 +36,19 @@ sum_tree = { path = "../sum_tree" }
text = { path = "../text" }
theme = { path = "../theme" }
util = { path = "../util" }
anyhow.workspace = true
async-broadcast = "0.4"
async-trait.workspace = true
futures.workspace = true
globset.workspace = true
lazy_static.workspace = true
log.workspace = true
parking_lot.workspace = true
postage.workspace = true
rand = { workspace = true, optional = true }
regex.workspace = true
schemars.workspace = true
serde.workspace = true
serde_derive.workspace = true
serde_json.workspace = true
@@ -67,7 +70,7 @@ settings = { path = "../settings", features = ["test-support"] }
util = { path = "../util", features = ["test-support"] }
ctor.workspace = true
env_logger.workspace = true
indoc = "1.0.4"
indoc.workspace = true
rand.workspace = true
tree-sitter-embedded-template = "*"
tree-sitter-html = "*"

View File

@@ -5,6 +5,7 @@ pub use crate::{
};
use crate::{
diagnostic_set::{DiagnosticEntry, DiagnosticGroup},
language_settings::{language_settings, LanguageSettings},
outline::OutlineItem,
syntax_map::{
SyntaxMap, SyntaxMapCapture, SyntaxMapCaptures, SyntaxSnapshot, ToTreeSitterPoint,
@@ -18,7 +19,6 @@ use futures::FutureExt as _;
use gpui::{fonts::HighlightStyle, AppContext, Entity, ModelContext, Task};
use lsp::LanguageServerId;
use parking_lot::Mutex;
use settings::Settings;
use similar::{ChangeTag, TextDiff};
use smallvec::SmallVec;
use smol::future::yield_now;
@@ -50,16 +50,10 @@ pub use {tree_sitter_rust, tree_sitter_typescript};
pub use lsp::DiagnosticSeverity;
struct GitDiffStatus {
diff: git::diff::BufferDiff,
update_in_progress: bool,
update_requested: bool,
}
pub struct Buffer {
text: TextBuffer,
diff_base: Option<String>,
git_diff_status: GitDiffStatus,
git_diff: git::diff::BufferDiff,
file: Option<Arc<dyn File>>,
saved_version: clock::Global,
saved_version_fingerprint: RopeFingerprint,
@@ -195,6 +189,7 @@ pub enum Event {
Saved,
FileHandleChanged,
Reloaded,
DiffBaseChanged,
LanguageChanged,
Reparsed,
DiagnosticsUpdated,
@@ -466,11 +461,7 @@ impl Buffer {
was_dirty_before_starting_transaction: None,
text: buffer,
diff_base,
git_diff_status: GitDiffStatus {
diff: git::diff::BufferDiff::new(),
update_in_progress: false,
update_requested: false,
},
git_diff: git::diff::BufferDiff::new(),
file,
syntax_map: Mutex::new(SyntaxMap::new()),
parsing_in_background: false,
@@ -501,7 +492,7 @@ impl Buffer {
BufferSnapshot {
text,
syntax,
git_diff: self.git_diff_status.diff.clone(),
git_diff: self.git_diff.clone(),
file: self.file.clone(),
remote_selections: self.remote_selections.clone(),
diagnostics: self.diagnostics.clone(),
@@ -620,7 +611,6 @@ impl Buffer {
cx,
);
}
self.git_diff_recalc(cx);
cx.emit(Event::Reloaded);
cx.notify();
}
@@ -676,50 +666,29 @@ impl Buffer {
pub fn set_diff_base(&mut self, diff_base: Option<String>, cx: &mut ModelContext<Self>) {
self.diff_base = diff_base;
self.git_diff_recalc(cx);
cx.emit(Event::DiffBaseChanged);
}
pub fn needs_git_diff_recalc(&self) -> bool {
self.git_diff_status.diff.needs_update(self)
}
pub fn git_diff_recalc(&mut self, cx: &mut ModelContext<Self>) -> Option<Task<()>> {
let diff_base = self.diff_base.clone()?; // TODO: Make this an Arc
let snapshot = self.snapshot();
pub fn git_diff_recalc(&mut self, cx: &mut ModelContext<Self>) {
if self.git_diff_status.update_in_progress {
self.git_diff_status.update_requested = true;
return;
}
let mut diff = self.git_diff.clone();
let diff = cx.background().spawn(async move {
diff.update(&diff_base, &snapshot).await;
diff
});
if let Some(diff_base) = &self.diff_base {
let snapshot = self.snapshot();
let diff_base = diff_base.clone();
let mut diff = self.git_diff_status.diff.clone();
let diff = cx.background().spawn(async move {
diff.update(&diff_base, &snapshot).await;
diff
});
cx.spawn_weak(|this, mut cx| async move {
let buffer_diff = diff.await;
if let Some(this) = this.upgrade(&cx) {
this.update(&mut cx, |this, cx| {
this.git_diff_status.diff = buffer_diff;
this.git_diff_update_count += 1;
cx.notify();
this.git_diff_status.update_in_progress = false;
if this.git_diff_status.update_requested {
this.git_diff_recalc(cx);
}
})
}
})
.detach()
} else {
let snapshot = self.snapshot();
self.git_diff_status.diff.clear(&snapshot);
self.git_diff_update_count += 1;
cx.notify();
}
let handle = cx.weak_handle();
Some(cx.spawn_weak(|_, mut cx| async move {
let buffer_diff = diff.await;
if let Some(this) = handle.upgrade(&mut cx) {
this.update(&mut cx, |this, _| {
this.git_diff = buffer_diff;
this.git_diff_update_count += 1;
})
}
}))
}
pub fn close(&mut self, cx: &mut ModelContext<Self>) {
@@ -1644,10 +1613,17 @@ impl Buffer {
cx: &mut ModelContext<Self>,
) {
if lamport_timestamp > self.diagnostics_timestamp {
match self.diagnostics.binary_search_by_key(&server_id, |e| e.0) {
Err(ix) => self.diagnostics.insert(ix, (server_id, diagnostics)),
Ok(ix) => self.diagnostics[ix].1 = diagnostics,
};
let ix = self.diagnostics.binary_search_by_key(&server_id, |e| e.0);
if diagnostics.len() == 0 {
if let Ok(ix) = ix {
self.diagnostics.remove(ix);
}
} else {
match ix {
Err(ix) => self.diagnostics.insert(ix, (server_id, diagnostics)),
Ok(ix) => self.diagnostics[ix].1 = diagnostics,
};
}
self.diagnostics_timestamp = lamport_timestamp;
self.diagnostics_update_count += 1;
self.text.lamport_clock.observe(lamport_timestamp);
@@ -1827,11 +1803,11 @@ impl BufferSnapshot {
pub fn language_indent_size_at<T: ToOffset>(&self, position: T, cx: &AppContext) -> IndentSize {
let language_name = self.language_at(position).map(|language| language.name());
let settings = cx.global::<Settings>();
if settings.hard_tabs(language_name.as_deref()) {
let settings = language_settings(language_name.as_deref(), cx);
if settings.hard_tabs {
IndentSize::tab()
} else {
IndentSize::spaces(settings.tab_size(language_name.as_deref()).get())
IndentSize::spaces(settings.tab_size.get())
}
}
@@ -2146,6 +2122,15 @@ impl BufferSnapshot {
.or(self.language.as_ref())
}
pub fn settings_at<'a, D: ToOffset>(
&self,
position: D,
cx: &'a AppContext,
) -> &'a LanguageSettings {
let language = self.language_at(position);
language_settings(language.map(|l| l.name()).as_deref(), cx)
}
pub fn language_scope_at<D: ToOffset>(&self, position: D) -> Option<LanguageScope> {
let offset = position.to_offset(self);
@@ -2500,18 +2485,22 @@ impl BufferSnapshot {
pub fn git_diff_hunks_in_row_range<'a>(
&'a self,
range: Range<u32>,
reversed: bool,
) -> impl 'a + Iterator<Item = git::diff::DiffHunk<u32>> {
self.git_diff.hunks_in_row_range(range, self, reversed)
self.git_diff.hunks_in_row_range(range, self)
}
pub fn git_diff_hunks_intersecting_range<'a>(
&'a self,
range: Range<Anchor>,
reversed: bool,
) -> impl 'a + Iterator<Item = git::diff::DiffHunk<u32>> {
self.git_diff
.hunks_intersecting_range(range, self, reversed)
self.git_diff.hunks_intersecting_range(range, self)
}
pub fn git_diff_hunks_intersecting_range_rev<'a>(
&'a self,
range: Range<Anchor>,
) -> impl 'a + Iterator<Item = git::diff::DiffHunk<u32>> {
self.git_diff.hunks_intersecting_range_rev(range, self)
}
pub fn diagnostics_in_range<'a, T, O>(

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