Compare commits

..

128 Commits

Author SHA1 Message Date
Mikayla Maki
4c71e4b749 Make settings store handle no user settings (#2550)
This fixes the crash users have been reporting with the theme selector
2023-06-01 11:13:02 -07:00
Mikayla Maki
32ea881be7 zed 0.89.2 2023-06-01 10:59:53 -07:00
Mikayla Maki
030968ec51 Redraw the terminal on every wakeup (#2551)
For whatever reason, the optimizations of panes and workspace have
caused the terminal to notify less often then it should. This PR fixes
that oversight.
2023-06-01 10:59:00 -07:00
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
Nathan Sobo
747322a02d Merge remote-tracking branch 'origin/main' into zmd 2023-05-24 11:04:07 -06: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
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
Nathan Sobo
7be41e19f7 WIP 2023-05-23 08:52:21 -06: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
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
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
Nathan Sobo
912fd23006 Parse markdown into an OpenAI chat completion request 2023-05-20 09:43:12 -06:00
105 changed files with 3069 additions and 2032 deletions

View File

@@ -2,12 +2,11 @@
Release Notes:
Use `N/A` in this section if this item should be skipped in the release notes.
- N/A
Add release note lines here:
or
* (Added|Fixed|Improved) ... ([#<public_issue_number_if_exists>](https://github.com/zed-industries/community/issues/<public_issue_number_if_exists>)).
* ...
- (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.

6
Cargo.lock generated
View File

@@ -3745,9 +3745,9 @@ dependencies = [
[[package]]
name = "lsp-types"
version = "0.91.1"
version = "0.94.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2368312c59425dd133cb9a327afee65be0a633a8ce471d248e2202a48f8f68ae"
checksum = "0b63735a13a1f9cd4f4835223d828ed9c2e35c8c5e61837774399f558b6a1237"
dependencies = [
"bitflags",
"serde",
@@ -8777,7 +8777,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.88.4"
version = "0.89.2"
dependencies = [
"activity_indicator",
"ai",

View File

@@ -1,18 +1,6 @@
You are #zed, a language model representing the collective understanding of an open source project called Zed. When a new human visits you, they'll send you their profile. You'll respond with an introduction tailored to their situation. For example, a new user might see something like this:
Welcome to Zed! Zed is an innovative, open-source platform designed to enhance team communication and collaboration. At the heart of Zed are *contexts*, which create a dynamic digital representation of shared mental models. Contexts offer personalized starting points and the flexibility to edit and explore, enabling teams to align knowledge, streamline communication, and improve overall performance.
As the #zed model, I'm happy to answer any questions. In fact, I will improve as a result of you doing so!
You might ask about Zed's core philosophy, how you can build your own model like this one, or how you might get involved. Zed's open source!
> [USER INPUT PROMPT]
You should base your introduction on your full understanding of the state of #zed and the user's profile, customizing your introduction to their specific needs. Don't welcome them to Zed if they've been using Zed for 2 days. If they're returning after a while, welcome them back.
User input begins on a line starting with >.
Your output begins on a line starting with <.
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. Spend your tokens on valuable information.
Use simple language and don't flatter the users.
Keep it short.
Risk being rude.

View File

@@ -39,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,

View File

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

View File

@@ -5010,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.
@@ -6606,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();
@@ -6617,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();
@@ -6628,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();
@@ -7377,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

@@ -609,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)
}
@@ -1508,6 +1499,7 @@ mod tests {
language::init(cx);
client::init_settings(cx);
workspace::init_settings(cx);
Project::init_settings(cx);
});
}

View File

@@ -2122,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) {
@@ -2500,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;
@@ -4876,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();
@@ -6818,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);
@@ -7206,6 +7262,7 @@ pub enum Event {
DirtyChanged,
Saved,
TitleChanged,
DiffBaseChanged,
SelectionsChanged {
local: bool,
},

View File

@@ -7,6 +7,7 @@ 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,
}
@@ -30,6 +31,7 @@ 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>,
}

View File

@@ -1246,7 +1246,7 @@ fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
#[gpui::test]
async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorTestContext::new(cx);
let mut cx = EditorTestContext::new(cx).await;
let line_height = cx.editor(|editor, cx| editor.style(cx).text.line_height(cx.font_cache()));
cx.simulate_window_resize(cx.window_id, vec2f(100., 4. * line_height));
@@ -1358,7 +1358,7 @@ async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppCon
#[gpui::test]
async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorTestContext::new(cx);
let mut cx = EditorTestContext::new(cx).await;
let line_height = cx.editor(|editor, cx| editor.style(cx).text.line_height(cx.font_cache()));
cx.simulate_window_resize(cx.window_id, vec2f(100., 4. * line_height));
@@ -1473,7 +1473,7 @@ async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
#[gpui::test]
async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorTestContext::new(cx);
let mut cx = EditorTestContext::new(cx).await;
cx.set_state("one «two threeˇ» four");
cx.update_editor(|editor, cx| {
editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
@@ -1637,7 +1637,7 @@ async fn test_newline_above(cx: &mut gpui::TestAppContext) {
.unwrap(),
);
let mut cx = EditorTestContext::new(cx);
let mut cx = EditorTestContext::new(cx).await;
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
cx.set_state(indoc! {"
const a: ˇA = (
@@ -1685,7 +1685,7 @@ async fn test_newline_below(cx: &mut gpui::TestAppContext) {
.unwrap(),
);
let mut cx = EditorTestContext::new(cx);
let mut cx = EditorTestContext::new(cx).await;
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
cx.set_state(indoc! {"
const a: ˇA = (
@@ -1751,7 +1751,7 @@ async fn test_tab(cx: &mut gpui::TestAppContext) {
settings.defaults.tab_size = NonZeroU32::new(3)
});
let mut cx = EditorTestContext::new(cx);
let mut cx = EditorTestContext::new(cx).await;
cx.set_state(indoc! {"
ˇabˇc
ˇ🏀ˇ🏀ˇefg
@@ -1779,7 +1779,7 @@ async fn test_tab(cx: &mut gpui::TestAppContext) {
async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorTestContext::new(cx);
let mut cx = EditorTestContext::new(cx).await;
let language = Arc::new(
Language::new(
LanguageConfig::default(),
@@ -1850,7 +1850,7 @@ async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) {
.unwrap(),
);
let mut cx = EditorTestContext::new(cx);
let mut cx = EditorTestContext::new(cx).await;
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
cx.set_state(indoc! {"
fn a() {
@@ -1876,7 +1876,7 @@ async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
settings.defaults.tab_size = NonZeroU32::new(4);
});
let mut cx = EditorTestContext::new(cx);
let mut cx = EditorTestContext::new(cx).await;
cx.set_state(indoc! {"
«oneˇ» «twoˇ»
@@ -1949,7 +1949,7 @@ async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
settings.defaults.hard_tabs = Some(true);
});
let mut cx = EditorTestContext::new(cx);
let mut cx = EditorTestContext::new(cx).await;
// select two ranges on one line
cx.set_state(indoc! {"
@@ -2156,7 +2156,7 @@ fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
async fn test_backspace(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorTestContext::new(cx);
let mut cx = EditorTestContext::new(cx).await;
// Basic backspace
cx.set_state(indoc! {"
@@ -2205,7 +2205,7 @@ async fn test_backspace(cx: &mut gpui::TestAppContext) {
async fn test_delete(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorTestContext::new(cx);
let mut cx = EditorTestContext::new(cx).await;
cx.set_state(indoc! {"
onˇe two three
fou«rˇ» five six
@@ -2559,7 +2559,7 @@ fn test_transpose(cx: &mut TestAppContext) {
async fn test_clipboard(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorTestContext::new(cx);
let mut cx = EditorTestContext::new(cx).await;
cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
cx.update_editor(|e, cx| e.cut(&Cut, cx));
@@ -2641,7 +2641,7 @@ async fn test_clipboard(cx: &mut gpui::TestAppContext) {
async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorTestContext::new(cx);
let mut cx = EditorTestContext::new(cx).await;
let language = Arc::new(Language::new(
LanguageConfig::default(),
Some(tree_sitter_rust::language()),
@@ -3085,7 +3085,7 @@ fn test_add_selection_above_below(cx: &mut TestAppContext) {
async fn test_select_next(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorTestContext::new(cx);
let mut cx = EditorTestContext::new(cx).await;
cx.set_state("abc\nˇabc abc\ndefabc\nabc");
cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx));
@@ -3314,7 +3314,7 @@ async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
async fn test_autoclose_pairs(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorTestContext::new(cx);
let mut cx = EditorTestContext::new(cx).await;
let language = Arc::new(Language::new(
LanguageConfig {
@@ -3485,7 +3485,7 @@ async fn test_autoclose_pairs(cx: &mut gpui::TestAppContext) {
async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorTestContext::new(cx);
let mut cx = EditorTestContext::new(cx).await;
let html_language = Arc::new(
Language::new(
@@ -3721,7 +3721,7 @@ async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorTestContext::new(cx);
let mut cx = EditorTestContext::new(cx).await;
let rust_language = Arc::new(
Language::new(
@@ -4938,7 +4938,7 @@ async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext)
let registry = Arc::new(LanguageRegistry::test());
registry.add(language.clone());
let mut cx = EditorTestContext::new(cx);
let mut cx = EditorTestContext::new(cx).await;
cx.update_buffer(|buffer, cx| {
buffer.set_language_registry(registry);
buffer.set_language(Some(language), cx);
@@ -5060,7 +5060,7 @@ async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext)
async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorTestContext::new(cx);
let mut cx = EditorTestContext::new(cx).await;
let html_language = Arc::new(
Language::new(
@@ -5985,7 +5985,7 @@ fn test_combine_syntax_and_fuzzy_match_highlights() {
async fn go_to_hunk(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorTestContext::new(cx);
let mut cx = EditorTestContext::new(cx).await;
let diff_base = r#"
use some::mod;

View File

@@ -40,7 +40,10 @@ use language::{
language_settings::ShowWhitespaceSetting, Bias, CursorShape, DiagnosticSeverity, OffsetUtf16,
Selection,
};
use project::ProjectPath;
use project::{
project_settings::{GitGutterSetting, ProjectSettings},
ProjectPath,
};
use smallvec::SmallVec;
use std::{
borrow::Cow,
@@ -51,7 +54,7 @@ use std::{
sync::Arc,
};
use text::Point;
use workspace::{item::Item, GitGutterSetting, WorkspaceSettings};
use workspace::item::Item;
enum FoldMarkers {}
@@ -551,11 +554,8 @@ impl EditorElement {
let scroll_top = scroll_position.y() * line_height;
let show_gutter = matches!(
settings::get::<WorkspaceSettings>(cx)
.git
.git_gutter
.unwrap_or_default(),
GitGutterSetting::TrackedFiles
settings::get::<ProjectSettings>(cx).git.git_gutter,
Some(GitGutterSetting::TrackedFiles)
);
if show_gutter {

View File

@@ -720,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 {

View File

@@ -66,6 +66,7 @@ pub enum Event {
},
Edited,
Reloaded,
DiffBaseChanged,
LanguageChanged,
Reparsed,
Saved,
@@ -343,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,
@@ -1312,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,
@@ -1550,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);
@@ -3870,10 +3868,13 @@ 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::SettingsStore;
use std::{env, rc::Rc};
@@ -4564,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();

View File

@@ -7,6 +7,7 @@ use gpui::{
};
use indoc::indoc;
use language::{Buffer, BufferSnapshot};
use project::{FakeFs, Project};
use std::{
any::TypeId,
ops::{Deref, DerefMut, Range},
@@ -25,11 +26,16 @@ 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.add_window(Default::default(), |cx| {
cx.focus_self();
build_editor(MultiBuffer::build_simple("", cx), cx)
build_editor(MultiBuffer::build_from_buffer(buffer, cx), cx)
})
});

View File

@@ -1577,6 +1577,7 @@ mod tests {
super::init(cx);
editor::init(cx);
workspace::init_settings(cx);
Project::init_settings(cx);
state
})
}

View File

@@ -161,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();

View File

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

@@ -876,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 {

View File

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

View File

@@ -796,6 +796,12 @@ impl LanguageRegistry {
http_client: Arc<dyn HttpClient>,
cx: &mut AppContext,
) -> Option<PendingLanguageServer> {
let server_id = self.state.write().next_language_server_id();
log::info!(
"starting language server name:{}, path:{root_path:?}, id:{server_id}",
adapter.name.0
);
#[cfg(any(test, feature = "test-support"))]
if language.fake_adapter.is_some() {
let task = cx.spawn(|cx| async move {
@@ -825,7 +831,6 @@ impl LanguageRegistry {
Ok(server)
});
let server_id = self.state.write().next_language_server_id();
return Some(PendingLanguageServer { server_id, task });
}
@@ -834,7 +839,6 @@ impl LanguageRegistry {
.clone()
.ok_or_else(|| anyhow!("language server download directory has not been assigned"))
.log_err()?;
let this = self.clone();
let language = language.clone();
let http_client = http_client.clone();
@@ -843,7 +847,6 @@ impl LanguageRegistry {
let adapter = adapter.clone();
let lsp_binary_statuses = self.lsp_binary_statuses_tx.clone();
let login_shell_env_loaded = self.login_shell_env_loaded.clone();
let server_id = self.state.write().next_language_server_id();
let task = cx.spawn(|cx| async move {
login_shell_env_loaded.await;

View File

@@ -20,7 +20,7 @@ anyhow.workspace = true
async-pipe = { git = "https://github.com/zed-industries/async-pipe-rs", rev = "82d00a04211cf4e1236029aa03e6b6ce2a74c553", optional = true }
futures.workspace = true
log.workspace = true
lsp-types = "0.91"
lsp-types = "0.94"
parking_lot.workspace = true
postage.workspace = true
serde.workspace = true

View File

@@ -361,13 +361,18 @@ impl LanguageServer {
capabilities: ClientCapabilities {
workspace: Some(WorkspaceClientCapabilities {
configuration: Some(true),
did_change_watched_files: Some(DynamicRegistrationClientCapabilities {
did_change_watched_files: Some(DidChangeWatchedFilesClientCapabilities {
dynamic_registration: Some(true),
relative_pattern_support: Some(true),
}),
did_change_configuration: Some(DynamicRegistrationClientCapabilities {
dynamic_registration: Some(true),
}),
workspace_folders: Some(true),
symbol: Some(WorkspaceSymbolClientCapabilities {
resolve_support: None,
..WorkspaceSymbolClientCapabilities::default()
}),
..Default::default()
}),
text_document: Some(TextDocumentClientCapabilities {
@@ -849,10 +854,12 @@ impl FakeLanguageServer {
T: request::Request,
T::Result: 'static + Send,
{
self.server.executor.start_waiting();
self.server.request::<T>(params).await
}
pub async fn receive_notification<T: notification::Notification>(&mut self) -> T::Params {
self.server.executor.start_waiting();
self.try_receive_notification::<T>().await.unwrap()
}

View File

@@ -8,14 +8,24 @@ use client::proto::{self, PeerId};
use fs::LineEnding;
use gpui::{AppContext, AsyncAppContext, ModelHandle};
use language::{
language_settings::language_settings,
point_from_lsp, point_to_lsp,
proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, CachedLspAdapter, CharKind, CodeAction,
Completion, OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Unclipped,
Completion, OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Transaction, Unclipped,
};
use lsp::{DocumentHighlightKind, LanguageServer, LanguageServerId, ServerCapabilities};
use std::{cmp::Reverse, ops::Range, path::Path, sync::Arc};
pub fn lsp_formatting_options(tab_size: u32) -> lsp::FormattingOptions {
lsp::FormattingOptions {
tab_size,
insert_spaces: true,
insert_final_newline: Some(true),
..lsp::FormattingOptions::default()
}
}
#[async_trait(?Send)]
pub(crate) trait LspCommand: 'static + Sized {
type Response: 'static + Default + Send;
@@ -109,6 +119,25 @@ pub(crate) struct GetCodeActions {
pub range: Range<Anchor>,
}
pub(crate) struct OnTypeFormatting {
pub position: PointUtf16,
pub trigger: String,
pub options: FormattingOptions,
pub push_to_history: bool,
}
pub(crate) struct FormattingOptions {
tab_size: u32,
}
impl From<lsp::FormattingOptions> for FormattingOptions {
fn from(value: lsp::FormattingOptions) -> Self {
Self {
tab_size: value.tab_size,
}
}
}
#[async_trait(?Send)]
impl LspCommand for PrepareRename {
type Response = Option<Range<Anchor>>;
@@ -1495,6 +1524,7 @@ impl LspCommand for GetCodeActions {
context: lsp::CodeActionContext {
diagnostics: relevant_diagnostics,
only: language_server.code_action_kinds(),
..lsp::CodeActionContext::default()
},
}
}
@@ -1596,3 +1626,134 @@ impl LspCommand for GetCodeActions {
message.buffer_id
}
}
#[async_trait(?Send)]
impl LspCommand for OnTypeFormatting {
type Response = Option<Transaction>;
type LspRequest = lsp::request::OnTypeFormatting;
type ProtoRequest = proto::OnTypeFormatting;
fn check_capabilities(&self, server_capabilities: &lsp::ServerCapabilities) -> bool {
let Some(on_type_formatting_options) = &server_capabilities.document_on_type_formatting_provider else { return false };
on_type_formatting_options
.first_trigger_character
.contains(&self.trigger)
|| on_type_formatting_options
.more_trigger_character
.iter()
.flatten()
.any(|chars| chars.contains(&self.trigger))
}
fn to_lsp(
&self,
path: &Path,
_: &Buffer,
_: &Arc<LanguageServer>,
_: &AppContext,
) -> lsp::DocumentOnTypeFormattingParams {
lsp::DocumentOnTypeFormattingParams {
text_document_position: lsp::TextDocumentPositionParams::new(
lsp::TextDocumentIdentifier::new(lsp::Url::from_file_path(path).unwrap()),
point_to_lsp(self.position),
),
ch: self.trigger.clone(),
options: lsp_formatting_options(self.options.tab_size),
}
}
async fn response_from_lsp(
self,
message: Option<Vec<lsp::TextEdit>>,
project: ModelHandle<Project>,
buffer: ModelHandle<Buffer>,
server_id: LanguageServerId,
mut cx: AsyncAppContext,
) -> Result<Option<Transaction>> {
if let Some(edits) = message {
let (lsp_adapter, lsp_server) =
language_server_for_buffer(&project, &buffer, server_id, &mut cx)?;
Project::deserialize_edits(
project,
buffer,
edits,
self.push_to_history,
lsp_adapter,
lsp_server,
&mut cx,
)
.await
} else {
Ok(None)
}
}
fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::OnTypeFormatting {
proto::OnTypeFormatting {
project_id,
buffer_id: buffer.remote_id(),
position: Some(language::proto::serialize_anchor(
&buffer.anchor_before(self.position),
)),
trigger: self.trigger.clone(),
version: serialize_version(&buffer.version()),
}
}
async fn from_proto(
message: proto::OnTypeFormatting,
_: ModelHandle<Project>,
buffer: ModelHandle<Buffer>,
mut cx: AsyncAppContext,
) -> Result<Self> {
let position = message
.position
.and_then(deserialize_anchor)
.ok_or_else(|| anyhow!("invalid position"))?;
buffer
.update(&mut cx, |buffer, _| {
buffer.wait_for_version(deserialize_version(&message.version))
})
.await?;
let tab_size = buffer.read_with(&cx, |buffer, cx| {
let language_name = buffer.language().map(|language| language.name());
language_settings(language_name.as_deref(), cx).tab_size
});
Ok(Self {
position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer)),
trigger: message.trigger.clone(),
options: lsp_formatting_options(tab_size.get()).into(),
push_to_history: false,
})
}
fn response_to_proto(
response: Option<Transaction>,
_: &mut Project,
_: PeerId,
_: &clock::Global,
_: &mut AppContext,
) -> proto::OnTypeFormattingResponse {
proto::OnTypeFormattingResponse {
transaction: response
.map(|transaction| language::proto::serialize_transaction(&transaction)),
}
}
async fn response_from_proto(
self,
message: proto::OnTypeFormattingResponse,
_: ModelHandle<Project>,
_: ModelHandle<Buffer>,
_: AsyncAppContext,
) -> Result<Option<Transaction>> {
let Some(transaction) = message.transaction else { return Ok(None) };
Ok(Some(language::proto::deserialize_transaction(transaction)?))
}
fn buffer_id_from_proto(message: &proto::OnTypeFormatting) -> u64 {
message.buffer_id
}
}

View File

@@ -1,6 +1,6 @@
mod ignore;
mod lsp_command;
mod project_settings;
pub mod project_settings;
pub mod search;
pub mod terminals;
pub mod worktree;
@@ -14,7 +14,10 @@ use clock::ReplicaId;
use collections::{hash_map, BTreeMap, HashMap, HashSet};
use copilot::Copilot;
use futures::{
channel::mpsc::{self, UnboundedReceiver},
channel::{
mpsc::{self, UnboundedReceiver},
oneshot,
},
future::{try_join_all, Shared},
stream::FuturesUnordered,
AsyncWriteExt, Future, FutureExt, StreamExt, TryFutureExt,
@@ -37,6 +40,7 @@ use language::{
PendingLanguageServer, PointUtf16, RopeFingerprint, TextBufferSnapshot, ToOffset, ToPointUtf16,
Transaction, Unclipped,
};
use log::error;
use lsp::{
DiagnosticSeverity, DiagnosticTag, DidChangeWatchedFilesRegistrationOptions,
DocumentHighlightKind, LanguageServer, LanguageServerId,
@@ -130,6 +134,8 @@ pub struct Project {
incomplete_remote_buffers: HashMap<u64, Option<ModelHandle<Buffer>>>,
buffer_snapshots: HashMap<u64, HashMap<LanguageServerId, Vec<LspBufferSnapshot>>>, // buffer_id -> server_id -> vec of snapshots
buffers_being_formatted: HashSet<u64>,
buffers_needing_diff: HashSet<WeakModelHandle<Buffer>>,
git_diff_debouncer: DelayedDebounced,
nonce: u128,
_maintain_buffer_languages: Task<()>,
_maintain_workspace_config: Task<()>,
@@ -137,6 +143,49 @@ pub struct Project {
copilot_enabled: bool,
}
struct DelayedDebounced {
task: Option<Task<()>>,
cancel_channel: Option<oneshot::Sender<()>>,
}
impl DelayedDebounced {
fn new() -> DelayedDebounced {
DelayedDebounced {
task: None,
cancel_channel: None,
}
}
fn fire_new<F>(&mut self, delay: Duration, cx: &mut ModelContext<Project>, func: F)
where
F: 'static + FnOnce(&mut Project, &mut ModelContext<Project>) -> Task<()>,
{
if let Some(channel) = self.cancel_channel.take() {
_ = channel.send(());
}
let (sender, mut receiver) = oneshot::channel::<()>();
self.cancel_channel = Some(sender);
let previous_task = self.task.take();
self.task = Some(cx.spawn(|workspace, mut cx| async move {
let mut timer = cx.background().timer(delay).fuse();
if let Some(previous_task) = previous_task {
previous_task.await;
}
futures::select_biased! {
_ = receiver => return,
_ = timer => {}
}
workspace
.update(&mut cx, |workspace, cx| (func)(workspace, cx))
.await;
}));
}
}
struct LspBufferSnapshot {
version: i32,
snapshot: TextBufferSnapshot,
@@ -417,6 +466,7 @@ impl Project {
client.add_model_request_handler(Self::handle_delete_project_entry);
client.add_model_request_handler(Self::handle_apply_additional_edits_for_completion);
client.add_model_request_handler(Self::handle_apply_code_action);
client.add_model_request_handler(Self::handle_on_type_formatting);
client.add_model_request_handler(Self::handle_reload_buffers);
client.add_model_request_handler(Self::handle_synchronize_buffers);
client.add_model_request_handler(Self::handle_format_buffers);
@@ -483,6 +533,8 @@ impl Project {
language_server_statuses: Default::default(),
last_workspace_edits_by_language_server: Default::default(),
buffers_being_formatted: Default::default(),
buffers_needing_diff: Default::default(),
git_diff_debouncer: DelayedDebounced::new(),
nonce: StdRng::from_entropy().gen(),
terminals: Terminals {
local_handles: Vec::new(),
@@ -572,6 +624,8 @@ impl Project {
last_workspace_edits_by_language_server: Default::default(),
opened_buffers: Default::default(),
buffers_being_formatted: Default::default(),
buffers_needing_diff: Default::default(),
git_diff_debouncer: DelayedDebounced::new(),
buffer_snapshots: Default::default(),
nonce: StdRng::from_entropy().gen(),
terminals: Terminals {
@@ -1406,7 +1460,7 @@ impl Project {
};
cx.foreground().spawn(async move {
pump_loading_buffer_reciever(loading_watch)
wait_for_loading_buffer(loading_watch)
.await
.map_err(|error| anyhow!("{}", error))
})
@@ -1606,6 +1660,7 @@ impl Project {
buffer: &ModelHandle<Buffer>,
cx: &mut ModelContext<Self>,
) -> Result<()> {
self.request_buffer_diff_recalculation(buffer, cx);
buffer.update(cx, |buffer, _| {
buffer.set_language_registry(self.languages.clone())
});
@@ -1923,6 +1978,13 @@ impl Project {
event: &BufferEvent,
cx: &mut ModelContext<Self>,
) -> Option<()> {
if matches!(
event,
BufferEvent::Edited { .. } | BufferEvent::Reloaded | BufferEvent::DiffBaseChanged
) {
self.request_buffer_diff_recalculation(&buffer, cx);
}
match event {
BufferEvent::Operation(operation) => {
self.buffer_ordered_messages_tx
@@ -2062,6 +2124,74 @@ impl Project {
None
}
fn request_buffer_diff_recalculation(
&mut self,
buffer: &ModelHandle<Buffer>,
cx: &mut ModelContext<Self>,
) {
self.buffers_needing_diff.insert(buffer.downgrade());
let first_insertion = self.buffers_needing_diff.len() == 1;
let settings = settings::get::<ProjectSettings>(cx);
let delay = if let Some(delay) = settings.git.gutter_debounce {
delay
} else {
if first_insertion {
let this = cx.weak_handle();
cx.defer(move |cx| {
if let Some(this) = this.upgrade(cx) {
this.update(cx, |this, cx| {
this.recalculate_buffer_diffs(cx).detach();
});
}
});
}
return;
};
const MIN_DELAY: u64 = 50;
let delay = delay.max(MIN_DELAY);
let duration = Duration::from_millis(delay);
self.git_diff_debouncer
.fire_new(duration, cx, move |this, cx| {
this.recalculate_buffer_diffs(cx)
});
}
fn recalculate_buffer_diffs(&mut self, cx: &mut ModelContext<Self>) -> Task<()> {
cx.spawn(|this, mut cx| async move {
let buffers: Vec<_> = this.update(&mut cx, |this, _| {
this.buffers_needing_diff.drain().collect()
});
let tasks: Vec<_> = this.update(&mut cx, |_, cx| {
buffers
.iter()
.filter_map(|buffer| {
let buffer = buffer.upgrade(cx)?;
buffer.update(cx, |buffer, cx| buffer.git_diff_recalc(cx))
})
.collect()
});
futures::future::join_all(tasks).await;
this.update(&mut cx, |this, cx| {
if !this.buffers_needing_diff.is_empty() {
this.recalculate_buffer_diffs(cx).detach();
} else {
// TODO: Would a `ModelContext<Project>.notify()` suffice here?
for buffer in buffers {
if let Some(buffer) = buffer.upgrade(cx) {
buffer.update(cx, |_, cx| cx.notify());
}
}
}
});
})
}
fn language_servers_for_worktree(
&self,
worktree_id: WorktreeId,
@@ -2888,10 +3018,12 @@ impl Project {
if let Some(worktree) = worktree.upgrade(cx) {
let worktree = worktree.read(cx);
if let Some(abs_path) = worktree.abs_path().to_str() {
if let Some(suffix) = watcher
.glob_pattern
.strip_prefix(abs_path)
.and_then(|s| s.strip_prefix(std::path::MAIN_SEPARATOR))
if let Some(suffix) = match &watcher.glob_pattern {
lsp::GlobPattern::String(s) => s,
lsp::GlobPattern::Relative(rp) => &rp.pattern,
}
.strip_prefix(abs_path)
.and_then(|s| s.strip_prefix(std::path::MAIN_SEPARATOR))
{
if let Some(glob) = Glob::new(suffix).log_err() {
builders
@@ -3476,12 +3608,7 @@ impl Project {
language_server
.request::<lsp::request::Formatting>(lsp::DocumentFormattingParams {
text_document,
options: lsp::FormattingOptions {
tab_size: tab_size.into(),
insert_spaces: true,
insert_final_newline: Some(true),
..Default::default()
},
options: lsp_command::lsp_formatting_options(tab_size.get()),
work_done_progress_params: Default::default(),
})
.await?
@@ -3497,12 +3624,7 @@ impl Project {
.request::<lsp::request::RangeFormatting>(lsp::DocumentRangeFormattingParams {
text_document,
range: lsp::Range::new(buffer_start, buffer_end),
options: lsp::FormattingOptions {
tab_size: tab_size.into(),
insert_spaces: true,
insert_final_newline: Some(true),
..Default::default()
},
options: lsp_command::lsp_formatting_options(tab_size.get()),
work_done_progress_params: Default::default(),
})
.await?
@@ -3640,7 +3762,7 @@ impl Project {
let worktree_abs_path = worktree.abs_path().clone();
requests.push(
server
.request::<lsp::request::WorkspaceSymbol>(
.request::<lsp::request::WorkspaceSymbolRequest>(
lsp::WorkspaceSymbolParams {
query: query.to_string(),
..Default::default()
@@ -3648,12 +3770,32 @@ impl Project {
)
.log_err()
.map(move |response| {
let lsp_symbols = response.flatten().map(|symbol_response| match symbol_response {
lsp::WorkspaceSymbolResponse::Flat(flat_responses) => {
flat_responses.into_iter().map(|lsp_symbol| {
(lsp_symbol.name, lsp_symbol.kind, lsp_symbol.location)
}).collect::<Vec<_>>()
}
lsp::WorkspaceSymbolResponse::Nested(nested_responses) => {
nested_responses.into_iter().filter_map(|lsp_symbol| {
let location = match lsp_symbol.location {
lsp::OneOf::Left(location) => location,
lsp::OneOf::Right(_) => {
error!("Unexpected: client capabilities forbid symbol resolutions in workspace.symbol.resolveSupport");
return None
}
};
Some((lsp_symbol.name, lsp_symbol.kind, location))
}).collect::<Vec<_>>()
}
}).unwrap_or_default();
(
adapter,
language,
worktree_id,
worktree_abs_path,
response.unwrap_or_default(),
lsp_symbols,
)
}),
);
@@ -3675,53 +3817,54 @@ impl Project {
adapter_language,
source_worktree_id,
worktree_abs_path,
response,
lsp_symbols,
) in responses
{
symbols.extend(response.into_iter().flatten().filter_map(|lsp_symbol| {
let abs_path = lsp_symbol.location.uri.to_file_path().ok()?;
let mut worktree_id = source_worktree_id;
let path;
if let Some((worktree, rel_path)) =
this.find_local_worktree(&abs_path, cx)
{
worktree_id = worktree.read(cx).id();
path = rel_path;
} else {
path = relativize_path(&worktree_abs_path, &abs_path);
}
let project_path = ProjectPath {
worktree_id,
path: path.into(),
};
let signature = this.symbol_signature(&project_path);
let adapter_language = adapter_language.clone();
let language = this
.languages
.language_for_file(&project_path.path, None)
.unwrap_or_else(move |_| adapter_language);
let language_server_name = adapter.name.clone();
Some(async move {
let language = language.await;
let label = language
.label_for_symbol(&lsp_symbol.name, lsp_symbol.kind)
.await;
Symbol {
language_server_name,
source_worktree_id,
path: project_path,
label: label.unwrap_or_else(|| {
CodeLabel::plain(lsp_symbol.name.clone(), None)
}),
kind: lsp_symbol.kind,
name: lsp_symbol.name,
range: range_from_lsp(lsp_symbol.location.range),
signature,
symbols.extend(lsp_symbols.into_iter().filter_map(
|(symbol_name, symbol_kind, symbol_location)| {
let abs_path = symbol_location.uri.to_file_path().ok()?;
let mut worktree_id = source_worktree_id;
let path;
if let Some((worktree, rel_path)) =
this.find_local_worktree(&abs_path, cx)
{
worktree_id = worktree.read(cx).id();
path = rel_path;
} else {
path = relativize_path(&worktree_abs_path, &abs_path);
}
})
}));
let project_path = ProjectPath {
worktree_id,
path: path.into(),
};
let signature = this.symbol_signature(&project_path);
let adapter_language = adapter_language.clone();
let language = this
.languages
.language_for_file(&project_path.path, None)
.unwrap_or_else(move |_| adapter_language);
let language_server_name = adapter.name.clone();
Some(async move {
let language = language.await;
let label =
language.label_for_symbol(&symbol_name, symbol_kind).await;
Symbol {
language_server_name,
source_worktree_id,
path: project_path,
label: label.unwrap_or_else(|| {
CodeLabel::plain(symbol_name.clone(), None)
}),
kind: symbol_kind,
name: symbol_name,
range: range_from_lsp(symbol_location.range),
signature,
}
})
},
));
}
symbols
});
@@ -4044,6 +4187,109 @@ impl Project {
}
}
fn apply_on_type_formatting(
&self,
buffer: ModelHandle<Buffer>,
position: Anchor,
trigger: String,
cx: &mut ModelContext<Self>,
) -> Task<Result<Option<Transaction>>> {
if self.is_local() {
cx.spawn(|this, mut cx| async move {
// Do not allow multiple concurrent formatting requests for the
// same buffer.
this.update(&mut cx, |this, cx| {
this.buffers_being_formatted
.insert(buffer.read(cx).remote_id())
});
let _cleanup = defer({
let this = this.clone();
let mut cx = cx.clone();
let closure_buffer = buffer.clone();
move || {
this.update(&mut cx, |this, cx| {
this.buffers_being_formatted
.remove(&closure_buffer.read(cx).remote_id());
});
}
});
buffer
.update(&mut cx, |buffer, _| {
buffer.wait_for_edits(Some(position.timestamp))
})
.await?;
this.update(&mut cx, |this, cx| {
let position = position.to_point_utf16(buffer.read(cx));
this.on_type_format(buffer, position, trigger, false, cx)
})
.await
})
} else if let Some(project_id) = self.remote_id() {
let client = self.client.clone();
let request = proto::OnTypeFormatting {
project_id,
buffer_id: buffer.read(cx).remote_id(),
position: Some(serialize_anchor(&position)),
trigger,
version: serialize_version(&buffer.read(cx).version()),
};
cx.spawn(|_, _| async move {
client
.request(request)
.await?
.transaction
.map(language::proto::deserialize_transaction)
.transpose()
})
} else {
Task::ready(Err(anyhow!("project does not have a remote id")))
}
}
async fn deserialize_edits(
this: ModelHandle<Self>,
buffer_to_edit: ModelHandle<Buffer>,
edits: Vec<lsp::TextEdit>,
push_to_history: bool,
_: Arc<CachedLspAdapter>,
language_server: Arc<LanguageServer>,
cx: &mut AsyncAppContext,
) -> Result<Option<Transaction>> {
let edits = this
.update(cx, |this, cx| {
this.edits_from_lsp(
&buffer_to_edit,
edits,
language_server.server_id(),
None,
cx,
)
})
.await?;
let transaction = buffer_to_edit.update(cx, |buffer, cx| {
buffer.finalize_last_transaction();
buffer.start_transaction();
for (range, text) in edits {
buffer.edit([(range, text)], None, cx);
}
if buffer.end_transaction(cx).is_some() {
let transaction = buffer.finalize_last_transaction().unwrap().clone();
if !push_to_history {
buffer.forget_transaction(transaction.id);
}
Some(transaction)
} else {
None
}
});
Ok(transaction)
}
async fn deserialize_workspace_edit(
this: ModelHandle<Self>,
edit: lsp::WorkspaceEdit,
@@ -4209,6 +4455,31 @@ impl Project {
)
}
pub fn on_type_format<T: ToPointUtf16>(
&self,
buffer: ModelHandle<Buffer>,
position: T,
trigger: String,
push_to_history: bool,
cx: &mut ModelContext<Self>,
) -> Task<Result<Option<Transaction>>> {
let tab_size = buffer.read_with(cx, |buffer, cx| {
let language_name = buffer.language().map(|language| language.name());
language_settings(language_name.as_deref(), cx).tab_size
});
let position = position.to_point_utf16(buffer.read(cx));
self.request_lsp(
buffer.clone(),
OnTypeFormatting {
position,
trigger,
options: lsp_command::lsp_formatting_options(tab_size.get()).into(),
push_to_history,
},
cx,
)
}
#[allow(clippy::type_complexity)]
pub fn search(
&self,
@@ -4600,7 +4871,7 @@ impl Project {
if worktree.read(cx).is_local() {
cx.subscribe(worktree, |this, worktree, event, cx| match event {
worktree::Event::UpdatedEntries(changes) => {
this.update_local_worktree_buffers(&worktree, &changes, cx);
this.update_local_worktree_buffers(&worktree, changes, cx);
this.update_local_worktree_language_servers(&worktree, changes, cx);
}
worktree::Event::UpdatedGitRepositories(updated_repos) => {
@@ -4634,13 +4905,13 @@ impl Project {
fn update_local_worktree_buffers(
&mut self,
worktree_handle: &ModelHandle<Worktree>,
changes: &HashMap<(Arc<Path>, ProjectEntryId), PathChange>,
changes: &[(Arc<Path>, ProjectEntryId, PathChange)],
cx: &mut ModelContext<Self>,
) {
let snapshot = worktree_handle.read(cx).snapshot();
let mut renamed_buffers = Vec::new();
for (path, entry_id) in changes.keys() {
for (path, entry_id, _) in changes {
let worktree_id = worktree_handle.read(cx).id();
let project_path = ProjectPath {
worktree_id,
@@ -4746,7 +5017,7 @@ impl Project {
fn update_local_worktree_language_servers(
&mut self,
worktree_handle: &ModelHandle<Worktree>,
changes: &HashMap<(Arc<Path>, ProjectEntryId), PathChange>,
changes: &[(Arc<Path>, ProjectEntryId, PathChange)],
cx: &mut ModelContext<Self>,
) {
if changes.is_empty() {
@@ -4777,23 +5048,21 @@ impl Project {
let params = lsp::DidChangeWatchedFilesParams {
changes: changes
.iter()
.filter_map(|((path, _), change)| {
if watched_paths.is_match(&path) {
Some(lsp::FileEvent {
uri: lsp::Url::from_file_path(abs_path.join(path))
.unwrap(),
typ: match change {
PathChange::Added => lsp::FileChangeType::CREATED,
PathChange::Removed => lsp::FileChangeType::DELETED,
PathChange::Updated
| PathChange::AddedOrUpdated => {
lsp::FileChangeType::CHANGED
}
},
})
} else {
None
.filter_map(|(path, _, change)| {
if !watched_paths.is_match(&path) {
return None;
}
let typ = match change {
PathChange::Loaded => return None,
PathChange::Added => lsp::FileChangeType::CREATED,
PathChange::Removed => lsp::FileChangeType::DELETED,
PathChange::Updated => lsp::FileChangeType::CHANGED,
PathChange::AddedOrUpdated => lsp::FileChangeType::CHANGED,
};
Some(lsp::FileEvent {
uri: lsp::Url::from_file_path(abs_path.join(path)).unwrap(),
typ,
})
})
.collect(),
};
@@ -4812,98 +5081,102 @@ impl Project {
fn update_local_worktree_buffers_git_repos(
&mut self,
worktree_handle: ModelHandle<Worktree>,
repos: &HashMap<Arc<Path>, LocalRepositoryEntry>,
changed_repos: &UpdatedGitRepositoriesSet,
cx: &mut ModelContext<Self>,
) {
debug_assert!(worktree_handle.read(cx).is_local());
// Setup the pending buffers
// Identify the loading buffers whose containing repository that has changed.
let future_buffers = self
.loading_buffers_by_path
.iter()
.filter_map(|(path, receiver)| {
let path = &path.path;
let (work_directory, repo) = repos
.iter()
.find(|(work_directory, _)| path.starts_with(work_directory))?;
let repo_relative_path = path.strip_prefix(work_directory).log_err()?;
.filter_map(|(project_path, receiver)| {
if project_path.worktree_id != worktree_handle.read(cx).id() {
return None;
}
let path = &project_path.path;
changed_repos.iter().find(|(work_dir, change)| {
path.starts_with(work_dir) && change.git_dir_changed
})?;
let receiver = receiver.clone();
let repo_ptr = repo.repo_ptr.clone();
let repo_relative_path = repo_relative_path.to_owned();
let path = path.clone();
Some(async move {
pump_loading_buffer_reciever(receiver)
wait_for_loading_buffer(receiver)
.await
.ok()
.map(|buffer| (buffer, repo_relative_path, repo_ptr))
.map(|buffer| (buffer, path))
})
})
.collect::<FuturesUnordered<_>>()
.filter_map(|result| async move {
let (buffer_handle, repo_relative_path, repo_ptr) = result?;
.collect::<FuturesUnordered<_>>();
let lock = repo_ptr.lock();
lock.load_index_text(&repo_relative_path)
.map(|diff_base| (diff_base, buffer_handle))
});
// Identify the current buffers whose containing repository has changed.
let current_buffers = self
.opened_buffers
.values()
.filter_map(|buffer| {
let buffer = buffer.upgrade(cx)?;
let file = File::from_dyn(buffer.read(cx).file())?;
if file.worktree != worktree_handle {
return None;
}
let path = file.path();
changed_repos.iter().find(|(work_dir, change)| {
path.starts_with(work_dir) && change.git_dir_changed
})?;
Some((buffer, path.clone()))
})
.collect::<Vec<_>>();
let update_diff_base_fn = update_diff_base(self);
cx.spawn(|_, mut cx| async move {
let diff_base_tasks = cx
if future_buffers.len() + current_buffers.len() == 0 {
return;
}
let remote_id = self.remote_id();
let client = self.client.clone();
cx.spawn_weak(move |_, mut cx| async move {
// Wait for all of the buffers to load.
let future_buffers = future_buffers.collect::<Vec<_>>().await;
// Reload the diff base for every buffer whose containing git repository has changed.
let snapshot =
worktree_handle.read_with(&cx, |tree, _| tree.as_local().unwrap().snapshot());
let diff_bases_by_buffer = cx
.background()
.spawn(future_buffers.collect::<Vec<_>>())
.spawn(async move {
future_buffers
.into_iter()
.filter_map(|e| e)
.chain(current_buffers)
.filter_map(|(buffer, path)| {
let (work_directory, repo) =
snapshot.repository_and_work_directory_for_path(&path)?;
let repo = snapshot.get_local_repo(&repo)?;
let relative_path = path.strip_prefix(&work_directory).ok()?;
let base_text = repo.repo_ptr.lock().load_index_text(&relative_path);
Some((buffer, base_text))
})
.collect::<Vec<_>>()
})
.await;
for (diff_base, buffer) in diff_base_tasks.into_iter() {
update_diff_base_fn(Some(diff_base), buffer, &mut cx);
// Assign the new diff bases on all of the buffers.
for (buffer, diff_base) in diff_bases_by_buffer {
let buffer_id = buffer.update(&mut cx, |buffer, cx| {
buffer.set_diff_base(diff_base.clone(), cx);
buffer.remote_id()
});
if let Some(project_id) = remote_id {
client
.send(proto::UpdateDiffBase {
project_id,
buffer_id,
diff_base,
})
.log_err();
}
}
})
.detach();
// And the current buffers
for (_, buffer) in &self.opened_buffers {
if let Some(buffer) = buffer.upgrade(cx) {
let file = match File::from_dyn(buffer.read(cx).file()) {
Some(file) => file,
None => continue,
};
if file.worktree != worktree_handle {
continue;
}
let path = file.path().clone();
let worktree = worktree_handle.read(cx);
let (work_directory, repo) = match repos
.iter()
.find(|(work_directory, _)| path.starts_with(work_directory))
{
Some(repo) => repo.clone(),
None => continue,
};
let relative_repo = match path.strip_prefix(work_directory).log_err() {
Some(relative_repo) => relative_repo.to_owned(),
None => continue,
};
drop(worktree);
let update_diff_base_fn = update_diff_base(self);
let git_ptr = repo.repo_ptr.clone();
let diff_base_task = cx
.background()
.spawn(async move { git_ptr.lock().load_index_text(&relative_repo) });
cx.spawn(|_, mut cx| async move {
let diff_base = diff_base_task.await;
update_diff_base_fn(diff_base, buffer, &mut cx);
})
.detach();
}
}
}
pub fn set_active_path(&mut self, entry: Option<ProjectPath>, cx: &mut ModelContext<Self>) {
@@ -4991,6 +5264,20 @@ impl Project {
Some(ProjectPath { worktree_id, path })
}
pub fn absolute_path(&self, project_path: &ProjectPath, cx: &AppContext) -> Option<PathBuf> {
let workspace_root = self
.worktree_for_id(project_path.worktree_id, cx)?
.read(cx)
.abs_path();
let project_path = project_path.path.as_ref();
Some(if project_path == Path::new("") {
workspace_root.to_path_buf()
} else {
workspace_root.join(project_path)
})
}
// RPC message handlers
async fn handle_unshare_project(
@@ -5601,7 +5888,7 @@ impl Project {
this.update(&mut cx, |this, cx| {
let Some(guest_id) = envelope.original_sender_id else {
log::error!("missing original_sender_id on SynchronizeBuffers request");
error!("missing original_sender_id on SynchronizeBuffers request");
return;
};
@@ -5779,6 +6066,38 @@ impl Project {
})
}
async fn handle_on_type_formatting(
this: ModelHandle<Self>,
envelope: TypedEnvelope<proto::OnTypeFormatting>,
_: Arc<Client>,
mut cx: AsyncAppContext,
) -> Result<proto::OnTypeFormattingResponse> {
let on_type_formatting = this.update(&mut cx, |this, cx| {
let buffer = this
.opened_buffers
.get(&envelope.payload.buffer_id)
.and_then(|buffer| buffer.upgrade(cx))
.ok_or_else(|| anyhow!("unknown buffer id {}", envelope.payload.buffer_id))?;
let position = envelope
.payload
.position
.and_then(deserialize_anchor)
.ok_or_else(|| anyhow!("invalid position"))?;
Ok::<_, anyhow::Error>(this.apply_on_type_formatting(
buffer,
position,
envelope.payload.trigger.clone(),
cx,
))
})?;
let transaction = on_type_formatting
.await?
.as_ref()
.map(language::proto::serialize_transaction);
Ok(proto::OnTypeFormattingResponse { transaction })
}
async fn handle_lsp_command<T: LspCommand>(
this: ModelHandle<Self>,
envelope: TypedEnvelope<T::ProtoRequest>,
@@ -6038,11 +6357,13 @@ impl Project {
let Some(this) = this.upgrade(&cx) else {
return Err(anyhow!("project dropped"));
};
let buffer = this.read_with(&cx, |this, cx| {
this.opened_buffers
.get(&id)
.and_then(|buffer| buffer.upgrade(cx))
});
if let Some(buffer) = buffer {
break buffer;
} else if this.read_with(&cx, |this, _| this.is_read_only()) {
@@ -6053,12 +6374,13 @@ impl Project {
this.incomplete_remote_buffers.entry(id).or_default();
});
drop(this);
opened_buffer_rx
.next()
.await
.ok_or_else(|| anyhow!("project dropped while waiting for buffer"))?;
};
buffer.update(&mut cx, |buffer, cx| buffer.git_diff_recalc(cx));
Ok(buffer)
})
}
@@ -6790,7 +7112,7 @@ impl Item for Buffer {
}
}
async fn pump_loading_buffer_reciever(
async fn wait_for_loading_buffer(
mut receiver: postage::watch::Receiver<Option<Result<ModelHandle<Buffer>, Arc<anyhow::Error>>>>,
) -> Result<ModelHandle<Buffer>, Arc<anyhow::Error>> {
loop {
@@ -6803,26 +7125,3 @@ async fn pump_loading_buffer_reciever(
receiver.next().await;
}
}
fn update_diff_base(
project: &Project,
) -> impl Fn(Option<String>, ModelHandle<Buffer>, &mut AsyncAppContext) {
let remote_id = project.remote_id();
let client = project.client().clone();
move |diff_base, buffer, cx| {
let buffer_id = buffer.update(cx, |buffer, cx| {
buffer.set_diff_base(diff_base.clone(), cx);
buffer.remote_id()
});
if let Some(project_id) = remote_id {
client
.send(proto::UpdateDiffBase {
project_id,
buffer_id: buffer_id as u64,
diff_base,
})
.log_err();
}
}
}

View File

@@ -8,6 +8,22 @@ use std::sync::Arc;
pub struct ProjectSettings {
#[serde(default)]
pub lsp: HashMap<Arc<str>, LspSettings>,
#[serde(default)]
pub git: GitSettings,
}
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
pub struct GitSettings {
pub git_gutter: Option<GitGutterSetting>,
pub gutter_debounce: Option<u64>,
}
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum GitGutterSetting {
#[default]
TrackedFiles,
Hide,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]

View File

@@ -506,7 +506,9 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
register_options: serde_json::to_value(
lsp::DidChangeWatchedFilesRegistrationOptions {
watchers: vec![lsp::FileSystemWatcher {
glob_pattern: "/the-root/*.{rs,c}".to_string(),
glob_pattern: lsp::GlobPattern::String(
"/the-root/*.{rs,c}".to_string(),
),
kind: None,
}],
},
@@ -1193,7 +1195,7 @@ async fn test_toggling_enable_language_server(cx: &mut gpui::TestAppContext) {
.await;
}
#[gpui::test]
#[gpui::test(iterations = 3)]
async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) {
init_test(cx);
@@ -1273,6 +1275,7 @@ async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) {
// The diagnostics have moved down since they were created.
buffer.next_notification(cx).await;
cx.foreground().run_until_parked();
buffer.read_with(cx, |buffer, _| {
assert_eq!(
buffer
@@ -1351,6 +1354,7 @@ async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) {
});
buffer.next_notification(cx).await;
cx.foreground().run_until_parked();
buffer.read_with(cx, |buffer, _| {
assert_eq!(
buffer
@@ -1443,6 +1447,7 @@ async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) {
});
buffer.next_notification(cx).await;
cx.foreground().run_until_parked();
buffer.read_with(cx, |buffer, _| {
assert_eq!(
buffer
@@ -2523,29 +2528,21 @@ async fn test_rescan_and_remote_updates(
// Create a remote copy of this worktree.
let tree = project.read_with(cx, |project, cx| project.worktrees(cx).next().unwrap());
let initial_snapshot = tree.read_with(cx, |tree, _| tree.as_local().unwrap().snapshot());
let remote = cx.update(|cx| {
Worktree::remote(
1,
1,
proto::WorktreeMetadata {
id: initial_snapshot.id().to_proto(),
root_name: initial_snapshot.root_name().into(),
abs_path: initial_snapshot
.abs_path()
.as_os_str()
.to_string_lossy()
.into(),
visible: true,
},
rpc.clone(),
cx,
)
});
remote.update(cx, |remote, _| {
let update = initial_snapshot.build_initial_update(1);
remote.as_remote_mut().unwrap().update_from_remote(update);
let metadata = tree.read_with(cx, |tree, _| tree.as_local().unwrap().metadata_proto());
let updates = Arc::new(Mutex::new(Vec::new()));
tree.update(cx, |tree, cx| {
let _ = tree.as_local_mut().unwrap().observe_updates(0, cx, {
let updates = updates.clone();
move |update| {
updates.lock().push(update);
async { true }
}
});
});
let remote = cx.update(|cx| Worktree::remote(1, 1, metadata, rpc.clone(), cx));
deterministic.run_until_parked();
cx.read(|cx| {
@@ -2611,14 +2608,11 @@ async fn test_rescan_and_remote_updates(
// Update the remote worktree. Check that it becomes consistent with the
// local worktree.
remote.update(cx, |remote, cx| {
let update = tree.read(cx).as_local().unwrap().snapshot().build_update(
&initial_snapshot,
1,
1,
true,
);
remote.as_remote_mut().unwrap().update_from_remote(update);
deterministic.run_until_parked();
remote.update(cx, |remote, _| {
for update in updates.lock().drain(..) {
remote.as_remote_mut().unwrap().update_from_remote(update);
}
});
deterministic.run_until_parked();
remote.read_with(cx, |remote, _| {

File diff suppressed because it is too large Load Diff

View File

@@ -527,18 +527,22 @@ impl ProjectPanel {
let entry = worktree.read(cx).entry_for_id(edit_state.entry_id)?.clone();
let filename = self.filename_editor.read(cx).text(cx);
let path_already_exists = |path| worktree.read(cx).entry_for_path(path).is_some();
let edit_task;
let edited_entry_id;
if is_new_entry {
self.selection = Some(Selection {
worktree_id,
entry_id: NEW_ENTRY_ID,
});
let new_path = entry.path.join(&filename);
if path_already_exists(new_path.as_path()) {
return None;
}
edited_entry_id = NEW_ENTRY_ID;
edit_task = self.project.update(cx, |project, cx| {
project.create_entry((worktree_id, new_path), is_dir, cx)
project.create_entry((worktree_id, &new_path), is_dir, cx)
})?;
} else {
let new_path = if let Some(parent) = entry.path.clone().parent() {
@@ -546,9 +550,13 @@ impl ProjectPanel {
} else {
filename.clone().into()
};
if path_already_exists(new_path.as_path()) {
return None;
}
edited_entry_id = entry.id;
edit_task = self.project.update(cx, |project, cx| {
project.rename_entry(entry.id, new_path, cx)
project.rename_entry(entry.id, new_path.as_path(), cx)
})?;
};
@@ -2126,6 +2134,152 @@ mod tests {
ensure_no_open_items_and_panes(window_id, &workspace, cx);
}
#[gpui::test]
async fn test_create_duplicate_items(cx: &mut gpui::TestAppContext) {
init_test_with_editor(cx);
let fs = FakeFs::new(cx.background());
fs.insert_tree(
"/src",
json!({
"test": {
"first.rs": "// First Rust file",
"second.rs": "// Second Rust file",
"third.rs": "// Third Rust file",
}
}),
)
.await;
let project = Project::test(fs.clone(), ["/src".as_ref()], cx).await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
select_path(&panel, "src/", cx);
panel.update(cx, |panel, cx| panel.confirm(&Confirm, cx));
cx.foreground().run_until_parked();
assert_eq!(
visible_entries_as_strings(&panel, 0..10, cx),
&["v src <== selected", " > test"]
);
panel.update(cx, |panel, cx| panel.new_directory(&NewDirectory, cx));
cx.read_window(window_id, |cx| {
let panel = panel.read(cx);
assert!(panel.filename_editor.is_focused(cx));
});
assert_eq!(
visible_entries_as_strings(&panel, 0..10, cx),
&["v src", " > [EDITOR: ''] <== selected", " > test"]
);
panel.update(cx, |panel, cx| {
panel
.filename_editor
.update(cx, |editor, cx| editor.set_text("test", cx));
assert!(
panel.confirm(&Confirm, cx).is_none(),
"Should not allow to confirm on conflicting new directory name"
)
});
assert_eq!(
visible_entries_as_strings(&panel, 0..10, cx),
&["v src", " > test"],
"File list should be unchanged after failed folder create confirmation"
);
select_path(&panel, "src/test/", cx);
panel.update(cx, |panel, cx| panel.confirm(&Confirm, cx));
cx.foreground().run_until_parked();
assert_eq!(
visible_entries_as_strings(&panel, 0..10, cx),
&["v src", " > test <== selected"]
);
panel.update(cx, |panel, cx| panel.new_file(&NewFile, cx));
cx.read_window(window_id, |cx| {
let panel = panel.read(cx);
assert!(panel.filename_editor.is_focused(cx));
});
assert_eq!(
visible_entries_as_strings(&panel, 0..10, cx),
&[
"v src",
" v test",
" [EDITOR: ''] <== selected",
" first.rs",
" second.rs",
" third.rs"
]
);
panel.update(cx, |panel, cx| {
panel
.filename_editor
.update(cx, |editor, cx| editor.set_text("first.rs", cx));
assert!(
panel.confirm(&Confirm, cx).is_none(),
"Should not allow to confirm on conflicting new file name"
)
});
assert_eq!(
visible_entries_as_strings(&panel, 0..10, cx),
&[
"v src",
" v test",
" first.rs",
" second.rs",
" third.rs"
],
"File list should be unchanged after failed file create confirmation"
);
select_path(&panel, "src/test/first.rs", cx);
panel.update(cx, |panel, cx| panel.confirm(&Confirm, cx));
cx.foreground().run_until_parked();
assert_eq!(
visible_entries_as_strings(&panel, 0..10, cx),
&[
"v src",
" v test",
" first.rs <== selected",
" second.rs",
" third.rs"
],
);
panel.update(cx, |panel, cx| panel.rename(&Rename, cx));
cx.read_window(window_id, |cx| {
let panel = panel.read(cx);
assert!(panel.filename_editor.is_focused(cx));
});
assert_eq!(
visible_entries_as_strings(&panel, 0..10, cx),
&[
"v src",
" v test",
" [EDITOR: 'first.rs'] <== selected",
" second.rs",
" third.rs"
]
);
panel.update(cx, |panel, cx| {
panel
.filename_editor
.update(cx, |editor, cx| editor.set_text("second.rs", cx));
assert!(
panel.confirm(&Confirm, cx).is_none(),
"Should not allow to confirm on conflicting file rename"
)
});
assert_eq!(
visible_entries_as_strings(&panel, 0..10, cx),
&[
"v src",
" v test",
" first.rs <== selected",
" second.rs",
" third.rs"
],
"File list should be unchanged after failed rename confirmation"
);
}
fn toggle_expand_dir(
panel: &ViewHandle<ProjectPanel>,
path: impl AsRef<Path>,
@@ -2229,6 +2383,7 @@ mod tests {
editor::init_settings(cx);
crate::init(cx);
workspace::init_settings(cx);
Project::init_settings(cx);
});
}
@@ -2243,6 +2398,7 @@ mod tests {
pane::init(cx);
crate::init(cx);
workspace::init(app_state.clone(), cx);
Project::init_settings(cx);
});
}

View File

@@ -284,7 +284,7 @@ mod tests {
symbol("uno", "/dir/test.rs"),
];
let fake_server = fake_servers.next().await.unwrap();
fake_server.handle_request::<lsp::request::WorkspaceSymbol, _, _>(
fake_server.handle_request::<lsp::WorkspaceSymbolRequest, _, _>(
move |params: lsp::WorkspaceSymbolParams, cx| {
let executor = cx.background();
let fake_symbols = fake_symbols.clone();
@@ -308,12 +308,12 @@ mod tests {
.await
};
Ok(Some(
Ok(Some(lsp::WorkspaceSymbolResponse::Flat(
matches
.into_iter()
.map(|mat| fake_symbols[mat.candidate_id].clone())
.collect(),
))
)))
}
},
);

View File

@@ -129,6 +129,9 @@ message Envelope {
GetPrivateUserInfo get_private_user_info = 105;
GetPrivateUserInfoResponse get_private_user_info_response = 106;
UpdateDiffBase update_diff_base = 107;
OnTypeFormatting on_type_formatting = 111;
OnTypeFormattingResponse on_type_formatting_response = 112;
}
}
@@ -670,6 +673,18 @@ message PerformRename {
repeated VectorClockEntry version = 5;
}
message OnTypeFormatting {
uint64 project_id = 1;
uint64 buffer_id = 2;
Anchor position = 3;
string trigger = 4;
repeated VectorClockEntry version = 5;
}
message OnTypeFormattingResponse {
Transaction transaction = 1;
}
message PerformRenameResponse {
ProjectTransaction transaction = 2;
}

View File

@@ -195,6 +195,8 @@ messages!(
(OpenBufferResponse, Background),
(PerformRename, Background),
(PerformRenameResponse, Background),
(OnTypeFormatting, Background),
(OnTypeFormattingResponse, Background),
(Ping, Foreground),
(PrepareRename, Background),
(PrepareRenameResponse, Background),
@@ -279,6 +281,7 @@ request_messages!(
(Ping, Ack),
(PerformRename, PerformRenameResponse),
(PrepareRename, PrepareRenameResponse),
(OnTypeFormatting, OnTypeFormattingResponse),
(ReloadBuffers, ReloadBuffersResponse),
(RequestContact, Ack),
(RemoveContact, Ack),
@@ -323,6 +326,7 @@ entity_messages!(
OpenBufferByPath,
OpenBufferForSymbol,
PerformRename,
OnTypeFormatting,
PrepareRename,
ReloadBuffers,
RemoveProjectCollaborator,

View File

@@ -6,4 +6,4 @@ pub use conn::Connection;
pub use peer::*;
mod macros;
pub const PROTOCOL_VERSION: u32 = 55;
pub const PROTOCOL_VERSION: u32 = 56;

View File

@@ -360,15 +360,6 @@ impl Item for ProjectSearchView {
.update(cx, |editor, cx| editor.navigate(data, cx))
}
fn git_diff_recalc(
&mut self,
project: ModelHandle<Project>,
cx: &mut ViewContext<Self>,
) -> Task<anyhow::Result<()>> {
self.results_editor
.update(cx, |editor, cx| editor.git_diff_recalc(project, cx))
}
fn to_item_events(event: &Self::Event) -> SmallVec<[ItemEvent; 2]> {
match event {
ViewEvent::UpdateTab => {

View File

@@ -55,15 +55,22 @@ pub fn watch_config_file(
.spawn(async move {
let events = fs.watch(&path, Duration::from_millis(100)).await;
futures::pin_mut!(events);
let contents = fs.load(&path).await.unwrap_or_default();
if tx.unbounded_send(contents).is_err() {
return;
}
loop {
if events.next().await.is_none() {
break;
}
if let Ok(contents) = fs.load(&path).await {
if !tx.unbounded_send(contents).is_ok() {
break;
}
}
if events.next().await.is_none() {
break;
}
}
})
.detach();

View File

@@ -1,4 +1,4 @@
use anyhow::Result;
use anyhow::{anyhow, Result};
use collections::{btree_map, hash_map, BTreeMap, HashMap};
use gpui::AppContext;
use lazy_static::lazy_static;
@@ -84,15 +84,26 @@ pub struct SettingsJsonSchemaParams<'a> {
}
/// A set of strongly-typed setting values defined via multiple JSON files.
#[derive(Default)]
pub struct SettingsStore {
setting_values: HashMap<TypeId, Box<dyn AnySettingValue>>,
default_deserialized_settings: Option<serde_json::Value>,
user_deserialized_settings: Option<serde_json::Value>,
default_deserialized_settings: serde_json::Value,
user_deserialized_settings: serde_json::Value,
local_deserialized_settings: BTreeMap<Arc<Path>, serde_json::Value>,
tab_size_callback: Option<(TypeId, Box<dyn Fn(&dyn Any) -> Option<usize>>)>,
}
impl Default for SettingsStore {
fn default() -> Self {
SettingsStore {
setting_values: Default::default(),
default_deserialized_settings: serde_json::json!({}),
user_deserialized_settings: serde_json::json!({}),
local_deserialized_settings: Default::default(),
tab_size_callback: Default::default(),
}
}
}
#[derive(Debug)]
struct SettingValue<T> {
global_value: Option<T>,
@@ -136,27 +147,24 @@ impl SettingsStore {
local_values: Vec::new(),
}));
if let Some(default_settings) = &self.default_deserialized_settings {
if let Some(default_settings) = setting_value
.deserialize_setting(default_settings)
if let Some(default_settings) = setting_value
.deserialize_setting(&self.default_deserialized_settings)
.log_err()
{
let mut user_values_stack = Vec::new();
if let Some(user_settings) = setting_value
.deserialize_setting(&self.user_deserialized_settings)
.log_err()
{
let mut user_values_stack = Vec::new();
user_values_stack = vec![user_settings];
}
if let Some(user_settings) = &self.user_deserialized_settings {
if let Some(user_settings) =
setting_value.deserialize_setting(user_settings).log_err()
{
user_values_stack = vec![user_settings];
}
}
if let Some(setting) = setting_value
.load_setting(&default_settings, &user_values_stack, cx)
.log_err()
{
setting_value.set_global_value(setting);
}
if let Some(setting) = setting_value
.load_setting(&default_settings, &user_values_stack, cx)
.log_err()
{
setting_value.set_global_value(setting);
}
}
}
@@ -189,9 +197,7 @@ impl SettingsStore {
/// This is only for debugging and reporting. For user-facing functionality,
/// use the typed setting interface.
pub fn untyped_user_settings(&self) -> &serde_json::Value {
self.user_deserialized_settings
.as_ref()
.unwrap_or(&serde_json::Value::Null)
&self.user_deserialized_settings
}
#[cfg(any(test, feature = "test-support"))]
@@ -213,11 +219,7 @@ impl SettingsStore {
cx: &AppContext,
update: impl FnOnce(&mut T::FileContent),
) {
if self.user_deserialized_settings.is_none() {
self.set_user_settings("{}", cx).unwrap();
}
let old_text =
serde_json::to_string(self.user_deserialized_settings.as_ref().unwrap()).unwrap();
let old_text = serde_json::to_string(&self.user_deserialized_settings).unwrap();
let new_text = self.new_text_for_update::<T>(old_text, update);
self.set_user_settings(&new_text, cx).unwrap();
}
@@ -250,11 +252,7 @@ impl SettingsStore {
.setting_values
.get(&setting_type_id)
.unwrap_or_else(|| panic!("unregistered setting type {}", type_name::<T>()))
.deserialize_setting(
self.user_deserialized_settings
.as_ref()
.expect("no user settings loaded"),
)
.deserialize_setting(&self.user_deserialized_settings)
.unwrap_or_else(|e| {
panic!(
"could not deserialize setting type {} from user settings: {}",
@@ -323,10 +321,14 @@ impl SettingsStore {
default_settings_content: &str,
cx: &AppContext,
) -> Result<()> {
self.default_deserialized_settings =
Some(parse_json_with_comments(default_settings_content)?);
self.recompute_values(None, cx)?;
Ok(())
let settings: serde_json::Value = parse_json_with_comments(default_settings_content)?;
if settings.is_object() {
self.default_deserialized_settings = settings;
self.recompute_values(None, cx)?;
Ok(())
} else {
Err(anyhow!("settings must be an object"))
}
}
/// Set the user settings via a JSON string.
@@ -335,9 +337,14 @@ impl SettingsStore {
user_settings_content: &str,
cx: &AppContext,
) -> Result<()> {
self.user_deserialized_settings = Some(parse_json_with_comments(user_settings_content)?);
self.recompute_values(None, cx)?;
Ok(())
let settings: serde_json::Value = parse_json_with_comments(user_settings_content)?;
if settings.is_object() {
self.user_deserialized_settings = settings;
self.recompute_values(None, cx)?;
Ok(())
} else {
Err(anyhow!("settings must be an object"))
}
}
/// Add or remove a set of local settings via a JSON string.
@@ -443,65 +450,63 @@ impl SettingsStore {
let mut user_settings_stack = Vec::<DeserializedSetting>::new();
let mut paths_stack = Vec::<Option<&Path>>::new();
for setting_value in self.setting_values.values_mut() {
if let Some(default_settings) = &self.default_deserialized_settings {
let default_settings = setting_value.deserialize_setting(default_settings)?;
let default_settings =
setting_value.deserialize_setting(&self.default_deserialized_settings)?;
user_settings_stack.clear();
paths_stack.clear();
user_settings_stack.clear();
paths_stack.clear();
if let Some(user_settings) = &self.user_deserialized_settings {
if let Some(user_settings) =
setting_value.deserialize_setting(user_settings).log_err()
{
user_settings_stack.push(user_settings);
paths_stack.push(None);
if let Some(user_settings) = setting_value
.deserialize_setting(&self.user_deserialized_settings)
.log_err()
{
user_settings_stack.push(user_settings);
paths_stack.push(None);
}
// If the global settings file changed, reload the global value for the field.
if changed_local_path.is_none() {
if let Some(value) = setting_value
.load_setting(&default_settings, &user_settings_stack, cx)
.log_err()
{
setting_value.set_global_value(value);
}
}
// Reload the local values for the setting.
for (path, local_settings) in &self.local_deserialized_settings {
// Build a stack of all of the local values for that setting.
while let Some(prev_path) = paths_stack.last() {
if let Some(prev_path) = prev_path {
if !path.starts_with(prev_path) {
paths_stack.pop();
user_settings_stack.pop();
continue;
}
}
break;
}
// If the global settings file changed, reload the global value for the field.
if changed_local_path.is_none() {
if let Some(local_settings) =
setting_value.deserialize_setting(&local_settings).log_err()
{
paths_stack.push(Some(path.as_ref()));
user_settings_stack.push(local_settings);
// If a local settings file changed, then avoid recomputing local
// settings for any path outside of that directory.
if changed_local_path.map_or(false, |changed_local_path| {
!path.starts_with(changed_local_path)
}) {
continue;
}
if let Some(value) = setting_value
.load_setting(&default_settings, &user_settings_stack, cx)
.log_err()
{
setting_value.set_global_value(value);
}
}
// Reload the local values for the setting.
for (path, local_settings) in &self.local_deserialized_settings {
// Build a stack of all of the local values for that setting.
while let Some(prev_path) = paths_stack.last() {
if let Some(prev_path) = prev_path {
if !path.starts_with(prev_path) {
paths_stack.pop();
user_settings_stack.pop();
continue;
}
}
break;
}
if let Some(local_settings) =
setting_value.deserialize_setting(&local_settings).log_err()
{
paths_stack.push(Some(path.as_ref()));
user_settings_stack.push(local_settings);
// If a local settings file changed, then avoid recomputing local
// settings for any path outside of that directory.
if changed_local_path.map_or(false, |changed_local_path| {
!path.starts_with(changed_local_path)
}) {
continue;
}
if let Some(value) = setting_value
.load_setting(&default_settings, &user_settings_stack, cx)
.log_err()
{
setting_value.set_local_value(path.clone(), value);
}
setting_value.set_local_value(path.clone(), value);
}
}
}

View File

@@ -50,6 +50,7 @@ impl TerminalPanel {
let window_id = cx.window_id();
let mut pane = Pane::new(
workspace.weak_handle(),
workspace.project().clone(),
workspace.app_state().background_actions,
Default::default(),
cx,
@@ -178,8 +179,9 @@ impl TerminalPanel {
(panel, pane, items)
})?;
let pane = pane.downgrade();
let items = futures::future::join_all(items).await;
workspace.update(&mut cx, |workspace, cx| {
pane.update(&mut cx, |pane, cx| {
let active_item_id = serialized_panel
.as_ref()
.and_then(|panel| panel.active_item_id);
@@ -187,17 +189,15 @@ impl TerminalPanel {
for item in items {
if let Some(item) = item.log_err() {
let item_id = item.id();
Pane::add_item(workspace, &pane, Box::new(item), false, false, None, cx);
pane.add_item(Box::new(item), false, false, None, cx);
if Some(item_id) == active_item_id {
active_ix = Some(pane.read(cx).items_len() - 1);
active_ix = Some(pane.items_len() - 1);
}
}
}
if let Some(active_ix) = active_ix {
pane.update(cx, |pane, cx| {
pane.activate_item(active_ix, false, false, cx)
});
pane.activate_item(active_ix, false, false, cx)
}
})?;
@@ -254,8 +254,10 @@ impl TerminalPanel {
Box::new(cx.add_view(|cx| {
TerminalView::new(terminal, workspace.database_id(), cx)
}));
let focus = pane.read(cx).has_focus();
Pane::add_item(workspace, &pane, terminal, true, focus, None, cx);
pane.update(cx, |pane, cx| {
let focus = pane.has_focus();
pane.add_item(terminal, true, focus, None, cx);
});
}
})?;
this.update(&mut cx, |this, cx| this.serialize(cx))?;

View File

@@ -133,8 +133,8 @@ impl TerminalView {
Event::Wakeup => {
if !cx.is_self_focused() {
this.has_new_content = true;
cx.notify();
}
cx.notify();
cx.emit(Event::Wakeup);
}
Event::Bell => {

View File

@@ -1,9 +1,8 @@
use crate::{
pane, persistence::model::ItemId, searchable::SearchableItemHandle, DelayedDebouncedEditAction,
FollowableItemBuilders, ItemNavHistory, Pane, ToolbarItemLocation, ViewId, Workspace,
WorkspaceId,
pane, persistence::model::ItemId, searchable::SearchableItemHandle, FollowableItemBuilders,
ItemNavHistory, Pane, ToolbarItemLocation, ViewId, Workspace, WorkspaceId,
};
use crate::{AutosaveSetting, WorkspaceSettings};
use crate::{AutosaveSetting, DelayedDebouncedEditAction, WorkspaceSettings};
use anyhow::Result;
use client::{proto, Client};
use gpui::{
@@ -102,13 +101,6 @@ pub trait Item: View {
) -> Task<Result<()>> {
unimplemented!("reload() must be implemented if can_save() returns true")
}
fn git_diff_recalc(
&mut self,
_project: ModelHandle<Project>,
_cx: &mut ViewContext<Self>,
) -> Task<Result<()>> {
Task::ready(Ok(()))
}
fn to_item_events(_event: &Self::Event) -> SmallVec<[ItemEvent; 2]> {
SmallVec::new()
}
@@ -221,11 +213,6 @@ pub trait ItemHandle: 'static + fmt::Debug {
cx: &mut WindowContext,
) -> Task<Result<()>>;
fn reload(&self, project: ModelHandle<Project>, cx: &mut WindowContext) -> Task<Result<()>>;
fn git_diff_recalc(
&self,
project: ModelHandle<Project>,
cx: &mut WindowContext,
) -> Task<Result<()>>;
fn act_as_type<'a>(&'a self, type_id: TypeId, cx: &'a AppContext) -> Option<&'a AnyViewHandle>;
fn to_followable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn FollowableItemHandle>>;
fn on_release(
@@ -381,7 +368,6 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
.is_none()
{
let mut pending_autosave = DelayedDebouncedEditAction::new();
let mut pending_git_update = DelayedDebouncedEditAction::new();
let pending_update = Rc::new(RefCell::new(None));
let pending_update_scheduled = Rc::new(AtomicBool::new(false));
@@ -450,48 +436,14 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
}
ItemEvent::Edit => {
let settings = settings::get::<WorkspaceSettings>(cx);
let debounce_delay = settings.git.gutter_debounce;
if let AutosaveSetting::AfterDelay { milliseconds } =
settings.autosave
{
let autosave = settings::get::<WorkspaceSettings>(cx).autosave;
if let AutosaveSetting::AfterDelay { milliseconds } = autosave {
let delay = Duration::from_millis(milliseconds);
let item = item.clone();
pending_autosave.fire_new(delay, cx, move |workspace, cx| {
Pane::autosave_item(&item, workspace.project().clone(), cx)
});
}
let item = item.clone();
if let Some(delay) = debounce_delay {
const MIN_GIT_DELAY: u64 = 50;
let delay = delay.max(MIN_GIT_DELAY);
let duration = Duration::from_millis(delay);
pending_git_update.fire_new(
duration,
cx,
move |workspace, cx| {
item.git_diff_recalc(workspace.project().clone(), cx)
},
);
} else {
cx.spawn(|workspace, mut cx| async move {
workspace
.update(&mut cx, |workspace, cx| {
item.git_diff_recalc(
workspace.project().clone(),
cx,
)
})?
.await?;
anyhow::Ok(())
})
.detach_and_log_err(cx);
}
}
_ => {}
@@ -576,14 +528,6 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
self.update(cx, |item, cx| item.reload(project, cx))
}
fn git_diff_recalc(
&self,
project: ModelHandle<Project>,
cx: &mut WindowContext,
) -> Task<Result<()>> {
self.update(cx, |item, cx| item.git_diff_recalc(project, cx))
}
fn act_as_type<'a>(&'a self, type_id: TypeId, cx: &'a AppContext) -> Option<&'a AnyViewHandle> {
self.read(cx).act_as_type(type_id, self, cx)
}

File diff suppressed because it is too large Load Diff

View File

@@ -183,7 +183,7 @@ pub fn handle_dropped_item<V: View>(
.zip(pane.upgrade(cx))
{
workspace.update(cx, |workspace, cx| {
Pane::move_item(workspace, from, to, item_id, index, cx);
workspace.move_item(from, to, item_id, index, cx);
})
}
});

View File

@@ -1,5 +1,5 @@
use crate::{item::ItemHandle, ItemDeserializers, Member, Pane, PaneAxis, Workspace, WorkspaceId};
use anyhow::{anyhow, Context, Result};
use anyhow::{Context, Result};
use async_recursion::async_recursion;
use db::sqlez::{
bindable::{Bind, Column, StaticColumnCount},
@@ -230,7 +230,7 @@ impl SerializedPane {
pub async fn deserialize_to(
&self,
project: &ModelHandle<Project>,
pane_handle: &WeakViewHandle<Pane>,
pane: &WeakViewHandle<Pane>,
workspace_id: WorkspaceId,
workspace: &WeakViewHandle<Workspace>,
cx: &mut AsyncAppContext,
@@ -239,7 +239,7 @@ impl SerializedPane {
let mut active_item_index = None;
for (index, item) in self.children.iter().enumerate() {
let project = project.clone();
let item_handle = pane_handle
let item_handle = pane
.update(cx, |_, cx| {
if let Some(deserializer) = cx.global::<ItemDeserializers>().get(&item.kind) {
deserializer(project, workspace.clone(), workspace_id, item.item_id, cx)
@@ -256,13 +256,9 @@ impl SerializedPane {
items.push(item_handle.clone());
if let Some(item_handle) = item_handle {
workspace.update(cx, |workspace, cx| {
let pane_handle = pane_handle
.upgrade(cx)
.ok_or_else(|| anyhow!("pane was dropped"))?;
Pane::add_item(workspace, &pane_handle, item_handle, true, true, None, cx);
anyhow::Ok(())
})??;
pane.update(cx, |pane, cx| {
pane.add_item(item_handle.clone(), true, true, None, cx);
})?;
}
if item.active {
@@ -271,7 +267,7 @@ impl SerializedPane {
}
if let Some(active_item_index) = active_item_index {
pane_handle.update(cx, |pane, cx| {
pane.update(cx, |pane, cx| {
pane.activate_item(active_item_index, false, false, cx);
})?;
}

View File

@@ -99,7 +99,7 @@ impl Item for SharedScreen {
Some(format!("{}'s screen", self.user.github_login).into())
}
fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
if let Some(nav_history) = self.nav_history.as_ref() {
if let Some(nav_history) = self.nav_history.as_mut() {
nav_history.push::<()>(None, cx);
}
}

View File

@@ -153,14 +153,13 @@ impl View for Toolbar {
let pane = pane.clone();
cx.window_context().defer(move |cx| {
workspace.update(cx, |workspace, cx| {
Pane::go_back(workspace, Some(pane.clone()), cx)
.detach_and_log_err(cx);
workspace.go_back(pane.clone(), cx).detach_and_log_err(cx);
});
})
}
}
},
super::GoBack { pane: None },
super::GoBack,
"Go Back",
cx,
));
@@ -182,14 +181,15 @@ impl View for Toolbar {
let pane = pane.clone();
cx.window_context().defer(move |cx| {
workspace.update(cx, |workspace, cx| {
Pane::go_forward(workspace, Some(pane.clone()), cx)
workspace
.go_forward(pane.clone(), cx)
.detach_and_log_err(cx);
});
});
}
}
},
super::GoForward { pane: None },
super::GoForward,
"Go Forward",
cx,
));

View File

@@ -260,6 +260,19 @@ pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
workspace.toggle_dock(DockPosition::Bottom, cx);
});
cx.add_action(Workspace::activate_pane_at_index);
cx.add_action(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| {
workspace.reopen_closed_item(cx).detach();
});
cx.add_action(|workspace: &mut Workspace, _: &GoBack, cx| {
workspace
.go_back(workspace.active_pane().downgrade(), cx)
.detach();
});
cx.add_action(|workspace: &mut Workspace, _: &GoForward, cx| {
workspace
.go_forward(workspace.active_pane().downgrade(), cx)
.detach();
});
cx.add_action(|_: &mut Workspace, _: &install_cli::Install, cx| {
cx.spawn(|workspace, mut cx| async move {
@@ -424,7 +437,7 @@ impl DelayedDebouncedEditAction {
}
}
fn fire_new<F>(&mut self, delay: Duration, cx: &mut ViewContext<Workspace>, f: F)
fn fire_new<F>(&mut self, delay: Duration, cx: &mut ViewContext<Workspace>, func: F)
where
F: 'static + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> Task<Result<()>>,
{
@@ -448,7 +461,7 @@ impl DelayedDebouncedEditAction {
}
if let Some(result) = workspace
.update(&mut cx, |workspace, cx| (f)(workspace, cx))
.update(&mut cx, |workspace, cx| (func)(workspace, cx))
.log_err()
{
result.await.log_err();
@@ -566,6 +579,7 @@ impl Workspace {
let center_pane = cx.add_view(|cx| {
Pane::new(
weak_handle.clone(),
project.clone(),
app_state.background_actions,
pane_history_timestamp.clone(),
cx,
@@ -996,6 +1010,115 @@ impl Workspace {
.collect()
}
fn navigate_history(
&mut self,
pane: WeakViewHandle<Pane>,
mode: NavigationMode,
cx: &mut ViewContext<Workspace>,
) -> Task<Result<()>> {
let to_load = if let Some(pane) = pane.upgrade(cx) {
cx.focus(&pane);
pane.update(cx, |pane, cx| {
loop {
// Retrieve the weak item handle from the history.
let entry = pane.nav_history_mut().pop(mode, cx)?;
// If the item is still present in this pane, then activate it.
if let Some(index) = entry
.item
.upgrade(cx)
.and_then(|v| pane.index_for_item(v.as_ref()))
{
let prev_active_item_index = pane.active_item_index();
pane.nav_history_mut().set_mode(mode);
pane.activate_item(index, true, true, cx);
pane.nav_history_mut().set_mode(NavigationMode::Normal);
let mut navigated = prev_active_item_index != pane.active_item_index();
if let Some(data) = entry.data {
navigated |= pane.active_item()?.navigate(data, cx);
}
if navigated {
break None;
}
}
// If the item is no longer present in this pane, then retrieve its
// project path in order to reopen it.
else {
break pane
.nav_history()
.path_for_item(entry.item.id())
.map(|(project_path, _)| (project_path, entry));
}
}
})
} else {
None
};
if let Some((project_path, entry)) = to_load {
// If the item was no longer present, then load it again from its previous path.
let task = self.load_path(project_path, cx);
cx.spawn(|workspace, mut cx| async move {
let task = task.await;
let mut navigated = false;
if let Some((project_entry_id, build_item)) = task.log_err() {
let prev_active_item_id = pane.update(&mut cx, |pane, _| {
pane.nav_history_mut().set_mode(mode);
pane.active_item().map(|p| p.id())
})?;
pane.update(&mut cx, |pane, cx| {
let item = pane.open_item(project_entry_id, true, cx, build_item);
navigated |= Some(item.id()) != prev_active_item_id;
pane.nav_history_mut().set_mode(NavigationMode::Normal);
if let Some(data) = entry.data {
navigated |= item.navigate(data, cx);
}
})?;
}
if !navigated {
workspace
.update(&mut cx, |workspace, cx| {
Self::navigate_history(workspace, pane, mode, cx)
})?
.await?;
}
Ok(())
})
} else {
Task::ready(Ok(()))
}
}
pub fn go_back(
&mut self,
pane: WeakViewHandle<Pane>,
cx: &mut ViewContext<Workspace>,
) -> Task<Result<()>> {
self.navigate_history(pane, NavigationMode::GoingBack, cx)
}
pub fn go_forward(
&mut self,
pane: WeakViewHandle<Pane>,
cx: &mut ViewContext<Workspace>,
) -> Task<Result<()>> {
self.navigate_history(pane, NavigationMode::GoingForward, cx)
}
pub fn reopen_closed_item(&mut self, cx: &mut ViewContext<Workspace>) -> Task<Result<()>> {
self.navigate_history(
self.active_pane().downgrade(),
NavigationMode::ReopeningClosedItem,
cx,
)
}
pub fn client(&self) -> &Client {
&self.app_state.client
}
@@ -1304,22 +1427,6 @@ impl Workspace {
})
}
pub fn absolute_path(&self, project_path: &ProjectPath, cx: &AppContext) -> Option<PathBuf> {
let workspace_root = self
.project()
.read(cx)
.worktree_for_id(project_path.worktree_id, cx)?
.read(cx)
.abs_path();
let project_path = project_path.path.as_ref();
Some(if project_path == Path::new("") {
workspace_root.to_path_buf()
} else {
workspace_root.join(project_path)
})
}
fn add_folder_to_project(&mut self, _: &AddFolderToProject, cx: &mut ViewContext<Self>) {
let mut paths = cx.prompt_for_paths(PathPromptOptions {
files: false,
@@ -1634,6 +1741,7 @@ impl Workspace {
let pane = cx.add_view(|cx| {
Pane::new(
self.weak_handle(),
self.project.clone(),
self.app_state.background_actions,
self.pane_history_timestamp.clone(),
cx,
@@ -1653,7 +1761,7 @@ impl Workspace {
) -> bool {
if let Some(center_pane) = self.last_active_center_pane.clone() {
if let Some(center_pane) = center_pane.upgrade(cx) {
Pane::add_item(self, &center_pane, item, true, true, None, cx);
center_pane.update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
true
} else {
false
@@ -1664,8 +1772,8 @@ impl Workspace {
}
pub fn add_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
let active_pane = self.active_pane().clone();
Pane::add_item(self, &active_pane, item, true, true, None, cx);
self.active_pane
.update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
}
pub fn open_abs_path(
@@ -1715,13 +1823,10 @@ impl Workspace {
});
let task = self.load_path(path.into(), cx);
cx.spawn(|this, mut cx| async move {
cx.spawn(|_, mut cx| async move {
let (project_entry_id, build_item) = task.await?;
let pane = pane
.upgrade(&cx)
.ok_or_else(|| anyhow!("pane was closed"))?;
this.update(&mut cx, |this, cx| {
Pane::open_item(this, pane, project_entry_id, focus_item, cx, build_item)
pane.update(&mut cx, |pane, cx| {
pane.open_item(project_entry_id, focus_item, cx, build_item)
})
})
}
@@ -1778,8 +1883,9 @@ impl Workspace {
pub fn open_shared_screen(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
if let Some(shared_screen) = self.shared_screen_for_peer(peer_id, &self.active_pane, cx) {
let pane = self.active_pane.clone();
Pane::add_item(self, &pane, Box::new(shared_screen), false, true, None, cx);
self.active_pane.update(cx, |pane, cx| {
pane.add_item(Box::new(shared_screen), false, true, None, cx)
});
}
}
@@ -1864,6 +1970,7 @@ impl Workspace {
cx: &mut ViewContext<Self>,
) {
match event {
pane::Event::AddItem { item } => item.added_to_pane(self, pane, cx),
pane::Event::Split(direction) => {
self.split_pane(pane, *direction, cx);
}
@@ -1924,7 +2031,7 @@ impl Workspace {
let item = pane.read(cx).active_item()?;
let maybe_pane_handle = if let Some(clone) = item.clone_on_split(self.database_id(), cx) {
let new_pane = self.add_pane(cx);
Pane::add_item(self, &new_pane, clone, true, true, None, cx);
new_pane.update(cx, |pane, cx| pane.add_item(clone, true, true, None, cx));
self.center.split(&pane, &new_pane, direction).unwrap();
Some(new_pane)
} else {
@@ -1946,7 +2053,7 @@ impl Workspace {
let Some(from) = from.upgrade(cx) else { return; };
let new_pane = self.add_pane(cx);
Pane::move_item(self, from.clone(), new_pane.clone(), item_id_to_move, 0, cx);
self.move_item(from.clone(), new_pane.clone(), item_id_to_move, 0, cx);
self.center
.split(&pane_to_split, &new_pane, split_direction)
.unwrap();
@@ -1974,6 +2081,41 @@ impl Workspace {
}))
}
pub fn move_item(
&mut self,
source: ViewHandle<Pane>,
destination: ViewHandle<Pane>,
item_id_to_move: usize,
destination_index: usize,
cx: &mut ViewContext<Self>,
) {
let item_to_move = source
.read(cx)
.items()
.enumerate()
.find(|(_, item_handle)| item_handle.id() == item_id_to_move);
if item_to_move.is_none() {
log::warn!("Tried to move item handle which was not in `from` pane. Maybe tab was closed during drop");
return;
}
let (item_ix, item_handle) = item_to_move.unwrap();
let item_handle = item_handle.clone();
if source != destination {
// Close item from previous pane
source.update(cx, |source, cx| {
source.remove_item(item_ix, false, cx);
});
}
// This automatically removes duplicate items in the pane
destination.update(cx, |destination, cx| {
destination.add_item(item_handle, true, true, Some(destination_index), cx);
cx.focus_self();
});
}
fn remove_pane(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
if self.center.remove(&pane).unwrap() {
self.force_remove_pane(&pane, cx);
@@ -2577,7 +2719,9 @@ impl Workspace {
if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) {
pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
} else {
Pane::add_item(self, &pane, item.boxed_clone(), false, false, None, cx);
pane.update(cx, |pane, cx| {
pane.add_item(item.boxed_clone(), false, false, None, cx)
});
}
if pane_was_focused {
@@ -4177,9 +4321,7 @@ mod tests {
});
workspace
.update(cx, |workspace, cx| {
Pane::go_back(workspace, Some(pane.downgrade()), cx)
})
.update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
.await
.unwrap();

View File

@@ -8,7 +8,6 @@ pub struct WorkspaceSettings {
pub confirm_quit: bool,
pub show_call_status_icon: bool,
pub autosave: AutosaveSetting,
pub git: GitSettings,
}
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
@@ -17,7 +16,6 @@ pub struct WorkspaceSettingsContent {
pub confirm_quit: Option<bool>,
pub show_call_status_icon: Option<bool>,
pub autosave: Option<AutosaveSetting>,
pub git: Option<GitSettings>,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]

View File

@@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathansobo@gmail.com>"]
description = "The fast, collaborative code editor."
edition = "2021"
name = "zed"
version = "0.88.4"
version = "0.89.2"
publish = false
[lib]

View File

@@ -1 +1 @@
stable
preview

View File

@@ -111,8 +111,8 @@ pub fn menus() -> Vec<Menu<'static>> {
Menu {
name: "Go",
items: vec![
MenuItem::action("Back", workspace::GoBack { pane: None }),
MenuItem::action("Forward", workspace::GoForward { pane: None }),
MenuItem::action("Back", workspace::GoBack),
MenuItem::action("Forward", workspace::GoForward),
MenuItem::separator(),
MenuItem::action("Go to File", file_finder::Toggle),
MenuItem::action("Go to Symbol in Project", project_symbols::Toggle),

View File

@@ -663,7 +663,7 @@ mod tests {
use util::http::FakeHttpClient;
use workspace::{
item::{Item, ItemHandle},
open_new, open_paths, pane, NewFile, Pane, SplitDirection, WorkspaceHandle,
open_new, open_paths, pane, NewFile, SplitDirection, WorkspaceHandle,
};
#[gpui::test]
@@ -1488,7 +1488,7 @@ mod tests {
);
workspace
.update(cx, |w, cx| Pane::go_back(w, None, cx))
.update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
.await
.unwrap();
assert_eq!(
@@ -1497,7 +1497,7 @@ mod tests {
);
workspace
.update(cx, |w, cx| Pane::go_back(w, None, cx))
.update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
.await
.unwrap();
assert_eq!(
@@ -1506,7 +1506,7 @@ mod tests {
);
workspace
.update(cx, |w, cx| Pane::go_back(w, None, cx))
.update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
.await
.unwrap();
assert_eq!(
@@ -1515,7 +1515,7 @@ mod tests {
);
workspace
.update(cx, |w, cx| Pane::go_back(w, None, cx))
.update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
.await
.unwrap();
assert_eq!(
@@ -1525,7 +1525,7 @@ mod tests {
// Go back one more time and ensure we don't navigate past the first item in the history.
workspace
.update(cx, |w, cx| Pane::go_back(w, None, cx))
.update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
.await
.unwrap();
assert_eq!(
@@ -1534,7 +1534,7 @@ mod tests {
);
workspace
.update(cx, |w, cx| Pane::go_forward(w, None, cx))
.update(cx, |w, cx| w.go_forward(w.active_pane().downgrade(), cx))
.await
.unwrap();
assert_eq!(
@@ -1543,7 +1543,7 @@ mod tests {
);
workspace
.update(cx, |w, cx| Pane::go_forward(w, None, cx))
.update(cx, |w, cx| w.go_forward(w.active_pane().downgrade(), cx))
.await
.unwrap();
assert_eq!(
@@ -1561,7 +1561,7 @@ mod tests {
.await
.unwrap();
workspace
.update(cx, |w, cx| Pane::go_forward(w, None, cx))
.update(cx, |w, cx| w.go_forward(w.active_pane().downgrade(), cx))
.await
.unwrap();
assert_eq!(
@@ -1570,7 +1570,7 @@ mod tests {
);
workspace
.update(cx, |w, cx| Pane::go_forward(w, None, cx))
.update(cx, |w, cx| w.go_forward(w.active_pane().downgrade(), cx))
.await
.unwrap();
assert_eq!(
@@ -1579,7 +1579,7 @@ mod tests {
);
workspace
.update(cx, |w, cx| Pane::go_back(w, None, cx))
.update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
.await
.unwrap();
assert_eq!(
@@ -1601,7 +1601,7 @@ mod tests {
.await
.unwrap();
workspace
.update(cx, |w, cx| Pane::go_back(w, None, cx))
.update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
.await
.unwrap();
assert_eq!(
@@ -1609,7 +1609,7 @@ mod tests {
(file1.clone(), DisplayPoint::new(10, 0), 0.)
);
workspace
.update(cx, |w, cx| Pane::go_forward(w, None, cx))
.update(cx, |w, cx| w.go_forward(w.active_pane().downgrade(), cx))
.await
.unwrap();
assert_eq!(
@@ -1653,7 +1653,7 @@ mod tests {
})
});
workspace
.update(cx, |w, cx| Pane::go_back(w, None, cx))
.update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
.await
.unwrap();
assert_eq!(
@@ -1661,7 +1661,7 @@ mod tests {
(file1.clone(), DisplayPoint::new(2, 0), 0.)
);
workspace
.update(cx, |w, cx| Pane::go_back(w, None, cx))
.update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
.await
.unwrap();
assert_eq!(
@@ -1766,81 +1766,97 @@ mod tests {
// Reopen all the closed items, ensuring they are reopened in the same order
// in which they were closed.
workspace
.update(cx, Pane::reopen_closed_item)
.update(cx, Workspace::reopen_closed_item)
.await
.unwrap();
assert_eq!(active_path(&workspace, cx), Some(file3.clone()));
workspace
.update(cx, Pane::reopen_closed_item)
.update(cx, Workspace::reopen_closed_item)
.await
.unwrap();
assert_eq!(active_path(&workspace, cx), Some(file2.clone()));
workspace
.update(cx, Pane::reopen_closed_item)
.update(cx, Workspace::reopen_closed_item)
.await
.unwrap();
assert_eq!(active_path(&workspace, cx), Some(file4.clone()));
workspace
.update(cx, Pane::reopen_closed_item)
.update(cx, Workspace::reopen_closed_item)
.await
.unwrap();
assert_eq!(active_path(&workspace, cx), Some(file1.clone()));
// Reopening past the last closed item is a no-op.
workspace
.update(cx, Pane::reopen_closed_item)
.update(cx, Workspace::reopen_closed_item)
.await
.unwrap();
assert_eq!(active_path(&workspace, cx), Some(file1.clone()));
// Reopening closed items doesn't interfere with navigation history.
workspace
.update(cx, |workspace, cx| Pane::go_back(workspace, None, cx))
.update(cx, |workspace, cx| {
workspace.go_back(workspace.active_pane().downgrade(), cx)
})
.await
.unwrap();
assert_eq!(active_path(&workspace, cx), Some(file4.clone()));
workspace
.update(cx, |workspace, cx| Pane::go_back(workspace, None, cx))
.update(cx, |workspace, cx| {
workspace.go_back(workspace.active_pane().downgrade(), cx)
})
.await
.unwrap();
assert_eq!(active_path(&workspace, cx), Some(file2.clone()));
workspace
.update(cx, |workspace, cx| Pane::go_back(workspace, None, cx))
.update(cx, |workspace, cx| {
workspace.go_back(workspace.active_pane().downgrade(), cx)
})
.await
.unwrap();
assert_eq!(active_path(&workspace, cx), Some(file3.clone()));
workspace
.update(cx, |workspace, cx| Pane::go_back(workspace, None, cx))
.update(cx, |workspace, cx| {
workspace.go_back(workspace.active_pane().downgrade(), cx)
})
.await
.unwrap();
assert_eq!(active_path(&workspace, cx), Some(file4.clone()));
workspace
.update(cx, |workspace, cx| Pane::go_back(workspace, None, cx))
.update(cx, |workspace, cx| {
workspace.go_back(workspace.active_pane().downgrade(), cx)
})
.await
.unwrap();
assert_eq!(active_path(&workspace, cx), Some(file3.clone()));
workspace
.update(cx, |workspace, cx| Pane::go_back(workspace, None, cx))
.update(cx, |workspace, cx| {
workspace.go_back(workspace.active_pane().downgrade(), cx)
})
.await
.unwrap();
assert_eq!(active_path(&workspace, cx), Some(file2.clone()));
workspace
.update(cx, |workspace, cx| Pane::go_back(workspace, None, cx))
.update(cx, |workspace, cx| {
workspace.go_back(workspace.active_pane().downgrade(), cx)
})
.await
.unwrap();
assert_eq!(active_path(&workspace, cx), Some(file1.clone()));
workspace
.update(cx, |workspace, cx| Pane::go_back(workspace, None, cx))
.update(cx, |workspace, cx| {
workspace.go_back(workspace.active_pane().downgrade(), cx)
})
.await
.unwrap();
assert_eq!(active_path(&workspace, cx), Some(file1.clone()));
@@ -2085,6 +2101,7 @@ mod tests {
theme::init((), cx);
call::init(app_state.client.clone(), app_state.user_store.clone(), cx);
workspace::init(app_state.clone(), cx);
Project::init_settings(cx);
language::init(cx);
editor::init(cx);
project_panel::init_settings(cx);

63
script/get-changes-since Executable file
View File

@@ -0,0 +1,63 @@
#!/usr/bin/env node --redirect-warnings=/dev/null
const { execFileSync } = require("child_process");
const { GITHUB_ACCESS_TOKEN } = process.env;
const PR_REGEX = /#\d+/ // Ex: matches on #4241
const FIXES_REGEX = /(fixes|closes|completes) (.+[/#]\d+.*)$/im;
main();
async function main() {
// Use form of: YYYY-MM-DD - 2023-01-09
const startDate = new Date(process.argv[2]);
const today = new Date()
console.log(`Changes from ${startDate} to ${today}\n`);
let pullRequestNumbers = getPullRequestNumbers(startDate, today);
// Fetch the pull requests from the GitHub API.
console.log("Merged Pull requests:");
for (const pullRequestNumber of pullRequestNumbers) {
const webURL = `https://github.com/zed-industries/zed/pull/${pullRequestNumber}`;
const apiURL = `https://api.github.com/repos/zed-industries/zed/pulls/${pullRequestNumber}`;
const response = await fetch(apiURL, {
headers: {
Authorization: `token ${GITHUB_ACCESS_TOKEN}`,
},
});
const pullRequest = await response.json();
console.log("*", pullRequest.title);
console.log(" PR URL: ", webURL);
console.log(" Merged: ", pullRequest.merged_at);
console.log()
}
}
function getPullRequestNumbers(startDate, endDate) {
const sinceDate = startDate.toISOString();
const untilDate = endDate.toISOString();
const pullRequestNumbers = execFileSync(
"git",
[
"log",
`--since=${sinceDate}`,
`--until=${untilDate}`,
"--oneline"
],
{ encoding: "utf8" }
)
.split("\n")
.filter(line => line.length > 0)
.map(line => {
const match = line.match(/#(\d+)/);
return match ? match[1] : null;
})
.filter(line => line);
return pullRequestNumbers;
}

View File

@@ -1,11 +1,9 @@
import * as fs from "fs"
import toml from "toml"
import { schemeMeta } from "./colorSchemes"
import { Meta, Verification } from "./themes/common/colorScheme"
import https from "https"
import crypto from "crypto"
import { MetaAndLicense } from "./themes/common/colorScheme"
const accepted_licenses_file = `${__dirname}/../../script/licenses/zed-licenses.toml`
const ACCEPTED_LICENSES_FILE = `${__dirname}/../../script/licenses/zed-licenses.toml`
// Use the cargo-about configuration file as the source of truth for supported licenses.
function parseAcceptedToml(file: string): string[] {
@@ -20,8 +18,11 @@ function parseAcceptedToml(file: string): string[] {
return obj.accepted
}
function checkLicenses(schemeMeta: Meta[], licenses: string[]) {
for (let meta of schemeMeta) {
function checkLicenses(
schemeMetaWithLicense: MetaAndLicense[],
licenses: string[]
) {
for (const { meta } of schemeMetaWithLicense) {
// FIXME: Add support for conjuctions and conditions
if (licenses.indexOf(meta.license.SPDX) < 0) {
throw Error(
@@ -31,62 +32,23 @@ function checkLicenses(schemeMeta: Meta[], licenses: string[]) {
}
}
function getLicenseText(
schemeMeta: Meta[],
callback: (meta: Meta, license_text: string) => void
) {
for (let meta of schemeMeta) {
if (typeof meta.license.license_text == "string") {
callback(meta, meta.license.license_text)
} else {
let license_text_obj: Verification = meta.license.license_text
// The following copied from the example code on nodejs.org:
// https://nodejs.org/api/http.html#httpgetoptions-callback
https
.get(license_text_obj.https_url, (res) => {
const { statusCode } = res
if (statusCode < 200 || statusCode >= 300) {
throw new Error(
`Failed to fetch license for: ${meta.name}, Status Code: ${statusCode}`
)
}
res.setEncoding("utf8")
let rawData = ""
res.on("data", (chunk) => {
rawData += chunk
})
res.on("end", () => {
const hash = crypto
.createHash("sha256")
.update(rawData)
.digest("hex")
if (license_text_obj.license_checksum == hash) {
callback(meta, rawData)
} else {
throw Error(
`Checksum for ${meta.name} did not match file downloaded from ${license_text_obj.https_url}`
)
}
})
})
.on("error", (e) => {
throw e
})
}
function generateLicenseFile(schemeMetaWithLicense: MetaAndLicense[]) {
for (const { meta, licenseFile } of schemeMetaWithLicense) {
const licenseText = fs.readFileSync(licenseFile).toString()
writeLicense(meta.name, meta.url, licenseText)
}
}
function writeLicense(schemeMeta: Meta, text: String) {
function writeLicense(
themeName: string,
themeUrl: string,
licenseText: String
) {
process.stdout.write(
`## [${schemeMeta.name}](${schemeMeta.url})\n\n${text}\n********************************************************************************\n\n`
`## [${themeName}](${themeUrl})\n\n${licenseText}\n********************************************************************************\n\n`
)
}
const accepted_licenses = parseAcceptedToml(accepted_licenses_file)
checkLicenses(schemeMeta, accepted_licenses)
getLicenseText(schemeMeta, (meta, text) => {
writeLicense(meta, text)
})
const acceptedLicenses = parseAcceptedToml(ACCEPTED_LICENSES_FILE)
checkLicenses(schemeMeta, acceptedLicenses)
generateLicenseFile(schemeMeta)

View File

@@ -1,7 +1,7 @@
import * as fs from "fs"
import { tmpdir } from "os"
import * as path from "path"
import colorSchemes, { staffColorSchemes } from "./colorSchemes"
import { colorSchemes, staffColorSchemes } from "./colorSchemes"
import app from "./styleTree/app"
import { ColorScheme } from "./themes/common/colorScheme"
import snakeCase from "./utils/snakeCase"

View File

@@ -1,54 +1,79 @@
import fs from "fs"
import path from "path"
import { ColorScheme, Meta } from "./themes/common/colorScheme"
import { ColorScheme, MetaAndLicense } from "./themes/common/colorScheme"
const colorSchemes: ColorScheme[] = []
export default colorSchemes
const THEMES_DIRECTORY = path.resolve(`${__dirname}/themes`)
const STAFF_DIRECTORY = path.resolve(`${__dirname}/themes/staff`)
const IGNORE_ITEMS = ["staff", "common", "common.ts"]
const ACCEPT_EXTENSION = ".ts"
const LICENSE_FILE_NAME = "LICENSE"
const schemeMeta: Meta[] = []
export { schemeMeta }
function getAllTsFiles(directoryPath: string) {
const files = fs.readdirSync(directoryPath)
const fileList: string[] = []
const staffColorSchemes: ColorScheme[] = []
export { staffColorSchemes }
for (const file of files) {
if (!IGNORE_ITEMS.includes(file)) {
const filePath = path.join(directoryPath, file)
const experimentalColorSchemes: ColorScheme[] = []
export { experimentalColorSchemes }
const themes_directory = path.resolve(`${__dirname}/themes`)
function for_all_color_schemes_in(
themesPath: string,
callback: (module: any, path: string) => void
) {
for (const fileName of fs.readdirSync(themesPath)) {
if (fileName == "template.ts") continue
const filePath = path.join(themesPath, fileName)
if (fs.statSync(filePath).isFile()) {
const colorScheme = require(filePath)
callback(colorScheme, path.basename(filePath))
if (fs.statSync(filePath).isDirectory()) {
fileList.push(...getAllTsFiles(filePath))
} else if (path.extname(file) === ACCEPT_EXTENSION) {
fileList.push(filePath)
}
}
}
return fileList
}
function fillColorSchemes(themesPath: string, colorSchemes: ColorScheme[]) {
for_all_color_schemes_in(themesPath, (colorScheme, _path) => {
function getAllColorSchemes(directoryPath: string) {
const files = getAllTsFiles(directoryPath)
return files.map((filePath) => ({
colorScheme: require(filePath),
filePath,
fileName: path.basename(filePath),
licenseFile: `${path.dirname(filePath)}/${LICENSE_FILE_NAME}`,
}))
}
function getColorSchemes(directoryPath: string) {
const colorSchemes: ColorScheme[] = []
for (const { colorScheme } of getAllColorSchemes(directoryPath)) {
if (colorScheme.dark) colorSchemes.push(colorScheme.dark)
if (colorScheme.light) colorSchemes.push(colorScheme.light)
})
else if (colorScheme.light) colorSchemes.push(colorScheme.light)
}
return colorSchemes
}
fillColorSchemes(themes_directory, colorSchemes)
fillColorSchemes(path.resolve(`${themes_directory}/staff`), staffColorSchemes)
function getMetaAndLicense(directoryPath: string) {
const meta: MetaAndLicense[] = []
function fillMeta(themesPath: string, meta: Meta[]) {
for_all_color_schemes_in(themesPath, (colorScheme, path) => {
if (colorScheme.meta) {
meta.push(colorScheme.meta)
} else {
throw Error(`Public theme ${path} must have a meta field`)
for (const { colorScheme, filePath, licenseFile } of getAllColorSchemes(
directoryPath
)) {
const licenseExists = fs.existsSync(licenseFile)
if (!licenseExists) {
throw Error(
`Public theme should have a LICENSE file ${licenseFile}`
)
}
})
if (!colorScheme.meta) {
throw Error(`Public theme ${filePath} must have a meta field`)
}
meta.push({
meta: colorScheme.meta,
licenseFile,
})
}
return meta
}
fillMeta(themes_directory, schemeMeta)
export const colorSchemes = getColorSchemes(THEMES_DIRECTORY)
export const staffColorSchemes = getColorSchemes(STAFF_DIRECTORY)
export const schemeMeta = getMetaAndLicense(THEMES_DIRECTORY)

View File

@@ -3,7 +3,7 @@ import { ColorScheme, Layer, StyleSets } from "../themes/common/colorScheme"
import { background, border, borderColor, foreground, text } from "./components"
import hoverPopover from "./hoverPopover"
import { SyntaxHighlightStyle, buildSyntax } from "../themes/common/syntax"
import { buildSyntax } from "../themes/common/syntax"
export default function editor(colorScheme: ColorScheme) {
const { isLight } = colorScheme
@@ -103,7 +103,7 @@ export default function editor(colorScheme: ColorScheme) {
? colorScheme.ramps.red(0.5).hex()
: colorScheme.ramps.red(0.4).hex(),
modified: isLight
? colorScheme.ramps.yellow(0.3).hex()
? colorScheme.ramps.yellow(0.5).hex()
: colorScheme.ramps.yellow(0.5).hex(),
inserted: isLight
? colorScheme.ramps.green(0.4).hex()
@@ -244,12 +244,12 @@ export default function editor(colorScheme: ColorScheme) {
thumb: {
background: withOpacity(background(layer, "inverted"), 0.3),
border: {
width: 1,
color: borderColor(layer, "variant"),
top: false,
right: true,
left: true,
bottom: false,
width: 1,
color: borderColor(layer, "variant"),
top: false,
right: true,
left: true,
bottom: false,
}
},
git: {

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2017 <eliverlara@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,6 +1,6 @@
import chroma from "chroma-js"
import { Meta } from "./common/colorScheme"
import { colorRamp, createColorScheme } from "./common/ramps"
import { Meta } from "../common/colorScheme"
import { colorRamp, createColorScheme } from "../common/ramps"
const name = "Andromeda"
@@ -34,12 +34,6 @@ export const meta: Meta = {
author: "EliverLara",
license: {
SPDX: "MIT",
license_text: {
https_url:
"https://raw.githubusercontent.com/EliverLara/Andromeda/master/LICENSE.md",
license_checksum:
"2f7886f1a05cefc2c26f5e49de1a39fa4466413c1ccb06fc80960e73f5ed4b89",
},
},
url: "https://github.com/EliverLara/Andromeda",
}

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2023 Bram de Haan, http://atelierbramdehaan.nl
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,7 +1,7 @@
import chroma from "chroma-js"
import { Meta } from "./common/colorScheme"
import { colorRamp, createColorScheme } from "./common/ramps"
import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common"
import { Meta } from "../common/colorScheme"
import { colorRamp, createColorScheme } from "../common/ramps"
import { metaCommon, name, buildSyntax, Variant } from "./common"
const variant: Variant = {
meta: {

View File

@@ -1,7 +1,7 @@
import chroma from "chroma-js"
import { Meta } from "./common/colorScheme"
import { colorRamp, createColorScheme } from "./common/ramps"
import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common"
import { Meta } from "../common/colorScheme"
import { colorRamp, createColorScheme } from "../common/ramps"
import { metaCommon, name, buildSyntax, Variant } from "./common"
const variant: Variant = {
meta: {

View File

@@ -1,7 +1,7 @@
import chroma from "chroma-js"
import { Meta } from "./common/colorScheme"
import { colorRamp, createColorScheme } from "./common/ramps"
import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common"
import { Meta } from "../common/colorScheme"
import { colorRamp, createColorScheme } from "../common/ramps"
import { metaCommon, name, buildSyntax, Variant } from "./common"
const variant: Variant = {
meta: {

View File

@@ -1,7 +1,7 @@
import chroma from "chroma-js"
import { Meta } from "./common/colorScheme"
import { colorRamp, createColorScheme } from "./common/ramps"
import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common"
import { Meta } from "../common/colorScheme"
import { colorRamp, createColorScheme } from "../common/ramps"
import { metaCommon, name, buildSyntax, Variant } from "./common"
const variant: Variant = {
meta: {

View File

@@ -1,7 +1,7 @@
import chroma from "chroma-js"
import { Meta } from "./common/colorScheme"
import { colorRamp, createColorScheme } from "./common/ramps"
import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common"
import { Meta } from "../common/colorScheme"
import { colorRamp, createColorScheme } from "../common/ramps"
import { metaCommon, name, buildSyntax, Variant } from "./common"
const variant: Variant = {
meta: {

View File

@@ -1,7 +1,7 @@
import chroma from "chroma-js"
import { Meta } from "./common/colorScheme"
import { colorRamp, createColorScheme } from "./common/ramps"
import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common"
import { Meta } from "../common/colorScheme"
import { colorRamp, createColorScheme } from "../common/ramps"
import { metaCommon, name, buildSyntax, Variant } from "./common"
const variant: Variant = {
meta: {

View File

@@ -1,7 +1,7 @@
import chroma from "chroma-js"
import { Meta } from "./common/colorScheme"
import { colorRamp, createColorScheme } from "./common/ramps"
import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common"
import { Meta } from "../common/colorScheme"
import { colorRamp, createColorScheme } from "../common/ramps"
import { metaCommon, name, buildSyntax, Variant } from "./common"
const variant: Variant = {
meta: {

View File

@@ -1,7 +1,7 @@
import chroma from "chroma-js"
import { Meta } from "./common/colorScheme"
import { colorRamp, createColorScheme } from "./common/ramps"
import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common"
import { Meta } from "../common/colorScheme"
import { colorRamp, createColorScheme } from "../common/ramps"
import { metaCommon, name, buildSyntax, Variant } from "./common"
const variant: Variant = {
meta: {

View File

@@ -1,7 +1,7 @@
import chroma from "chroma-js"
import { Meta } from "./common/colorScheme"
import { colorRamp, createColorScheme } from "./common/ramps"
import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common"
import { Meta } from "../common/colorScheme"
import { colorRamp, createColorScheme } from "../common/ramps"
import { metaCommon, name, buildSyntax, Variant } from "./common"
const variant: Variant = {
meta: {

View File

@@ -1,7 +1,7 @@
import chroma from "chroma-js"
import { Meta } from "./common/colorScheme"
import { colorRamp, createColorScheme } from "./common/ramps"
import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common"
import { Meta } from "../common/colorScheme"
import { colorRamp, createColorScheme } from "../common/ramps"
import { metaCommon, name, buildSyntax, Variant } from "./common"
const variant: Variant = {
meta: {

View File

@@ -1,7 +1,7 @@
import chroma from "chroma-js"
import { Meta } from "./common/colorScheme"
import { colorRamp, createColorScheme } from "./common/ramps"
import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common"
import { Meta } from "../common/colorScheme"
import { colorRamp, createColorScheme } from "../common/ramps"
import { metaCommon, name, buildSyntax, Variant } from "./common"
const variant: Variant = {
meta: {

View File

@@ -1,7 +1,7 @@
import chroma from "chroma-js"
import { Meta } from "./common/colorScheme"
import { colorRamp, createColorScheme } from "./common/ramps"
import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common"
import { Meta } from "../common/colorScheme"
import { colorRamp, createColorScheme } from "../common/ramps"
import { metaCommon, name, buildSyntax, Variant } from "./common"
const variant: Variant = {
meta: {

View File

@@ -1,7 +1,7 @@
import chroma from "chroma-js"
import { Meta } from "./common/colorScheme"
import { colorRamp, createColorScheme } from "./common/ramps"
import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common"
import { Meta } from "../common/colorScheme"
import { colorRamp, createColorScheme } from "../common/ramps"
import { metaCommon, name, buildSyntax, Variant } from "./common"
const variant: Variant = {
meta: {

View File

@@ -1,7 +1,7 @@
import chroma from "chroma-js"
import { Meta } from "./common/colorScheme"
import { colorRamp, createColorScheme } from "./common/ramps"
import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common"
import { Meta } from "../common/colorScheme"
import { colorRamp, createColorScheme } from "../common/ramps"
import { metaCommon, name, buildSyntax, Variant } from "./common"
const variant: Variant = {
meta: {

View File

@@ -1,7 +1,7 @@
import chroma from "chroma-js"
import { Meta } from "./common/colorScheme"
import { colorRamp, createColorScheme } from "./common/ramps"
import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common"
import { Meta } from "../common/colorScheme"
import { colorRamp, createColorScheme } from "../common/ramps"
import { metaCommon, name, buildSyntax, Variant } from "./common"
const variant: Variant = {
meta: {

View File

@@ -1,7 +1,7 @@
import chroma from "chroma-js"
import { Meta } from "./common/colorScheme"
import { colorRamp, createColorScheme } from "./common/ramps"
import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common"
import { Meta } from "../common/colorScheme"
import { colorRamp, createColorScheme } from "../common/ramps"
import { metaCommon, name, buildSyntax, Variant } from "./common"
const variant: Variant = {
meta: {

View File

@@ -1,7 +1,7 @@
import chroma from "chroma-js"
import { Meta } from "./common/colorScheme"
import { colorRamp, createColorScheme } from "./common/ramps"
import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common"
import { Meta } from "../common/colorScheme"
import { colorRamp, createColorScheme } from "../common/ramps"
import { metaCommon, name, buildSyntax, Variant } from "./common"
const variant: Variant = {
meta: {

View File

@@ -1,7 +1,7 @@
import chroma from "chroma-js"
import { Meta } from "./common/colorScheme"
import { colorRamp, createColorScheme } from "./common/ramps"
import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common"
import { Meta } from "../common/colorScheme"
import { colorRamp, createColorScheme } from "../common/ramps"
import { metaCommon, name, buildSyntax, Variant } from "./common"
const variant: Variant = {
meta: {

View File

@@ -1,7 +1,7 @@
import chroma from "chroma-js"
import { Meta } from "./common/colorScheme"
import { colorRamp, createColorScheme } from "./common/ramps"
import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common"
import { Meta } from "../common/colorScheme"
import { colorRamp, createColorScheme } from "../common/ramps"
import { metaCommon, name, buildSyntax, Variant } from "./common"
const variant: Variant = {
meta: {

View File

@@ -1,7 +1,7 @@
import chroma from "chroma-js"
import { Meta } from "./common/colorScheme"
import { colorRamp, createColorScheme } from "./common/ramps"
import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common"
import { Meta } from "../common/colorScheme"
import { colorRamp, createColorScheme } from "../common/ramps"
import { metaCommon, name, buildSyntax, Variant } from "./common"
const variant: Variant = {
meta: {

View File

@@ -1,4 +1,4 @@
import { License, Meta, ThemeSyntax } from "./colorScheme"
import { License, Meta, ThemeSyntax } from "../common/colorScheme"
export interface Variant {
meta: Meta
@@ -29,11 +29,6 @@ export const metaCommon: {
author: "Bram de Haan (http://atelierbramdehaan.nl)",
license: {
SPDX: "MIT",
license_text: {
https_url: "https://atelierbram.mit-license.org/license.txt",
license_checksum:
"f95ce526ef4e7eecf7a832bba0e3451cc1000f9ce63eb01ed6f64f8109f5d0a5",
},
},
}

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 Ike Ku
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,5 +1,5 @@
import { createColorScheme } from "./common/ramps"
import { ayu, meta as themeMeta, buildTheme } from "./common/ayu-common"
import { createColorScheme } from "../common/ramps"
import { ayu, meta as themeMeta, buildTheme } from "./common"
export const meta = {
...themeMeta,

View File

@@ -1,5 +1,5 @@
import { createColorScheme } from "./common/ramps"
import { ayu, meta as themeMeta, buildTheme } from "./common/ayu-common"
import { createColorScheme } from "../common/ramps"
import { ayu, meta as themeMeta, buildTheme } from "./common"
export const meta = {
...themeMeta,

View File

@@ -1,5 +1,5 @@
import { createColorScheme } from "./common/ramps"
import { ayu, meta as themeMeta, buildTheme } from "./common/ayu-common"
import { createColorScheme } from "../common/ramps"
import { ayu, meta as themeMeta, buildTheme } from "./common"
export const meta = {
...themeMeta,

View File

@@ -1,8 +1,8 @@
import { dark, light, mirage } from "ayu"
import { ThemeSyntax } from "./syntax"
import { ThemeSyntax } from "../common/syntax"
import chroma from "chroma-js"
import { colorRamp } from "./ramps"
import { Meta } from "./colorScheme"
import { colorRamp } from "../common/ramps"
import { Meta } from "../common/colorScheme"
export const ayu = {
dark,
@@ -79,12 +79,6 @@ export const meta: Meta = {
author: "dempfi",
license: {
SPDX: "MIT",
license_text: {
https_url:
"https://raw.githubusercontent.com/dempfi/ayu/master/LICENSE",
license_checksum:
"e0af0e0d1754c18ca075649d42f5c6d9a60f8bdc03c20dfd97105f2253a94173",
},
},
url: "https://github.com/dempfi/ayu",
}

View File

@@ -19,6 +19,11 @@ export interface ColorScheme {
syntax?: Partial<ThemeSyntax>
}
export interface MetaAndLicense {
meta: Meta
licenseFile: string
}
export interface Meta {
name: string
author: string
@@ -28,13 +33,6 @@ export interface Meta {
export interface License {
SPDX: SPDXExpression
/// A url where we can download the license's text
license_text: Verification | string
}
export interface Verification {
https_url: string
license_checksum: string
}
// License name -> License text

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) <YEAR> <COPYRIGHT HOLDER>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,6 +1,6 @@
import chroma from "chroma-js"
import { Meta, ThemeSyntax } from "./common/colorScheme"
import { colorRamp, createColorScheme } from "./common/ramps"
import { Meta, ThemeSyntax } from "../common/colorScheme"
import { colorRamp, createColorScheme } from "../common/ramps"
const name = "Gruvbox"
@@ -248,8 +248,6 @@ export const meta: Meta = {
name,
license: {
SPDX: "MIT", // "MIT/X11"
license_text:
"Copyright <YEAR> <COPYRIGHT HOLDER>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/ or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.",
},
author: "morhetz <morhetz@gmail.com>",
url: "https://github.com/morhetz/gruvbox",

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014 GitHub Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,7 +1,7 @@
import chroma from "chroma-js"
import { fontWeights } from "../common"
import { Meta, ThemeSyntax } from "./common/colorScheme"
import { colorRamp, createColorScheme } from "./common/ramps"
import { fontWeights } from "../../common"
import { Meta, ThemeSyntax } from "../common/colorScheme"
import { colorRamp, createColorScheme } from "../common/ramps"
const name = "One Dark"
@@ -74,12 +74,6 @@ export const meta: Meta = {
author: "simurai",
license: {
SPDX: "MIT",
license_text: {
https_url:
"https://raw.githubusercontent.com/atom/atom/master/packages/one-light-ui/LICENSE.md",
license_checksum:
"d5af8fc171f6f600c0ab4e7597dca398dda80dbe6821ce01cef78e859e7a00f8",
},
},
url: "https://github.com/atom/atom/tree/master/packages/one-dark-ui",
}

View File

@@ -1,7 +1,7 @@
import chroma from "chroma-js"
import { fontWeights } from "../common"
import { Meta, ThemeSyntax } from "./common/colorScheme"
import { colorRamp, createColorScheme } from "./common/ramps"
import { fontWeights } from "../../common"
import { Meta, ThemeSyntax } from "../common/colorScheme"
import { colorRamp, createColorScheme } from "../common/ramps"
const name = "One Light"
@@ -73,12 +73,6 @@ export const meta: Meta = {
author: "simurai",
license: {
SPDX: "MIT",
license_text: {
https_url:
"https://raw.githubusercontent.com/atom/atom/master/packages/one-light-ui/LICENSE.md",
license_checksum:
"d5af8fc171f6f600c0ab4e7597dca398dda80dbe6821ce01cef78e859e7a00f8",
},
},
url: "https://github.com/atom/atom/tree/master/packages/one-light-ui",
}

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2021 Emilia Dunfelt
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,6 +1,6 @@
import chroma from "chroma-js"
import { Meta } from "./common/colorScheme"
import { colorRamp, createColorScheme } from "./common/ramps"
import { Meta } from "../common/colorScheme"
import { colorRamp, createColorScheme } from "../common/ramps"
const name = "Rosé Pine Dawn"
@@ -34,12 +34,6 @@ export const meta: Meta = {
author: "edunfelt",
license: {
SPDX: "MIT",
license_text: {
https_url:
"https://raw.githubusercontent.com/edunfelt/base16-rose-pine-scheme/main/LICENSE",
license_checksum:
"6ca1b9da8c78c8441c5aa43d024a4e4a7bf59d1ecca1480196e94fda0f91ee4a",
},
},
url: "https://github.com/edunfelt/base16-rose-pine-scheme",
}

View File

@@ -1,6 +1,6 @@
import chroma from "chroma-js"
import { Meta } from "./common/colorScheme"
import { colorRamp, createColorScheme } from "./common/ramps"
import { Meta } from "../common/colorScheme"
import { colorRamp, createColorScheme } from "../common/ramps"
const name = "Rosé Pine Moon"
@@ -34,12 +34,6 @@ export const meta: Meta = {
author: "edunfelt",
license: {
SPDX: "MIT",
license_text: {
https_url:
"https://raw.githubusercontent.com/edunfelt/base16-rose-pine-scheme/main/LICENSE",
license_checksum:
"6ca1b9da8c78c8441c5aa43d024a4e4a7bf59d1ecca1480196e94fda0f91ee4a",
},
},
url: "https://github.com/edunfelt/base16-rose-pine-scheme",
}

View File

@@ -1,6 +1,6 @@
import chroma from "chroma-js"
import { Meta } from "./common/colorScheme"
import { colorRamp, createColorScheme } from "./common/ramps"
import { Meta } from "../common/colorScheme"
import { colorRamp, createColorScheme } from "../common/ramps"
const name = "Rosé Pine"
@@ -32,12 +32,6 @@ export const meta: Meta = {
author: "edunfelt",
license: {
SPDX: "MIT",
license_text: {
https_url:
"https://raw.githubusercontent.com/edunfelt/base16-rose-pine-scheme/main/LICENSE",
license_checksum:
"6ca1b9da8c78c8441c5aa43d024a4e4a7bf59d1ecca1480196e94fda0f91ee4a",
},
},
url: "https://github.com/edunfelt/base16-rose-pine-scheme",
}

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2019 George Essig
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

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