Compare commits

..

73 Commits

Author SHA1 Message Date
Piotr Osiewicz
bf4e1ffad3 WIP: rainbow brackets 2024-09-12 11:36:36 -04:00
Conrad Irwin
092f29d394 Use a bigger prefix for numeric sorting (#17752)
Release Notes:

- Fixed sorting of files with YYYYmmddHHMMSS prefix
2024-09-12 09:11:19 -04:00
Conrad Irwin
25b6e43b0f bump eslint memory usage (#17724)
Release Notes:

- Increased memory limit for eslint to reduce crashes
2024-09-11 16:22:10 -04:00
Thorsten Ball
3a6a29f117 vim: Fix inline completions showing up in normal mode (#17727)
Booleans are hard.

Release Notes:

- Fixed inline completions showing up in Vim normal mode.
2024-09-11 16:13:17 -04:00
Thorsten Ball
9407d86ce6 project: Use login shell to get environment per project (#17717)
This is a follow-up to #17075 to spawn a login shell when getting the
environment for projects.

The reason why we didn't do it before is that we only used the
environment for certain language servers and not a lot of other things,
like tasks.

But with #17075 we now use the project more often and use it as the
_base_ environment for tasks/terminals.

Before the change, terminals and tasks would inherit the Zed process'
environment, including PATH and so on. After the change, we would set
the environment, overwriting the PATH instead of merging. But the
non-login shell environment is a subset of the login-shell environment.


Release Notes:

- Fixed environment variables used per project in terminals/tasks
overwriting the base environment and not making use of a login-shell
environment.
2024-09-11 13:33:42 -04:00
Thorsten Ball
b5c42edf1e lsp: Fix noisy logs when starting language servers (#17713)
We would log every time we'd lookup a language server for a file and
we'd also log "starting language server" even though we were about to
only download it and not start it.


Release Notes:

- N/A
2024-09-11 12:56:39 -04:00
Nero Song
13c553c50f Doc Fix: Shortcut of "Go forward" in key-bindings.md (#17689)
"Control + _"  =>  "Control + Shift + _"

Doc was edited so quickly using zed that caused a typo 😄


### shot
<img width="729" alt="image"
src="https://github.com/user-attachments/assets/104af7da-1205-43fd-b721-ffab7312487b">


### doc url
https://zed.dev/docs/key-bindings


Release Notes:

- N/A
2024-09-11 08:49:12 -04:00
Kirill Bulatov
ec189fe884 Add a way to join all panes into one (#17673)
Closes https://github.com/zed-industries/zed/issues/17536
Closes https://github.com/zed-industries/zed/pull/17548


Release Notes:

- Added a way to join all panes into one with `pane::JoinAll` action
([#17536](https://github.com/zed-industries/zed/issues/17536))

---------

Co-authored-by: Yogesh Dhamija <ydhamija96@gmail.com>
2024-09-10 23:58:57 -04:00
Marshall Bowers
331d28d479 php: Remove invalid node types from highlights queries (#17680)
This PR removes some invalid node types from the PHP highlights queries.

Release Notes:

- N/A
2024-09-10 23:37:37 -04:00
Dairon M.
8088d1a9b7 erlang: Bump to v0.1.0 (#17679)
Changes:

- https://github.com/zed-industries/zed/pull/14914
- https://github.com/zed-industries/zed/pull/14879
- https://github.com/zed-industries/zed/pull/14923
- https://github.com/zed-industries/zed/pull/15973
- https://github.com/zed-industries/zed/pull/16955

Release Notes:

- N/A

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-09-10 23:21:24 -04:00
Amin Ahmed Khan
ef5a7e1642 Fix OpenAI key URL (#17675)
Update the create Open AI Key URL

Release Notes:

- Fixed a link in the Assistant panel to the OpenAI console.
2024-09-10 23:14:43 -04:00
Piotr Osiewicz
ccf6f27b8f settings: Remove auxiliary Content types where possible (#16744)
Release Notes:

- N/A
2024-09-10 22:59:10 -04:00
Marshall Bowers
8f28445612 php: Bump to v0.2.0 (#17674)
This PR bumps the PHP extension to v0.2.0.

Changes:

- https://github.com/zed-industries/zed/pull/16720
- https://github.com/zed-industries/zed/pull/16955
- https://github.com/zed-industries/zed/pull/17243
- https://github.com/zed-industries/zed/pull/17545

Release Notes:

- N/A
2024-09-10 22:57:44 -04:00
Piotr Osiewicz
f374038da0 pane: Serialize pinned tab state (#17670)
Release Notes:

- Tab pin state is now persisted across Zed runs.
2024-09-10 19:19:21 -04:00
Bedis Nbiba
d1a47faeb7 docs: Update Deno docs (#17579)
add the minimal configuration needed to make deno lsp work

Release Notes:

- N/A
2024-09-10 18:26:11 -04:00
maan2003
d6663fcb29 Pass temperature to Anthropic (#17509)
Release Notes:

- N/A

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-09-10 18:09:00 -04:00
Fernando Tagawa
b16af138e2 php: Add auto-indent (#17545)
Release Notes:

- N/A
2024-09-10 17:41:37 -04:00
Marshall Bowers
3ff81c2e86 assistant: Simplify image insertion (#17668)
This PR simplifies how images are inserted into the context editor.

We don't need to hold the `images` in a `HashMap` on the `Context`, as
we were only inserting them to pull them out again.

Release Notes:

- N/A
2024-09-10 17:37:26 -04:00
Piotr Osiewicz
3cea7ccbff pane: Fix pinned tabs being persisted after closing (#17666)
Release Notes:

- Fixed tabs staying pinned after closing unrelated tabs
2024-09-10 17:26:06 -04:00
Thorsten Ball
48a16f9e70 ssh: Lookup language servers in env on SSH host (#17658)
Release Notes:

- ssh remoting: Lookup language server binaries in environment on SSH
host

---------

Co-authored-by: Bennet <bennet@zed.dev>
2024-09-10 16:41:29 -04:00
Peter Tripp
19463b59e2 Add docs for search settings (#17662) 2024-09-10 16:12:45 -04:00
Conrad Irwin
36eb1c15ea use ssh lsp store (#17655)
Release Notes:

- ssh remoting: Added support for booting langauge servers (in limited
circumstances)

---------

Co-authored-by: Mikayla <mikayla@zed.dev>
2024-09-10 15:51:01 -04:00
Conrad Irwin
130f19d8f9 Correctly merge settings for vtsls (#17657)
Release Notes:

- Fixed vtsls initialization_options in project settings files
2024-09-10 15:50:51 -04:00
Peter Tripp
bd1ff476b9 Revert tokenizer for custom OpenAI models (#17660)
Fix for custom openai models tokenizer settings.
2024-09-10 15:38:27 -04:00
Marshall Bowers
a23e381096 assistant: Pass up tool results in LLM request messages (#17656)
This PR makes it so we pass up the tool results in the `tool_results`
field in the request message to the LLM.

This required reworking how we track non-text content in the context
editor.

We also removed serialization of images in context history, as we were
never deserializing it, and thus it was unneeded.

Release Notes:

- N/A

---------

Co-authored-by: Antonio <antonio@zed.dev>
2024-09-10 15:25:57 -04:00
Joseph T Lyons
1b627925d3 v0.154.x dev 2024-09-10 14:40:51 -04:00
KorigamiK
ae3880e71a Add ability to open files with system default application (#17231) 2024-09-10 14:36:36 -04:00
Niklas Haas
06142f975b Use the configured UI font size for the inline assistant (#17542) 2024-09-10 14:26:48 -04:00
Peter Tripp
fb9d01b0d5 assistant: Add display_name for OpenAI and Gemini (#17508) 2024-09-10 13:41:06 -04:00
Peter Tripp
85f4c96fef Ubuntu 22 (Linux arm runner) fixes (#17643)
Our GitHub Actions Linux ARM hosted runner was running Ubuntu 20 was EOL'd.

This gets builds working on the Ubuntu 22 Linux ARM runner which have spun to replace the EOL'd one. It pushes forward our Glibc requirement for Linux ARM users (was >= 2.29, now >= 2.35; sorry!) but also uses a newer version of clang/llvm (was 10, now 15; yay!).
2024-09-10 12:40:54 -04:00
Bennet Bo Fenner
0b0cd9005e assistant: Fix file slash command not allowing to select multiple files when pressing tab (#17652)
Release Notes:

- Allow to add multiple files in a single `/file` command when pressing
tab
2024-09-10 11:58:16 -04:00
Eric Andres
5f61e3140f Fix vim surround behavior around text objects (#17603)
Performing `ysa")` on `"Hello World"` should produce `("Hello World")`.
Instead it places the parens inside the quotes (i.e. `"(Hello World)"`).
This PR fixes the behavior by preserving the `around` flag from the
operator sequence.

Closes #12976 and partially fixes #13841

Release Notes:

- Fixed the behavior of surrounding a text object in vim.
2024-09-10 11:49:04 -04:00
Bennet Bo Fenner
d5498c52f8 assistant: Fix terminal inline assistant not showing retry on error (#17651)
Release Notes:

- Fixed an issue where a failed inline assistant prompt could not be
restarted
2024-09-10 11:45:21 -04:00
Bennet Bo Fenner
a7ac37156c assistant: Fix configuration page showing incorrect Anthropic API key label (#17650)
Release Notes:

- N/A
2024-09-10 11:23:50 -04:00
Marshall Bowers
a078cb104c Disable definition lists in Markdown (#17648)
This PR disables definition list support in `pulldown_cmark`, as it is
has been causing a number of issues.

I opened an issue upstream with the panic we were seeing:
https://github.com/pulldown-cmark/pulldown-cmark/issues/957.

Release Notes:

- N/A
2024-09-10 11:16:27 -04:00
Bennet Bo Fenner
93b3520c11 assistant: Prevent possible execution of generated terminal commands (#17647)
Closes #17424

Release Notes:

- Fixed an issue where commands generated by the terminal command could
sometimes be executed without confirmation
2024-09-10 11:03:44 -04:00
Thorsten Ball
bf64c0899f go: Fix regression by restoring regex to match tests (#17645)
This fixes a regression that snuck in with #17108.

When running a single test with `go test` the
regex wouldn't be used anymore.

This restores the old behavior.

Release Notes:

- Fixed a regression when running Go tests. A recent change dropped the
regex used to match single test names when using `go test` in tasks to
run tests. That could lead to more or the wrong tests being run. This
restores the old behavior.
2024-09-10 10:22:12 -04:00
Piotr Osiewicz
929eff815c project panel: Get rid of unwrap in autofolding code (#17641)
@WeetHet spotted a crash in recently-introduced project panel
autofolding that relates to unwrapping.

Release Notes:

- N/A
2024-09-10 08:50:23 -04:00
Piotr Osiewicz
75256bdfe1 lsp: Add support for workspace/workspaceFolders request (#17639)
Related to: #17574

Release Notes:

- N/A
2024-09-10 08:41:02 -04:00
Piotr Osiewicz
56bc3c36ad project search: make sorting comparator comply with Ord preconditions (#17604)
Closes #17493
/cc @SomeoneToIgnore /cc @ConradIrwin 

Release Notes:

- N/A
2024-09-10 06:55:46 -04:00
Kenichi Kamiya
2fc74a1b71 Update doc comments with tabs.file_icons default (#17629)
The diff only contains doc comments changes, however I expect this also
fixes generating JSON Schema which generated by
[schemars](092dc17ae4/docs/examples/6-doc_comments.md).

This default value is actually true at first.

1818fef32f

However, it was changed in the following commit.

bf7e474bbc

Closes #17628

Release Notes:

- N/A
2024-09-10 06:50:43 -04:00
ZZzzaaKK
f71cb14d7a Add ',' to word chars for line wrapping (#17590)
Closes #16407 

Adds ',' to the is_word_char() matches for line wrapping, so that commas
aren't wrapped to the start of a new line.

Release Notes:

- N/A
2024-09-09 14:52:43 -07:00
Marshall Bowers
da9601c698 markdown: Handle definition lists in parser (#17617)
Resolves https://github.com/zed-industries/zed/issues/17607.

This PR makes it so the Markdown parser can handle Markdown containing
definition lists.

Note that this is just parser support, we aren't yet doing anything with
the definition lists themselves.

Release Notes:

- N/A
2024-09-09 17:49:40 -04:00
Sergio Nonide
c1193875e8 Fix blurry cursor on Wayland at a scale other than 100% (#17496)
Closes #13258

Release Notes:

- Fixed blurry mouse cursor on wayland when the screen scale is other
than 100%

Before:
![Screenshot from 2024-09-06
14-38-30](https://github.com/user-attachments/assets/e4553503-ecea-4b53-b80d-43732d34fa62)

After:
![Screenshot from 2024-09-06
14-38-56](https://github.com/user-attachments/assets/ce563d3a-2b44-44b9-9f59-f0042609924e)
2024-09-09 14:44:02 -07:00
Taras Martyniuk
f92d0de58d terraform: Update indents (#17200)
Closes #15988

Fixed indent configuration for terraform/HCL

Release Notes:
- N/A



https://github.com/user-attachments/assets/09b44ac9-ef09-463c-876d-0fbcdd1f09c9

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-09-09 14:31:57 -07:00
Fernando Tagawa
59be07ad90 x11: Implement Drag and Drop (#17491)
Closes #16225

Release Notes:

- x11: Implemented Drag and Drop.
2024-09-09 14:27:45 -07:00
Piotr Osiewicz
12dde17608 assistant panel: automatically insert selections (#17589)
Addresses parts of feedback from
https://www.jacobcolling.com/friction-log/zed-friction-log

Release Notes:
- "Assistant::NewContext" now automatically does quote selection as well
- "Assistant::QuoteSelection" now handles multicursor selections,
inserting multiple excerpts.
2024-09-09 12:31:55 -04:00
Piotr Osiewicz
dd257b8412 project panel: Do not allow collapsing auto-folded directory past the list of ancestors (#17594)
Closes #ISSUE

Release Notes:

- N/A
2024-09-09 09:14:07 -04:00
Bennet Bo Fenner
fcf79c0f1d assistant: Support copy/pasting creases (#17490)
https://github.com/user-attachments/assets/78a2572d-8e8f-4206-9680-dcd884e7bbbd

Release Notes:

- Added support for copying and pasting slash commands in the assistant
panel

---------

Co-authored-by: Thorsten <thorsten@zed.dev>
2024-09-09 09:01:26 -04:00
Piotr Osiewicz
66ef318823 project panel: select autofolded entries (#17520)
Closes #17252

Release Notes:

- Intermediate auto-folded project entries can now be selected and acted
upon (removed, renamed, cut, pasted).
2024-09-09 07:22:16 -04:00
Elan Ruusamäe
174e125686 doc: vim.md: Remove duplicate we we (#17565)
...also adds some puncuation
2024-09-08 10:04:44 -06:00
Conrad Irwin
657be0aa3e vim doc tweaks (#17564)
Release Notes:

- N/A
2024-09-08 08:50:35 -06:00
Vishal Bhavsar
89ae97e5e9 vim: Revert 'Y' to yank to end of line (#17563)
Closes https://github.com/zed-industries/zed/issues/17549
2024-09-08 08:38:00 -06:00
thataboy
894866da94 Refactor SearchSettings (#17550)
Related to #17179.

Simplify handling of search settings since there is no requirement to
watch for settings.json changes and update search panels while they are
opened.

Attn: @SomeoneToIgnore 

Per our discussion. Ran test on search crate. Ran `cargo fmt`.

Release Notes:

- N/A
2024-09-07 19:58:28 -04:00
saahityaedams
63188b6754 Fix parenthesis matching for file links in terminal (#17512)
Closes #17391 

Release Notes:

- Fixed parenthesis matching for file links in terminal
([#17391](https://github.com/zed-industries/zed/issues/17391))
2024-09-07 10:51:02 -04:00
thataboy
65961b80fc Add a way to configure default search options (#17179)
Closes https://github.com/zed-industries/zed/issues/4646

```json
// Search options to enable by default when opening new project and buffer searches.
"search": {
  "whole_word": false,
  "case_sensitive": false,
  "include_ignored": false,
  "regex": false
}
```

Release Notes:

- Added `search` settings section to configure default options enabled
in buffer and project searches
([#4646](https://github.com/zed-industries/zed/issues/4646))

---------

Co-authored-by: Kirill Bulatov <mail4score@gmail.com>
2024-09-07 10:25:41 -04:00
Kirill Bulatov
8985fd87c2 Fix different kinds values used for worktree_id (#17523) 2024-09-07 00:51:09 -04:00
Jason Lee
47aec5e64d Improve popup menu to leave some margin with window edges (#17159)
Release Notes:

- Improved popup menu to leave some margin with window edges.

## Updates in GPUI

- gpui: Add `snap_to_window_with_margin` method to `anchored` to support
leave margin to window edges.

## Before

<img width="609" alt="before-snap-to-window 2024-08-30 222506"
src="https://github.com/user-attachments/assets/62bb6791-7c89-4558-9484-5c7b31f5e91e">

## After

<img width="698" alt="snap-to-window1 2024-08-30 222506"
src="https://github.com/user-attachments/assets/51634e79-2a95-42fe-8362-a3c7003648eb">

<img width="622" alt="snap-to-window 2024-08-30 222506"
src="https://github.com/user-attachments/assets/43a865d6-d238-4fdc-ae9d-8160b9ba7953">
2024-09-07 03:05:57 +02:00
Piotr Osiewicz
b401f6951b pane: Add tooltips to close/unpin buttons (#17521)
These tooltips also showcase keybinds when the tab is an active one. /cc
@danilo-leal

Release Notes:

- N/A
2024-09-07 03:02:34 +02:00
Conrad Irwin
a7da16d192 vim: Fix ctrl-n/p 2024-09-06 18:48:06 -06:00
Piotr Osiewicz
095a08d9c8 chore: Another round of style lints fixes (#17519)
Closes #ISSUE

Release Notes:

- N/A
2024-09-07 02:36:55 +02:00
Danilo Leal
cfd43572c1 Tweak git blame tooltip design (#17517)
This PR adds some tiny design tweaks to the git blame tooltip. I guess
the most "notable", so to speak, thing is the addition of a divider
between the pull request and commit hash buttons. I was motivated to do
that because, at first, I was unsure what the copy button would copy
(the PR number or the hash?). From the position only, you eventually
figure out, but hopefully, the divider will help bumping this affordance
a bit more. I experimented with adding a labeled "Copy commit hash"
button, but that'd clutter the design too much. Additionally, the top
border on the footer should also slightly help indicating the commit
message area is scrollable.

| Before | After |
|--------|--------|
| <img width="518" alt="Screenshot 2024-09-06 at 4 54 41 PM"
src="https://github.com/user-attachments/assets/68d8335d-946d-4149-b241-6892c0b9577e">
| <img width="513" alt="Screenshot 2024-09-06 at 4 55 26 PM"
src="https://github.com/user-attachments/assets/1dc1b47c-1b7f-4e94-a2c1-7e54b9940689">
|

---

Release Notes:

- N/A
2024-09-06 17:12:20 -03:00
jjy
c90ae3d90a vim: Fix matching brackets with 1 offset in normal mode (#17396)
Closes #17342

Release Notes:

- N/A

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2024-09-06 14:06:30 -06:00
apricotbucket28
f91bf7fca5 blade: Update to e142a3a to fix crash (#17510)
Closes https://github.com/zed-industries/zed/issues/17005

Like https://github.com/zed-industries/zed/pull/17319, but based on
https://github.com/kvark/blade/pull/144.

This should be cherry-picked into preview if possible 🙂 

Release Notes:

- Linux: Fixed crash when closing windows on Wayland.
2024-09-06 15:54:40 -04:00
Piotr Osiewicz
832e3e7dd7 chore: Revert changes made to ui crate structure (#17516)
As a part of https://github.com/zed-industries/zed/pull/17488 I
flattened module structure of ui crate to fix module_inception lint.
However, that's actually unnecessary as we can pass that lint via a
custom knob for clippy.

Closes #ISSUE

Release Notes:

- N/A
2024-09-06 21:38:56 +02:00
Danilo Leal
986e0113e5 docs: Add copywriting and design tweaks (#17514)
Follow up to https://github.com/zed-industries/zed/pull/17505. This one
contains a bit more copywriting adjustments. Figured we were using the
"You can do x..." sentence shape quite frequently, so tried to kickstart
reducing that slightly. There are also more images not loading in
complement to the one I removed, but I'm not fully sure why that's the
case.

--- 

Release Notes:

- N/A
2024-09-06 15:57:14 -03:00
Peter Tripp
0b17c72f4e Minor improvements to release tooling (#17502)
- bump-version.sh: Push tag before branch; speeds up release action runs (built from tag).
- get-changes: Fetch GITHUB_ACCESS_TOKEN via `gh auth token` if env var unset.
2024-09-06 13:34:33 -04:00
David Soria Parra
0282c3a981 context_server: Fix arguments handling (#17478)
We accidentally do not accept prompts with an empty list of arguments,
as opposed to non given arguments list. We need to allow these. We also
not really supporting non required arguments, despite the protocol
describing it. This is a first iteration on fixing this as well.

Release Notes:

- N/A
2024-09-06 13:10:36 -04:00
Danilo Leal
7180880047 docs: Add design touch-ups (#17505)
This PR adds several design tweaks to elements of the documentation,
most notably swapping the heading title font from `PPAgrandir` to
`Lora`, which makes the docs consistent with the new website. Aside from
this, there's also some tiny, low-hanging content adjustments to a few
pages.

--- 

Release Notes:

- N/A
2024-09-06 13:43:54 -03:00
Peter Tripp
2d06d5c906 Add YAML LSP initialization_options (#17479)
Makes YAML language server configurable under `lsp.yaml-language-server`:
- Add support for `initialization_options` 
- Add support for custom `bin` specification
2024-09-06 11:42:36 -04:00
Piotr Osiewicz
903f92045a lsp: Watch paths outside of worktrees at language servers request (#17499)
Another stab at https://github.com/zed-industries/zed/pull/17173, this
time fixing the segfault found in
https://github.com/zed-industries/zed/pull/17206

Release Notes:

- Improved language server reliability in multi-worktree projects and
monorepo. We now notify the language server more reliably about which
files have changed.
2024-09-06 15:47:17 +02:00
Thorsten Ball
938c90fd3b Revert FPS counter (#17485)
**UPDATE**: Response so far seems to be that this fixes the performance
issues on Intel MacBooks. So we're going to go ahead and merge it.

This reverts the FPS counter added in 11753914d (#16422) because in this
issue someone bisected recent performance regressions down to this
commit:

- https://github.com/zed-industries/zed/issues/16729

Another issue that's possibly related:

-
https://github.com/zed-industries/zed/issues/17305#issuecomment-2332316242

We're reverting this in a PR to create a bundle that people can try out.

Assets:

- Universal Binary:
https://github.com/zed-industries/zed/actions/runs/10735702994/artifacts/1900460781
- x86/Intel:
https://github.com/zed-industries/zed/actions/runs/10735702994/artifacts/1900461236
- Apple Silicon:
https://github.com/zed-industries/zed/actions/runs/10735702994/artifacts/1900460978


Release Notes:

- Removed the recently-added FPS counter since the changes it made to
the Metal renderer on macOS could lead to performance regressions on
Intel MacBooks.

Co-authored-by: Bennet <bennet@zed.dev>
2024-09-06 15:35:00 +02:00
Thorsten Ball
54dd40878f Fix Workspace references being leaked (#17497)
We noticed that the `Workspace` was never released (along with the
`Project` and everything that comes along with that) when closing a
window.

After playing around with the LeakDetector and debugging with
`cx.on_release()` callbacks, we found two culprits: the inline assistant
and the outline panel.

Both held strong references to `View<Workspace>` after PR #16589 and PR
#16845.

This PR changes both references to `WeakView<Workspace>` which fixes the
leak but keeps the behaviour the same.

Release Notes:

- N/A

Co-authored-by: Bennet <bennet@zed.dev>
2024-09-06 15:32:34 +02:00
235 changed files with 6103 additions and 3831 deletions

View File

@@ -339,7 +339,7 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
bundle-linux-aarch64:
bundle-linux-aarch64: # this runs on ubuntu22.04
timeout-minutes: 60
name: Create arm64 Linux bundle
runs-on:
@@ -360,8 +360,8 @@ jobs:
- name: Set up Clang
run: |
sudo apt-get update
sudo apt-get install -y llvm-10 clang-10 build-essential cmake pkg-config libasound2-dev libfontconfig-dev libwayland-dev libxkbcommon-x11-dev libssl-dev libsqlite3-dev libzstd-dev libvulkan1 libgit2-dev
echo "/usr/lib/llvm-10/bin" >> $GITHUB_PATH
sudo apt-get install -y llvm-15 clang-15 build-essential cmake pkg-config libasound2-dev libfontconfig-dev libwayland-dev libxkbcommon-x11-dev libssl-dev libsqlite3-dev libzstd-dev libvulkan1 libgit2-dev
echo "/usr/lib/llvm-15/bin" >> $GITHUB_PATH
- uses: rui314/setup-mold@0bf4f07ef9048ec62a45f9dbf2f098afa49695f0 # v1
with:

36
Cargo.lock generated
View File

@@ -1660,8 +1660,8 @@ dependencies = [
[[package]]
name = "blade-graphics"
version = "0.4.0"
source = "git+https://github.com/kvark/blade?rev=fee06c42f658b36dd9ac85444a9ee2a481383695#fee06c42f658b36dd9ac85444a9ee2a481383695"
version = "0.5.0"
source = "git+https://github.com/kvark/blade?rev=e142a3a5e678eb6a13e642ad8401b1f3aa38e969#e142a3a5e678eb6a13e642ad8401b1f3aa38e969"
dependencies = [
"ash",
"ash-window",
@@ -1690,8 +1690,8 @@ dependencies = [
[[package]]
name = "blade-macros"
version = "0.2.1"
source = "git+https://github.com/kvark/blade?rev=fee06c42f658b36dd9ac85444a9ee2a481383695#fee06c42f658b36dd9ac85444a9ee2a481383695"
version = "0.3.0"
source = "git+https://github.com/kvark/blade?rev=e142a3a5e678eb6a13e642ad8401b1f3aa38e969#e142a3a5e678eb6a13e642ad8401b1f3aa38e969"
dependencies = [
"proc-macro2",
"quote",
@@ -1701,7 +1701,7 @@ dependencies = [
[[package]]
name = "blade-util"
version = "0.1.0"
source = "git+https://github.com/kvark/blade?rev=fee06c42f658b36dd9ac85444a9ee2a481383695#fee06c42f658b36dd9ac85444a9ee2a481383695"
source = "git+https://github.com/kvark/blade?rev=e142a3a5e678eb6a13e642ad8401b1f3aa38e969#e142a3a5e678eb6a13e642ad8401b1f3aa38e969"
dependencies = [
"blade-graphics",
"bytemuck",
@@ -4886,9 +4886,9 @@ dependencies = [
[[package]]
name = "glow"
version = "0.13.1"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd348e04c43b32574f2de31c8bb397d96c9fcfa1371bd4ca6d8bdc464ab121b1"
checksum = "f865cbd94bd355b89611211e49508da98a1fce0ad755c1e8448fb96711b24528"
dependencies = [
"js-sys",
"slotmap",
@@ -7868,21 +7868,6 @@ version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "performance"
version = "0.1.0"
dependencies = [
"anyhow",
"collections",
"gpui",
"log",
"schemars",
"serde",
"settings",
"util",
"workspace",
]
[[package]]
name = "perplexity"
version = "0.1.0"
@@ -14212,7 +14197,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.153.0"
version = "0.154.0"
dependencies = [
"activity_indicator",
"anyhow",
@@ -14275,7 +14260,6 @@ dependencies = [
"outline_panel",
"parking_lot",
"paths",
"performance",
"profiling",
"project",
"project_panel",
@@ -14387,7 +14371,7 @@ dependencies = [
[[package]]
name = "zed_erlang"
version = "0.0.1"
version = "0.1.0"
dependencies = [
"zed_extension_api 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -14457,7 +14441,7 @@ dependencies = [
[[package]]
name = "zed_php"
version = "0.1.3"
version = "0.2.0"
dependencies = [
"zed_extension_api 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]

View File

@@ -72,7 +72,6 @@ members = [
"crates/outline",
"crates/outline_panel",
"crates/paths",
"crates/performance",
"crates/picker",
"crates/prettier",
"crates/project",
@@ -167,7 +166,7 @@ members = [
# Tooling
#
"tooling/xtask"
"tooling/xtask",
]
default-members = ["crates/zed"]
@@ -246,7 +245,6 @@ open_ai = { path = "crates/open_ai" }
outline = { path = "crates/outline" }
outline_panel = { path = "crates/outline_panel" }
paths = { path = "crates/paths" }
performance = { path = "crates/performance" }
picker = { path = "crates/picker" }
plugin = { path = "crates/plugin" }
plugin_macros = { path = "crates/plugin_macros" }
@@ -324,9 +322,9 @@ async-watch = "0.3.1"
async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] }
base64 = "0.22"
bitflags = "2.6.0"
blade-graphics = { git = "https://github.com/kvark/blade", rev = "fee06c42f658b36dd9ac85444a9ee2a481383695" }
blade-macros = { git = "https://github.com/kvark/blade", rev = "fee06c42f658b36dd9ac85444a9ee2a481383695" }
blade-util = { git = "https://github.com/kvark/blade", rev = "fee06c42f658b36dd9ac85444a9ee2a481383695" }
blade-graphics = { git = "https://github.com/kvark/blade", rev = "e142a3a5e678eb6a13e642ad8401b1f3aa38e969" }
blade-macros = { git = "https://github.com/kvark/blade", rev = "e142a3a5e678eb6a13e642ad8401b1f3aa38e969" }
blade-util = { git = "https://github.com/kvark/blade", rev = "e142a3a5e678eb6a13e642ad8401b1f3aa38e969" }
cargo_metadata = "0.18"
cargo_toml = "0.20"
chrono = { version = "0.4", features = ["serde"] }
@@ -573,14 +571,18 @@ single_range_in_vec_init = "allow"
# There are a bunch of rules currently failing in the `style` group, so
# allow all of those, for now.
style = { level = "allow", priority = -1 }
# Temporary list of style lints that we've fixed so far.
module_inception = { level = "deny" }
question_mark = { level = "deny" }
redundant_closure = { level = "deny" }
# Individual rules that have violations in the codebase:
type_complexity = "allow"
# We often return trait objects from `new` functions.
new_ret_no_self = { level = "allow" }
# We have a few `next` functions that differ in lifetimes
# compared to Iterator::next. Yet, clippy complains about those.
should_implement_trait = { level = "allow" }
# Individual rules that have violations in the codebase:
type_complexity = "allow"
[workspace.metadata.cargo-machete]
ignored = ["bindgen", "cbindgen", "prost_build", "serde"]

View File

@@ -553,6 +553,7 @@
"ctrl-backspace": ["project_panel::Delete", { "skip_prompt": false }],
"ctrl-delete": ["project_panel::Delete", { "skip_prompt": false }],
"alt-ctrl-r": "project_panel::RevealInFileManager",
"ctrl-shift-enter": "project_panel::OpenWithSystem",
"alt-shift-f": "project_panel::NewSearchInDirectory",
"shift-down": "menu::SelectNext",
"shift-up": "menu::SelectPrev",

View File

@@ -563,8 +563,8 @@
"cmd-backspace": ["project_panel::Trash", { "skip_prompt": true }],
"cmd-delete": ["project_panel::Delete", { "skip_prompt": false }],
"alt-cmd-r": "project_panel::RevealInFileManager",
"ctrl-shift-enter": "project_panel::OpenWithSystem",
"cmd-alt-backspace": ["project_panel::Delete", { "skip_prompt": false }],
"alt-shift-f": "project_panel::NewSearchInDirectory",
"shift-down": "menu::SelectNext",
"shift-up": "menu::SelectPrev",

View File

@@ -214,7 +214,7 @@
"shift-d": "vim::DeleteToEndOfLine",
"shift-j": "vim::JoinLines",
"y": ["vim::PushOperator", "Yank"],
"shift-y": "vim::YankToEndOfLine",
"shift-y": "vim::YankLine",
"i": "vim::InsertBefore",
"shift-i": "vim::InsertFirstNonWhitespace",
"a": "vim::InsertAfter",
@@ -323,11 +323,16 @@
"ctrl-t": "vim::Indent",
"ctrl-d": "vim::Outdent",
"ctrl-k": ["vim::PushOperator", { "Digraph": {} }],
"ctrl-p": "editor::ShowCompletions",
"ctrl-n": "editor::ShowCompletions",
"ctrl-r": ["vim::PushOperator", "Register"]
}
},
{
"context": "vim_mode == insert && !(showing_code_actions || showing_completions)",
"bindings": {
"ctrl-p": "editor::ShowCompletions",
"ctrl-n": "editor::ShowCompletions"
}
},
{
"context": "vim_mode == replace",
"bindings": {
@@ -488,6 +493,7 @@
"v": "project_panel::OpenPermanent",
"p": "project_panel::Open",
"x": "project_panel::RevealInFileManager",
"s": "project_panel::OpenWithSystem",
"shift-g": "menu::SelectLast",
"g g": "menu::SelectFirst",
"-": "project_panel::SelectParent",

View File

@@ -279,6 +279,13 @@
"relative_line_numbers": false,
// If 'search_wrap' is disabled, search result do not wrap around the end of the file.
"search_wrap": true,
// Search options to enable by default when opening new project and buffer searches.
"search": {
"whole_word": false,
"case_sensitive": false,
"include_ignored": false,
"regex": false
},
// When to populate a new search's query based on the text under the cursor.
// This setting can take the following three values:
//
@@ -432,6 +439,7 @@
// The list of language servers to use (or disable) for all languages.
//
// This is typically customized on a per-language basis.
"rainbow_brackets": true,
"language_servers": ["..."],
// When to automatically save edited buffers. This setting can
// take four values.

1
clippy.toml Normal file
View File

@@ -0,0 +1 @@
allow-private-module-inception = true

View File

@@ -11,7 +11,7 @@ use crate::{
},
slash_command_picker,
terminal_inline_assistant::TerminalInlineAssistant,
Assist, CacheStatus, ConfirmCommand, Context, ContextEvent, ContextId, ContextStore,
Assist, CacheStatus, ConfirmCommand, Content, Context, ContextEvent, ContextId, ContextStore,
ContextStoreEvent, CycleMessageRole, DeployHistory, DeployPromptLibrary, InlineAssistId,
InlineAssistant, InsertDraggedFiles, InsertIntoEditor, Message, MessageId, MessageMetadata,
MessageStatus, ModelPickerDelegate, ModelSelector, NewContext, PendingSlashCommand,
@@ -26,14 +26,15 @@ use collections::{BTreeSet, HashMap, HashSet};
use editor::{
actions::{FoldAt, MoveToEndOfLine, Newline, ShowCompletions, UnfoldAt},
display_map::{
BlockDisposition, BlockId, BlockProperties, BlockStyle, Crease, CustomBlockId, FoldId,
RenderBlock, ToDisplayPoint,
BlockDisposition, BlockId, BlockProperties, BlockStyle, Crease, CreaseMetadata,
CustomBlockId, FoldId, RenderBlock, ToDisplayPoint,
},
scroll::{Autoscroll, AutoscrollStrategy, ScrollAnchor},
Anchor, Editor, EditorEvent, ExcerptRange, MultiBuffer, RowExt, ToOffset as _, ToPoint,
};
use editor::{display_map::CreaseId, FoldPlaceholder};
use fs::Fs;
use futures::FutureExt;
use gpui::{
canvas, div, img, percentage, point, pulsating_between, size, Action, Animation, AnimationExt,
AnyElement, AnyView, AppContext, AsyncWindowContext, ClipboardEntry, ClipboardItem,
@@ -50,17 +51,19 @@ use language_model::{
provider::cloud::PROVIDER_ID, LanguageModelProvider, LanguageModelProviderId,
LanguageModelRegistry, Role,
};
use language_model::{LanguageModelImage, LanguageModelToolUse};
use multi_buffer::MultiBufferRow;
use picker::{Picker, PickerDelegate};
use project::{Project, ProjectLspAdapterDelegate, Worktree};
use project::lsp_store::ProjectLspAdapterDelegate;
use project::{Project, Worktree};
use search::{buffer_search::DivRegistrar, BufferSearchBar};
use serde::{Deserialize, Serialize};
use settings::{update_settings_file, Settings};
use smol::stream::StreamExt;
use std::{
borrow::Cow,
cmp,
collections::hash_map,
fmt::Write,
ops::{ControlFlow, Range},
path::PathBuf,
sync::Arc,
@@ -329,7 +332,7 @@ impl AssistantPanel {
cx: &mut ViewContext<Self>,
) -> Self {
let model_selector_menu_handle = PopoverMenuHandle::default();
let model_summary_editor = cx.new_view(|cx| Editor::single_line(cx));
let model_summary_editor = cx.new_view(Editor::single_line);
let context_editor_toolbar = cx.new_view(|_| {
ContextEditorToolbarItem::new(
workspace,
@@ -341,7 +344,7 @@ impl AssistantPanel {
let pane = cx.new_view(|cx| {
let mut pane = Pane::new(
workspace.weak_handle(),
workspace.project().downgrade(),
workspace.project().clone(),
Default::default(),
None,
NewContext.boxed_clone(),
@@ -939,9 +942,16 @@ impl AssistantPanel {
cx: &mut ViewContext<Workspace>,
) {
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
panel.update(cx, |panel, cx| {
panel.new_context(cx);
});
let did_create_context = panel
.update(cx, |panel, cx| {
panel.new_context(cx)?;
Some(())
})
.is_some();
if did_create_context {
ContextEditor::quote_selection(workspace, &Default::default(), cx);
}
}
}
@@ -1097,7 +1107,7 @@ impl AssistantPanel {
pane.activate_item(configuration_item_ix, true, true, cx);
});
} else {
let configuration = cx.new_view(|cx| ConfigurationView::new(cx));
let configuration = cx.new_view(ConfigurationView::new);
self.configuration_subscription = Some(cx.subscribe(
&configuration,
|this, _, event: &ConfigurationViewEvent, cx| match event {
@@ -1988,6 +1998,20 @@ impl ContextEditor {
let buffer_row = MultiBufferRow(start.to_point(&buffer).row);
buffer_rows_to_fold.insert(buffer_row);
self.context.update(cx, |context, cx| {
context.insert_content(
Content::ToolUse {
range: tool_use.source_range.clone(),
tool_use: LanguageModelToolUse {
id: tool_use.id.to_string(),
name: tool_use.name.clone(),
input: tool_use.input.clone(),
},
},
cx,
);
});
Crease::new(
start..end,
placeholder,
@@ -2491,20 +2515,26 @@ impl ContextEditor {
.unwrap();
let buffer_row = MultiBufferRow(start.to_point(&buffer).row);
buffer_rows_to_fold.insert(buffer_row);
creases.push(Crease::new(
start..end,
FoldPlaceholder {
render: render_fold_icon_button(
cx.view().downgrade(),
section.icon,
section.label.clone(),
),
constrain_width: false,
merge_adjacent: false,
},
render_slash_command_output_toggle,
|_, _, _| Empty.into_any_element(),
));
creases.push(
Crease::new(
start..end,
FoldPlaceholder {
render: render_fold_icon_button(
cx.view().downgrade(),
section.icon,
section.label.clone(),
),
constrain_width: false,
merge_adjacent: false,
},
render_slash_command_output_toggle,
|_, _, _| Empty.into_any_element(),
)
.with_metadata(CreaseMetadata {
icon: section.icon,
label: section.label,
}),
);
}
editor.insert_creases(creases, cx);
@@ -3180,87 +3210,93 @@ impl ContextEditor {
return;
};
let selection = editor.update(cx, |editor, cx| editor.selections.newest_adjusted(cx));
let editor = editor.read(cx);
let buffer = editor.buffer().read(cx).snapshot(cx);
let range = editor::ToOffset::to_offset(&selection.start, &buffer)
..editor::ToOffset::to_offset(&selection.end, &buffer);
let selected_text = buffer.text_for_range(range.clone()).collect::<String>();
if selected_text.is_empty() {
return;
}
let start_language = buffer.language_at(range.start);
let end_language = buffer.language_at(range.end);
let language_name = if start_language == end_language {
start_language.map(|language| language.code_fence_block_name())
} else {
None
};
let language_name = language_name.as_deref().unwrap_or("");
let filename = buffer
.file_at(selection.start)
.map(|file| file.full_path(cx));
let text = if language_name == "markdown" {
selected_text
.lines()
.map(|line| format!("> {}", line))
.collect::<Vec<_>>()
.join("\n")
} else {
let start_symbols = buffer
.symbols_containing(selection.start, None)
.map(|(_, symbols)| symbols);
let end_symbols = buffer
.symbols_containing(selection.end, None)
.map(|(_, symbols)| symbols);
let outline_text =
if let Some((start_symbols, end_symbols)) = start_symbols.zip(end_symbols) {
Some(
start_symbols
.into_iter()
.zip(end_symbols)
.take_while(|(a, b)| a == b)
.map(|(a, _)| a.text)
.collect::<Vec<_>>()
.join(" > "),
)
let mut creases = vec![];
editor.update(cx, |editor, cx| {
let selections = editor.selections.all_adjusted(cx);
let buffer = editor.buffer().read(cx).snapshot(cx);
for selection in selections {
let range = editor::ToOffset::to_offset(&selection.start, &buffer)
..editor::ToOffset::to_offset(&selection.end, &buffer);
let selected_text = buffer.text_for_range(range.clone()).collect::<String>();
if selected_text.is_empty() {
continue;
}
let start_language = buffer.language_at(range.start);
let end_language = buffer.language_at(range.end);
let language_name = if start_language == end_language {
start_language.map(|language| language.code_fence_block_name())
} else {
None
};
let language_name = language_name.as_deref().unwrap_or("");
let filename = buffer
.file_at(selection.start)
.map(|file| file.full_path(cx));
let text = if language_name == "markdown" {
selected_text
.lines()
.map(|line| format!("> {}", line))
.collect::<Vec<_>>()
.join("\n")
} else {
let start_symbols = buffer
.symbols_containing(selection.start, None)
.map(|(_, symbols)| symbols);
let end_symbols = buffer
.symbols_containing(selection.end, None)
.map(|(_, symbols)| symbols);
let line_comment_prefix = start_language
.and_then(|l| l.default_scope().line_comment_prefixes().first().cloned());
let outline_text = if let Some((start_symbols, end_symbols)) =
start_symbols.zip(end_symbols)
{
Some(
start_symbols
.into_iter()
.zip(end_symbols)
.take_while(|(a, b)| a == b)
.map(|(a, _)| a.text)
.collect::<Vec<_>>()
.join(" > "),
)
} else {
None
};
let fence = codeblock_fence_for_path(
filename.as_deref(),
Some(selection.start.row..selection.end.row),
);
let line_comment_prefix = start_language
.and_then(|l| l.default_scope().line_comment_prefixes().first().cloned());
if let Some((line_comment_prefix, outline_text)) = line_comment_prefix.zip(outline_text)
{
let breadcrumb = format!("{line_comment_prefix}Excerpt from: {outline_text}\n");
format!("{fence}{breadcrumb}{selected_text}\n```")
} else {
format!("{fence}{selected_text}\n```")
let fence = codeblock_fence_for_path(
filename.as_deref(),
Some(selection.start.row..selection.end.row),
);
if let Some((line_comment_prefix, outline_text)) =
line_comment_prefix.zip(outline_text)
{
let breadcrumb =
format!("{line_comment_prefix}Excerpt from: {outline_text}\n");
format!("{fence}{breadcrumb}{selected_text}\n```")
} else {
format!("{fence}{selected_text}\n```")
}
};
let crease_title = if let Some(path) = filename {
let start_line = selection.start.row + 1;
let end_line = selection.end.row + 1;
if start_line == end_line {
format!("{}, Line {}", path.display(), start_line)
} else {
format!("{}, Lines {} to {}", path.display(), start_line, end_line)
}
} else {
"Quoted selection".to_string()
};
creases.push((text, crease_title));
}
};
let crease_title = if let Some(path) = filename {
let start_line = selection.start.row + 1;
let end_line = selection.end.row + 1;
if start_line == end_line {
format!("{}, Line {}", path.display(), start_line)
} else {
format!("{}, Lines {} to {}", path.display(), start_line, end_line)
}
} else {
"Quoted selection".to_string()
};
});
if creases.is_empty() {
return;
}
// Activate the panel
if !panel.focus_handle(cx).contains_focused(cx) {
workspace.toggle_panel_focus::<AssistantPanel>(cx);
@@ -3277,39 +3313,40 @@ impl ContextEditor {
context.update(cx, |context, cx| {
context.editor.update(cx, |editor, cx| {
editor.insert("\n", cx);
for (text, crease_title) in creases {
let point = editor.selections.newest::<Point>(cx).head();
let start_row = MultiBufferRow(point.row);
let point = editor.selections.newest::<Point>(cx).head();
let start_row = MultiBufferRow(point.row);
editor.insert(&text, cx);
editor.insert(&text, cx);
let snapshot = editor.buffer().read(cx).snapshot(cx);
let anchor_before = snapshot.anchor_after(point);
let anchor_after = editor
.selections
.newest_anchor()
.head()
.bias_left(&snapshot);
let snapshot = editor.buffer().read(cx).snapshot(cx);
let anchor_before = snapshot.anchor_after(point);
let anchor_after = editor
.selections
.newest_anchor()
.head()
.bias_left(&snapshot);
editor.insert("\n", cx);
editor.insert("\n", cx);
let fold_placeholder = quote_selection_fold_placeholder(
crease_title,
cx.view().downgrade(),
);
let crease = Crease::new(
anchor_before..anchor_after,
fold_placeholder,
render_quote_selection_output_toggle,
|_, _, _| Empty.into_any(),
);
editor.insert_creases(vec![crease], cx);
editor.fold_at(
&FoldAt {
buffer_row: start_row,
},
cx,
);
let fold_placeholder = quote_selection_fold_placeholder(
crease_title,
cx.view().downgrade(),
);
let crease = Crease::new(
anchor_before..anchor_after,
fold_placeholder,
render_quote_selection_output_toggle,
|_, _, _| Empty.into_any(),
);
editor.insert_creases(vec![crease], cx);
editor.fold_at(
&FoldAt {
buffer_row: start_row,
},
cx,
);
}
})
});
};
@@ -3318,39 +3355,113 @@ impl ContextEditor {
}
fn copy(&mut self, _: &editor::actions::Copy, cx: &mut ViewContext<Self>) {
let editor = self.editor.read(cx);
let context = self.context.read(cx);
if editor.selections.count() == 1 {
let selection = editor.selections.newest::<usize>(cx);
let mut copied_text = String::new();
let mut spanned_messages = 0;
for message in context.messages(cx) {
if message.offset_range.start >= selection.range().end {
break;
} else if message.offset_range.end >= selection.range().start {
let range = cmp::max(message.offset_range.start, selection.range().start)
..cmp::min(message.offset_range.end, selection.range().end);
if !range.is_empty() {
spanned_messages += 1;
write!(&mut copied_text, "## {}\n\n", message.role).unwrap();
for chunk in context.buffer().read(cx).text_for_range(range) {
copied_text.push_str(chunk);
}
copied_text.push('\n');
}
}
}
if spanned_messages > 1 {
cx.write_to_clipboard(ClipboardItem::new_string(copied_text));
return;
}
if self.editor.read(cx).selections.count() == 1 {
let (copied_text, metadata) = self.get_clipboard_contents(cx);
cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
copied_text,
metadata,
));
cx.stop_propagation();
return;
}
cx.propagate();
}
fn paste(&mut self, _: &editor::actions::Paste, cx: &mut ViewContext<Self>) {
fn cut(&mut self, _: &editor::actions::Cut, cx: &mut ViewContext<Self>) {
if self.editor.read(cx).selections.count() == 1 {
let (copied_text, metadata) = self.get_clipboard_contents(cx);
self.editor.update(cx, |editor, cx| {
let selections = editor.selections.all::<Point>(cx);
editor.transact(cx, |this, cx| {
this.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.select(selections);
});
this.insert("", cx);
cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
copied_text,
metadata,
));
});
});
cx.stop_propagation();
return;
}
cx.propagate();
}
fn get_clipboard_contents(&mut self, cx: &mut ViewContext<Self>) -> (String, CopyMetadata) {
let creases = self.editor.update(cx, |editor, cx| {
let selection = editor.selections.newest::<Point>(cx);
let selection_start = editor.selections.newest::<usize>(cx).start;
let snapshot = editor.buffer().read(cx).snapshot(cx);
editor.display_map.update(cx, |display_map, cx| {
display_map
.snapshot(cx)
.crease_snapshot
.creases_in_range(
MultiBufferRow(selection.start.row)..MultiBufferRow(selection.end.row + 1),
&snapshot,
)
.filter_map(|crease| {
if let Some(metadata) = &crease.metadata {
let start = crease
.range
.start
.to_offset(&snapshot)
.saturating_sub(selection_start);
let end = crease
.range
.end
.to_offset(&snapshot)
.saturating_sub(selection_start);
let range_relative_to_selection = start..end;
if range_relative_to_selection.is_empty() {
None
} else {
Some(SelectedCreaseMetadata {
range_relative_to_selection,
crease: metadata.clone(),
})
}
} else {
None
}
})
.collect::<Vec<_>>()
})
});
let context = self.context.read(cx);
let selection = self.editor.read(cx).selections.newest::<usize>(cx);
let mut text = String::new();
for message in context.messages(cx) {
if message.offset_range.start >= selection.range().end {
break;
} else if message.offset_range.end >= selection.range().start {
let range = cmp::max(message.offset_range.start, selection.range().start)
..cmp::min(message.offset_range.end, selection.range().end);
if !range.is_empty() {
for chunk in context.buffer().read(cx).text_for_range(range) {
text.push_str(chunk);
}
text.push('\n');
}
}
}
(text, CopyMetadata { creases })
}
fn paste(&mut self, action: &editor::actions::Paste, cx: &mut ViewContext<Self>) {
cx.stop_propagation();
let images = if let Some(item) = cx.read_from_clipboard() {
item.into_entries()
.filter_map(|entry| {
@@ -3365,9 +3476,62 @@ impl ContextEditor {
Vec::new()
};
let metadata = if let Some(item) = cx.read_from_clipboard() {
item.entries().first().and_then(|entry| {
if let ClipboardEntry::String(text) = entry {
text.metadata_json::<CopyMetadata>()
} else {
None
}
})
} else {
None
};
if images.is_empty() {
// If we didn't find any valid image data to paste, propagate to let normal pasting happen.
cx.propagate();
self.editor.update(cx, |editor, cx| {
let paste_position = editor.selections.newest::<usize>(cx).head();
editor.paste(action, cx);
if let Some(metadata) = metadata {
let buffer = editor.buffer().read(cx).snapshot(cx);
let mut buffer_rows_to_fold = BTreeSet::new();
let weak_editor = cx.view().downgrade();
editor.insert_creases(
metadata.creases.into_iter().map(|metadata| {
let start = buffer.anchor_after(
paste_position + metadata.range_relative_to_selection.start,
);
let end = buffer.anchor_before(
paste_position + metadata.range_relative_to_selection.end,
);
let buffer_row = MultiBufferRow(start.to_point(&buffer).row);
buffer_rows_to_fold.insert(buffer_row);
Crease::new(
start..end,
FoldPlaceholder {
constrain_width: false,
render: render_fold_icon_button(
weak_editor.clone(),
metadata.crease.icon,
metadata.crease.label.clone(),
),
merge_adjacent: false,
},
render_slash_command_output_toggle,
|_, _, _| Empty.into_any(),
)
.with_metadata(metadata.crease.clone())
}),
cx,
);
for buffer_row in buffer_rows_to_fold.into_iter().rev() {
editor.fold_at(&FoldAt { buffer_row }, cx);
}
}
});
} else {
let mut image_positions = Vec::new();
self.editor.update(cx, |editor, cx| {
@@ -3388,10 +3552,22 @@ impl ContextEditor {
self.context.update(cx, |context, cx| {
for image in images {
let Some(render_image) = image.to_image_data(cx).log_err() else {
continue;
};
let image_id = image.id();
context.insert_image(image, cx);
let image_task = LanguageModelImage::from_image(image, cx).shared();
for image_position in image_positions.iter() {
context.insert_image_anchor(image_id, image_position.text_anchor, cx);
context.insert_content(
Content::Image {
anchor: image_position.text_anchor,
image_id,
image: image_task.clone(),
render_image: render_image.clone(),
},
cx,
);
}
}
});
@@ -3406,11 +3582,23 @@ impl ContextEditor {
let new_blocks = self
.context
.read(cx)
.images(cx)
.filter_map(|image| {
.contents(cx)
.filter_map(|content| {
if let Content::Image {
anchor,
render_image,
..
} = content
{
Some((anchor, render_image))
} else {
None
}
})
.filter_map(|(anchor, render_image)| {
const MAX_HEIGHT_IN_LINES: u32 = 8;
let anchor = buffer.anchor_in_excerpt(excerpt_id, image.anchor).unwrap();
let image = image.render_image.clone();
let anchor = buffer.anchor_in_excerpt(excerpt_id, anchor).unwrap();
let image = render_image.clone();
anchor.is_valid(&buffer).then(|| BlockProperties {
position: anchor,
height: MAX_HEIGHT_IN_LINES,
@@ -4037,6 +4225,17 @@ fn render_fold_icon_button(
})
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct CopyMetadata {
creases: Vec<SelectedCreaseMetadata>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct SelectedCreaseMetadata {
range_relative_to_selection: Range<usize>,
crease: CreaseMetadata,
}
impl EventEmitter<EditorEvent> for ContextEditor {}
impl EventEmitter<SearchEvent> for ContextEditor {}
@@ -4062,6 +4261,7 @@ impl Render for ContextEditor {
.capture_action(cx.listener(ContextEditor::cancel))
.capture_action(cx.listener(ContextEditor::save))
.capture_action(cx.listener(ContextEditor::copy))
.capture_action(cx.listener(ContextEditor::cut))
.capture_action(cx.listener(ContextEditor::paste))
.capture_action(cx.listener(ContextEditor::cycle_message_role))
.capture_action(cx.listener(ContextEditor::confirm_command))
@@ -4222,8 +4422,7 @@ impl Item for ContextEditor {
}
fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
self.editor
.update(cx, |editor, cx| Item::deactivated(editor, cx))
self.editor.update(cx, Item::deactivated)
}
}
@@ -5155,9 +5354,17 @@ fn make_lsp_adapter_delegate(
.worktrees(cx)
.next()
.ok_or_else(|| anyhow!("no worktrees when constructing ProjectLspAdapterDelegate"))?;
let fs = if project.is_local() {
Some(project.fs().clone())
} else {
None
};
let http_client = project.client().http_client().clone();
project.lsp_store().update(cx, |lsp_store, cx| {
Ok(ProjectLspAdapterDelegate::new(lsp_store, &worktree, cx)
as Arc<dyn LspAdapterDelegate>)
Ok(
ProjectLspAdapterDelegate::new(lsp_store, &worktree, http_client, fs, None, cx)
as Arc<dyn LspAdapterDelegate>,
)
})
})
}

View File

@@ -160,10 +160,12 @@ impl AssistantSettingsContent {
.filter_map(|model| match model {
OpenAiModel::Custom {
name,
display_name,
max_tokens,
max_output_tokens,
} => Some(open_ai::AvailableModel {
name,
display_name,
max_tokens,
max_output_tokens,
}),

View File

@@ -17,28 +17,27 @@ use feature_flags::{FeatureFlag, FeatureFlagAppExt};
use fs::{Fs, RemoveOptions};
use futures::{
future::{self, Shared},
stream::FuturesUnordered,
FutureExt, StreamExt,
};
use gpui::{
AppContext, AsyncAppContext, Context as _, EventEmitter, Image, Model, ModelContext,
RenderImage, SharedString, Subscription, Task,
AppContext, AsyncAppContext, Context as _, EventEmitter, Model, ModelContext, RenderImage,
SharedString, Subscription, Task,
};
use language::{AnchorRangeExt, Bias, Buffer, LanguageRegistry, OffsetRangeExt, Point, ToOffset};
use language_model::{
LanguageModel, LanguageModelCacheConfiguration, LanguageModelCompletionEvent,
LanguageModelImage, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage,
LanguageModelRequestTool, MessageContent, Role, StopReason,
LanguageModelRequestTool, LanguageModelToolResult, LanguageModelToolUse, MessageContent, Role,
StopReason,
};
use open_ai::Model as OpenAiModel;
use paths::{context_images_dir, contexts_dir};
use paths::contexts_dir;
use project::Project;
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
use std::{
cmp::{self, max, Ordering},
collections::hash_map,
fmt::Debug,
iter, mem,
ops::Range,
@@ -49,7 +48,7 @@ use std::{
};
use telemetry_events::AssistantKind;
use text::BufferSnapshot;
use util::{post_inc, ResultExt, TryFutureExt};
use util::{post_inc, TryFutureExt};
use uuid::Uuid;
#[derive(Clone, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
@@ -377,23 +376,8 @@ impl MessageMetadata {
}
}
#[derive(Clone, Debug)]
pub struct MessageImage {
image_id: u64,
image: Shared<Task<Option<LanguageModelImage>>>,
}
impl PartialEq for MessageImage {
fn eq(&self, other: &Self) -> bool {
self.image_id == other.image_id
}
}
impl Eq for MessageImage {}
#[derive(Clone, Debug)]
pub struct Message {
pub image_offsets: SmallVec<[(usize, MessageImage); 1]>,
pub offset_range: Range<usize>,
pub index_range: Range<usize>,
pub anchor_range: Range<language::Anchor>,
@@ -403,60 +387,43 @@ pub struct Message {
pub cache: Option<MessageCacheMetadata>,
}
impl Message {
fn to_request_message(&self, buffer: &Buffer) -> Option<LanguageModelRequestMessage> {
let mut content = Vec::new();
let mut range_start = self.offset_range.start;
for (image_offset, message_image) in self.image_offsets.iter() {
if *image_offset != range_start {
if let Some(text) = Self::collect_text_content(buffer, range_start..*image_offset) {
content.push(text);
}
}
if let Some(image) = message_image.image.clone().now_or_never().flatten() {
content.push(language_model::MessageContent::Image(image));
}
range_start = *image_offset;
}
if range_start != self.offset_range.end {
if let Some(text) =
Self::collect_text_content(buffer, range_start..self.offset_range.end)
{
content.push(text);
}
}
if content.is_empty() {
return None;
}
Some(LanguageModelRequestMessage {
role: self.role,
content,
cache: self.cache.as_ref().map_or(false, |cache| cache.is_anchor),
})
}
fn collect_text_content(buffer: &Buffer, range: Range<usize>) -> Option<MessageContent> {
let text: String = buffer.text_for_range(range.clone()).collect();
if text.trim().is_empty() {
None
} else {
Some(MessageContent::Text(text))
}
}
#[derive(Debug, Clone)]
pub enum Content {
Image {
anchor: language::Anchor,
image_id: u64,
render_image: Arc<RenderImage>,
image: Shared<Task<Option<LanguageModelImage>>>,
},
ToolUse {
range: Range<language::Anchor>,
tool_use: LanguageModelToolUse,
},
ToolResult {
range: Range<language::Anchor>,
tool_use_id: Arc<str>,
},
}
#[derive(Clone, Debug)]
pub struct ImageAnchor {
pub anchor: language::Anchor,
pub image_id: u64,
pub render_image: Arc<RenderImage>,
pub image: Shared<Task<Option<LanguageModelImage>>>,
impl Content {
fn range(&self) -> Range<language::Anchor> {
match self {
Self::Image { anchor, .. } => *anchor..*anchor,
Self::ToolUse { range, .. } | Self::ToolResult { range, .. } => range.clone(),
}
}
fn cmp(&self, other: &Self, buffer: &BufferSnapshot) -> Ordering {
let self_range = self.range();
let other_range = other.range();
if self_range.end.cmp(&other_range.start, buffer).is_lt() {
Ordering::Less
} else if self_range.start.cmp(&other_range.end, buffer).is_gt() {
Ordering::Greater
} else {
Ordering::Equal
}
}
}
struct PendingCompletion {
@@ -500,8 +467,7 @@ pub struct Context {
slash_command_output_sections: Vec<SlashCommandOutputSection<language::Anchor>>,
pending_tool_uses_by_id: HashMap<Arc<str>, PendingToolUse>,
message_anchors: Vec<MessageAnchor>,
images: HashMap<u64, (Arc<RenderImage>, Shared<Task<Option<LanguageModelImage>>>)>,
image_anchors: Vec<ImageAnchor>,
contents: Vec<Content>,
messages_metadata: HashMap<MessageId, MessageMetadata>,
summary: Option<ContextSummary>,
pending_summary: Task<Option<()>>,
@@ -595,8 +561,7 @@ impl Context {
pending_ops: Vec::new(),
operations: Vec::new(),
message_anchors: Default::default(),
image_anchors: Default::default(),
images: Default::default(),
contents: Default::default(),
messages_metadata: Default::default(),
pending_slash_commands: Vec::new(),
finished_slash_commands: HashSet::default(),
@@ -659,11 +624,6 @@ impl Context {
id: message.id,
start: message.offset_range.start,
metadata: self.messages_metadata[&message.id].clone(),
image_offsets: message
.image_offsets
.iter()
.map(|image_offset| (image_offset.0, image_offset.1.image_id))
.collect(),
})
.collect(),
summary: self
@@ -700,7 +660,7 @@ impl Context {
telemetry: Option<Arc<Telemetry>>,
cx: &mut ModelContext<Self>,
) -> Self {
let id = saved_context.id.clone().unwrap_or_else(|| ContextId::new());
let id = saved_context.id.clone().unwrap_or_else(ContextId::new);
let mut this = Self::new(
id,
ReplicaId::default(),
@@ -1957,6 +1917,14 @@ impl Context {
output_range
});
this.insert_content(
Content::ToolResult {
range: anchor_range.clone(),
tool_use_id: tool_use_id.clone(),
},
cx,
);
cx.emit(ContextEvent::ToolFinished {
tool_use_id,
output_range: anchor_range,
@@ -2038,6 +2006,7 @@ impl Context {
let stream_completion = async {
let request_start = Instant::now();
let mut events = stream.await?;
let mut stop_reason = StopReason::EndTurn;
while let Some(event) = events.next().await {
if response_latency.is_none() {
@@ -2050,7 +2019,7 @@ impl Context {
.message_anchors
.iter()
.position(|message| message.id == assistant_message_id)?;
let event_to_emit = this.buffer.update(cx, |buffer, cx| {
this.buffer.update(cx, |buffer, cx| {
let message_old_end_offset = this.message_anchors[message_ix + 1..]
.iter()
.find(|message| message.start.is_valid(buffer))
@@ -2059,13 +2028,9 @@ impl Context {
});
match event {
LanguageModelCompletionEvent::Stop(reason) => match reason {
StopReason::ToolUse => {
return Some(ContextEvent::UsePendingTools);
}
StopReason::EndTurn => {}
StopReason::MaxTokens => {}
},
LanguageModelCompletionEvent::Stop(reason) => {
stop_reason = reason;
}
LanguageModelCompletionEvent::Text(chunk) => {
buffer.edit(
[(
@@ -2116,14 +2081,9 @@ impl Context {
);
}
}
None
});
cx.emit(ContextEvent::StreamedCompletion);
if let Some(event) = event_to_emit {
cx.emit(event);
}
Some(())
})?;
@@ -2136,13 +2096,14 @@ impl Context {
this.update_cache_status_for_completion(cx);
})?;
anyhow::Ok(())
anyhow::Ok(stop_reason)
};
let result = stream_completion.await;
this.update(&mut cx, |this, cx| {
let error_message = result
.as_ref()
.err()
.map(|error| error.to_string().trim().to_string());
@@ -2170,6 +2131,16 @@ impl Context {
error_message,
);
}
if let Ok(stop_reason) = result {
match stop_reason {
StopReason::ToolUse => {
cx.emit(ContextEvent::UsePendingTools);
}
StopReason::EndTurn => {}
StopReason::MaxTokens => {}
}
}
})
.ok();
}
@@ -2186,18 +2157,94 @@ impl Context {
pub fn to_completion_request(&self, cx: &AppContext) -> LanguageModelRequest {
let buffer = self.buffer.read(cx);
let request_messages = self
.messages(cx)
.filter(|message| message.status == MessageStatus::Done)
.filter_map(|message| message.to_request_message(&buffer))
.collect();
LanguageModelRequest {
messages: request_messages,
let mut contents = self.contents(cx).peekable();
fn collect_text_content(buffer: &Buffer, range: Range<usize>) -> Option<String> {
let text: String = buffer.text_for_range(range.clone()).collect();
if text.trim().is_empty() {
None
} else {
Some(text)
}
}
let mut completion_request = LanguageModelRequest {
messages: Vec::new(),
tools: Vec::new(),
stop: Vec::new(),
temperature: 1.0,
};
for message in self.messages(cx) {
if message.status != MessageStatus::Done {
continue;
}
let mut offset = message.offset_range.start;
let mut request_message = LanguageModelRequestMessage {
role: message.role,
content: Vec::new(),
cache: message
.cache
.as_ref()
.map_or(false, |cache| cache.is_anchor),
};
while let Some(content) = contents.peek() {
if content
.range()
.end
.cmp(&message.anchor_range.end, buffer)
.is_lt()
{
let content = contents.next().unwrap();
let range = content.range().to_offset(buffer);
request_message.content.extend(
collect_text_content(buffer, offset..range.start).map(MessageContent::Text),
);
match content {
Content::Image { image, .. } => {
if let Some(image) = image.clone().now_or_never().flatten() {
request_message
.content
.push(language_model::MessageContent::Image(image));
}
}
Content::ToolUse { tool_use, .. } => {
request_message
.content
.push(language_model::MessageContent::ToolUse(tool_use.clone()));
}
Content::ToolResult { tool_use_id, .. } => {
request_message.content.push(
language_model::MessageContent::ToolResult(
LanguageModelToolResult {
tool_use_id: tool_use_id.to_string(),
is_error: false,
content: collect_text_content(buffer, range.clone())
.unwrap_or_default(),
},
),
);
}
}
offset = range.end;
} else {
break;
}
}
request_message.content.extend(
collect_text_content(buffer, offset..message.offset_range.end)
.map(MessageContent::Text),
);
completion_request.messages.push(request_message);
}
completion_request
}
pub fn cancel_last_assist(&mut self, cx: &mut ModelContext<Self>) -> bool {
@@ -2324,53 +2371,31 @@ impl Context {
}
}
pub fn insert_image(&mut self, image: Image, cx: &mut ModelContext<Self>) -> Option<()> {
if let hash_map::Entry::Vacant(entry) = self.images.entry(image.id()) {
entry.insert((
image.to_image_data(cx).log_err()?,
LanguageModelImage::from_image(image, cx).shared(),
));
}
Some(())
}
pub fn insert_image_anchor(
&mut self,
image_id: u64,
anchor: language::Anchor,
cx: &mut ModelContext<Self>,
) -> bool {
cx.emit(ContextEvent::MessagesEdited);
pub fn insert_content(&mut self, content: Content, cx: &mut ModelContext<Self>) {
let buffer = self.buffer.read(cx);
let insertion_ix = match self
.image_anchors
.binary_search_by(|existing_anchor| anchor.cmp(&existing_anchor.anchor, buffer))
.contents
.binary_search_by(|probe| probe.cmp(&content, buffer))
{
Ok(ix) => ix,
Ok(ix) => {
self.contents.remove(ix);
ix
}
Err(ix) => ix,
};
if let Some((render_image, image)) = self.images.get(&image_id) {
self.image_anchors.insert(
insertion_ix,
ImageAnchor {
anchor,
image_id,
image: image.clone(),
render_image: render_image.clone(),
},
);
true
} else {
false
}
self.contents.insert(insertion_ix, content);
cx.emit(ContextEvent::MessagesEdited);
}
pub fn images<'a>(&'a self, _cx: &'a AppContext) -> impl 'a + Iterator<Item = ImageAnchor> {
self.image_anchors.iter().cloned()
pub fn contents<'a>(&'a self, cx: &'a AppContext) -> impl 'a + Iterator<Item = Content> {
let buffer = self.buffer.read(cx);
self.contents
.iter()
.filter(|content| {
let range = content.range();
range.start.is_valid(buffer) && range.end.is_valid(buffer)
})
.cloned()
}
pub fn split_message(
@@ -2533,22 +2558,14 @@ impl Context {
return;
}
let messages = self
.messages(cx)
.filter_map(|message| message.to_request_message(self.buffer.read(cx)))
.chain(Some(LanguageModelRequestMessage {
role: Role::User,
content: vec![
"Summarize the context into a short title without punctuation.".into(),
],
cache: false,
}));
let request = LanguageModelRequest {
messages: messages.collect(),
tools: Vec::new(),
stop: Vec::new(),
temperature: 1.0,
};
let mut request = self.to_completion_request(cx);
request.messages.push(LanguageModelRequestMessage {
role: Role::User,
content: vec![
"Summarize the context into a short title without punctuation.".into(),
],
cache: false,
});
self.pending_summary = cx.spawn(|this, mut cx| {
async move {
@@ -2648,10 +2665,8 @@ impl Context {
cx: &'a AppContext,
) -> impl 'a + Iterator<Item = Message> {
let buffer = self.buffer.read(cx);
let messages = message_anchors.enumerate();
let images = self.image_anchors.iter();
Self::messages_from_iters(buffer, &self.messages_metadata, messages, images)
Self::messages_from_iters(buffer, &self.messages_metadata, message_anchors.enumerate())
}
pub fn messages<'a>(&'a self, cx: &'a AppContext) -> impl 'a + Iterator<Item = Message> {
@@ -2662,10 +2677,8 @@ impl Context {
buffer: &'a Buffer,
metadata: &'a HashMap<MessageId, MessageMetadata>,
messages: impl Iterator<Item = (usize, &'a MessageAnchor)> + 'a,
images: impl Iterator<Item = &'a ImageAnchor> + 'a,
) -> impl 'a + Iterator<Item = Message> {
let mut messages = messages.peekable();
let mut images = images.peekable();
iter::from_fn(move || {
if let Some((start_ix, message_anchor)) = messages.next() {
@@ -2686,22 +2699,6 @@ impl Context {
let message_end_anchor = message_end.unwrap_or(language::Anchor::MAX);
let message_end = message_end_anchor.to_offset(buffer);
let mut image_offsets = SmallVec::new();
while let Some(image_anchor) = images.peek() {
if image_anchor.anchor.cmp(&message_end_anchor, buffer).is_lt() {
image_offsets.push((
image_anchor.anchor.to_offset(buffer),
MessageImage {
image_id: image_anchor.image_id,
image: image_anchor.image.clone(),
},
));
images.next();
} else {
break;
}
}
return Some(Message {
index_range: start_ix..end_ix,
offset_range: message_start..message_end,
@@ -2710,7 +2707,6 @@ impl Context {
role: metadata.role,
status: metadata.status.clone(),
cache: metadata.cache.clone(),
image_offsets,
});
}
None
@@ -2748,9 +2744,6 @@ impl Context {
})?;
if let Some(summary) = summary {
this.read_with(&cx, |this, cx| this.serialize_images(fs.clone(), cx))?
.await;
let context = this.read_with(&cx, |this, cx| this.serialize(cx))?;
let mut discriminant = 1;
let mut new_path;
@@ -2790,45 +2783,6 @@ impl Context {
});
}
pub fn serialize_images(&self, fs: Arc<dyn Fs>, cx: &AppContext) -> Task<()> {
let mut images_to_save = self
.images
.iter()
.map(|(id, (_, llm_image))| {
let fs = fs.clone();
let llm_image = llm_image.clone();
let id = *id;
async move {
if let Some(llm_image) = llm_image.await {
let path: PathBuf =
context_images_dir().join(&format!("{}.png.base64", id));
if fs
.metadata(path.as_path())
.await
.log_err()
.flatten()
.is_none()
{
fs.atomic_write(path, llm_image.source.to_string())
.await
.log_err();
}
}
}
})
.collect::<FuturesUnordered<_>>();
cx.background_executor().spawn(async move {
if fs
.create_dir(context_images_dir().as_ref())
.await
.log_err()
.is_some()
{
while let Some(_) = images_to_save.next().await {}
}
})
}
pub(crate) fn custom_summary(&mut self, custom_summary: String, cx: &mut ModelContext<Self>) {
let timestamp = self.next_timestamp();
let summary = self.summary.get_or_insert(ContextSummary::default());
@@ -2914,9 +2868,6 @@ pub struct SavedMessage {
pub id: MessageId,
pub start: usize,
pub metadata: MessageMetadata,
#[serde(default)]
// This is defaulted for backwards compatibility with JSON files created before August 2024. We didn't always have this field.
pub image_offsets: Vec<(usize, u64)>,
}
#[derive(Serialize, Deserialize)]
@@ -3102,7 +3053,6 @@ impl SavedContextV0_3_0 {
timestamp,
cache: None,
},
image_offsets: Vec::new(),
})
})
.collect(),

View File

@@ -390,7 +390,7 @@ impl ContextStore {
context_proto
.operations
.into_iter()
.map(|op| ContextOperation::from_proto(op))
.map(ContextOperation::from_proto)
.collect::<Result<Vec<_>>>()
})
.await?;
@@ -527,7 +527,7 @@ impl ContextStore {
context_proto
.operations
.into_iter()
.map(|op| ContextOperation::from_proto(op))
.map(ContextOperation::from_proto)
.collect::<Result<Vec<_>>>()
})
.await?;

View File

@@ -1921,7 +1921,7 @@ impl PromptEditor {
font_family: settings.ui_font.family.clone(),
font_features: settings.ui_font.features.clone(),
font_fallbacks: settings.ui_font.fallbacks.clone(),
font_size: rems(0.875).into(),
font_size: settings.ui_font_size.into(),
font_weight: settings.ui_font.weight,
line_height: relative(1.3),
..Default::default()
@@ -2373,20 +2373,7 @@ impl Codegen {
None
};
// Higher Temperature increases the randomness of model outputs.
// If Markdown or No Language is Known, increase the randomness for more creative output
// If Code, decrease temperature to get more deterministic outputs
let temperature = if let Some(language) = language_name.clone() {
if language.as_ref() == "Markdown" {
1.0
} else {
0.5
}
} else {
1.0
};
let language_name = language_name.as_deref();
let language_name = language_name.as_ref();
let start = buffer.point_to_buffer_offset(edit_range.start);
let end = buffer.point_to_buffer_offset(edit_range.end);
let (buffer, range) = if let Some((start, end)) = start.zip(end) {
@@ -2421,7 +2408,7 @@ impl Codegen {
messages,
tools: Vec::new(),
stop: vec!["|END|>".to_string()],
temperature,
temperature: 1.,
})
}
@@ -3073,7 +3060,7 @@ mod tests {
codegen.handle_stream(
String::new(),
range,
future::ready(Ok(chunks_rx.map(|chunk| Ok(chunk)).boxed())),
future::ready(Ok(chunks_rx.map(Ok).boxed())),
cx,
)
});
@@ -3145,7 +3132,7 @@ mod tests {
codegen.handle_stream(
String::new(),
range.clone(),
future::ready(Ok(chunks_rx.map(|chunk| Ok(chunk)).boxed())),
future::ready(Ok(chunks_rx.map(Ok).boxed())),
cx,
)
});
@@ -3220,7 +3207,7 @@ mod tests {
codegen.handle_stream(
String::new(),
range.clone(),
future::ready(Ok(chunks_rx.map(|chunk| Ok(chunk)).boxed())),
future::ready(Ok(chunks_rx.map(Ok).boxed())),
cx,
)
});
@@ -3294,7 +3281,7 @@ mod tests {
codegen.handle_stream(
String::new(),
range.clone(),
future::ready(Ok(chunks_rx.map(|chunk| Ok(chunk)).boxed())),
future::ready(Ok(chunks_rx.map(Ok).boxed())),
cx,
)
});

View File

@@ -4,7 +4,7 @@ use fs::Fs;
use futures::StreamExt;
use gpui::AssetSource;
use handlebars::{Handlebars, RenderError};
use language::BufferSnapshot;
use language::{BufferSnapshot, LanguageName};
use parking_lot::Mutex;
use serde::Serialize;
use std::{ops::Range, path::PathBuf, sync::Arc, time::Duration};
@@ -123,7 +123,7 @@ impl PromptBuilder {
if params.fs.is_dir(parent_dir).await {
let (mut changes, _watcher) = params.fs.watch(parent_dir, Duration::from_secs(1)).await;
while let Some(changed_paths) = changes.next().await {
if changed_paths.iter().any(|p| p == &templates_dir) {
if changed_paths.iter().any(|p| &p.path == &templates_dir) {
let mut log_message = format!("Prompt template overrides directory detected at {}", templates_dir.display());
if let Ok(target) = params.fs.read_link(&templates_dir).await {
log_message.push_str(" -> ");
@@ -162,18 +162,18 @@ impl PromptBuilder {
let mut combined_changes = futures::stream::select(changes, parent_changes);
while let Some(changed_paths) = combined_changes.next().await {
if changed_paths.iter().any(|p| p == &templates_dir) {
if changed_paths.iter().any(|p| &p.path == &templates_dir) {
if !params.fs.is_dir(&templates_dir).await {
log::info!("Prompt template overrides directory removed. Restoring built-in prompt templates.");
Self::register_built_in_templates(&mut handlebars.lock()).log_err();
break;
}
}
for changed_path in changed_paths {
if changed_path.starts_with(&templates_dir) && changed_path.extension().map_or(false, |ext| ext == "hbs") {
log::info!("Reloading prompt template override: {}", changed_path.display());
if let Some(content) = params.fs.load(&changed_path).await.log_err() {
let file_name = changed_path.file_stem().unwrap().to_string_lossy();
for event in changed_paths {
if event.path.starts_with(&templates_dir) && event.path.extension().map_or(false, |ext| ext == "hbs") {
log::info!("Reloading prompt template override: {}", event.path.display());
if let Some(content) = params.fs.load(&event.path).await.log_err() {
let file_name = event.path.file_stem().unwrap().to_string_lossy();
handlebars.lock().register_template_string(&file_name, content).log_err();
}
}
@@ -204,11 +204,11 @@ impl PromptBuilder {
pub fn generate_content_prompt(
&self,
user_prompt: String,
language_name: Option<&str>,
language_name: Option<&LanguageName>,
buffer: BufferSnapshot,
range: Range<usize>,
) -> Result<String, RenderError> {
let content_type = match language_name {
let content_type = match language_name.as_ref().map(|l| l.0.as_ref()) {
None | Some("Markdown" | "Plain Text") => "text",
Some(_) => "code",
};

View File

@@ -44,10 +44,9 @@ impl SlashCommand for ContextServerSlashCommand {
}
fn requires_argument(&self) -> bool {
self.prompt
.arguments
.as_ref()
.map_or(false, |args| !args.is_empty())
self.prompt.arguments.as_ref().map_or(false, |args| {
args.iter().any(|arg| arg.required == Some(true))
})
}
fn complete_argument(
@@ -179,6 +178,8 @@ fn prompt_arguments(prompt: &PromptInfo, arguments: &[String]) -> Result<HashMap
let mut map = HashMap::default();
map.insert(args[0].name.clone(), arguments.join(" "));
Ok(map)
} else if arguments.is_empty() && args[0].required == Some(false) {
Ok(HashMap::default())
} else {
Err(anyhow!("Prompt expects argument but none given"))
}
@@ -199,7 +200,7 @@ fn prompt_arguments(prompt: &PromptInfo, arguments: &[String]) -> Result<HashMap
pub fn acceptable_prompt(prompt: &PromptInfo) -> bool {
match &prompt.arguments {
None => true,
Some(args) if args.len() == 1 => true,
Some(args) if args.len() <= 1 => true,
_ => false,
}
}

View File

@@ -164,11 +164,7 @@ impl SlashCommand for FileSlashCommand {
Some(ArgumentCompletion {
label,
new_text: text,
after_completion: if path_match.is_dir {
AfterCompletion::Compose
} else {
AfterCompletion::Run
},
after_completion: AfterCompletion::Compose,
replace_previous_arguments: false,
})
})

View File

@@ -253,7 +253,7 @@ fn tab_items_for_queries(
.fold(HashMap::default(), |mut candidates, (id, path_string)| {
candidates
.entry(path_string)
.or_insert_with(|| Vec::new())
.or_insert_with(Vec::new)
.push(id);
candidates
});

View File

@@ -465,7 +465,8 @@ impl EventEmitter<PromptEditorEvent> for PromptEditor {}
impl Render for PromptEditor {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let buttons = match &self.codegen.read(cx).status {
let status = &self.codegen.read(cx).status;
let buttons = match status {
CodegenStatus::Idle => {
vec![
IconButton::new("cancel", IconName::Close)
@@ -516,7 +517,8 @@ impl Render for PromptEditor {
.tooltip(|cx| Tooltip::for_action("Cancel Assist", &menu::Cancel, cx))
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)));
if self.edited_since_done {
let has_error = matches!(status, CodegenStatus::Error(_));
if has_error || self.edited_since_done {
vec![
cancel,
IconButton::new("restart", IconName::RotateCw)
@@ -988,7 +990,7 @@ impl TerminalTransaction {
pub fn push(&mut self, hunk: String, cx: &mut AppContext) {
// Ensure that the assistant cannot accidentally execute commands that are streamed into the terminal
let input = hunk.replace(CARRIAGE_RETURN, " ");
let input = Self::sanitize_input(hunk);
self.terminal
.update(cx, |terminal, _| terminal.input(input));
}
@@ -1003,6 +1005,10 @@ impl TerminalTransaction {
terminal.input(CARRIAGE_RETURN.to_string())
});
}
fn sanitize_input(input: String) -> String {
input.replace(['\r', '\n'], "")
}
}
pub struct Codegen {

View File

@@ -116,27 +116,30 @@ impl Drop for MacOsUnmounter {
}
}
/// Whether or not to automatically check for updates.
#[derive(Clone, Copy, JsonSchema, Deserialize, Serialize)]
#[serde(default)]
#[serde(transparent)]
struct AutoUpdateSetting(bool);
/// Whether or not to automatically check for updates.
///
/// Default: true
#[derive(Clone, Copy, Default, JsonSchema, Deserialize, Serialize)]
#[serde(transparent)]
struct AutoUpdateSettingContent(bool);
impl Default for AutoUpdateSetting {
fn default() -> Self {
Self(true)
}
}
impl Settings for AutoUpdateSetting {
const KEY: Option<&'static str> = Some("auto_update");
type FileContent = Option<AutoUpdateSettingContent>;
type FileContent = Self;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
let auto_update = [sources.release_channel, sources.user]
.into_iter()
.find_map(|value| value.copied().flatten())
.unwrap_or(sources.default.ok_or_else(Self::missing_default)?);
.find_map(|value| value.copied())
.unwrap_or(*sources.default);
Ok(Self(auto_update.0))
Ok(auto_update)
}
}

View File

@@ -529,14 +529,13 @@ mod test {
let (a, b) = cx.update(|cx| {
(
one_at_a_time.spawn(cx, |_| async {
assert!(false);
Ok(2)
panic!("");
}),
one_at_a_time.spawn(cx, |_| async { Ok(3) }),
)
});
assert_eq!(a.await.unwrap(), None);
assert_eq!(a.await.unwrap(), None::<u32>);
assert_eq!(b.await.unwrap(), Some(3));
let promise = cx.update(|cx| one_at_a_time.spawn(cx, |_| async { Ok(4) }));

View File

@@ -4,30 +4,20 @@ use schemars::JsonSchema;
use serde_derive::{Deserialize, Serialize};
use settings::{Settings, SettingsSources};
#[derive(Deserialize, Debug)]
pub struct CallSettings {
pub mute_on_join: bool,
pub share_on_join: bool,
}
/// Configuration of voice calls in Zed.
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
pub struct CallSettingsContent {
#[derive(Clone, Debug, Default, Deserialize, Serialize, JsonSchema)]
#[serde(default)]
pub struct CallSettings {
/// Whether the microphone should be muted when joining a channel or a call.
///
/// Default: false
pub mute_on_join: Option<bool>,
pub mute_on_join: bool,
/// Whether your current project should be shared when joining an empty channel.
///
/// Default: true
pub share_on_join: Option<bool>,
pub share_on_join: bool,
}
impl Settings for CallSettings {
const KEY: Option<&'static str> = Some("calls");
type FileContent = CallSettingsContent;
type FileContent = Self;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
sources.json_merge()

View File

@@ -99,20 +99,26 @@ pub const CONNECTION_TIMEOUT: Duration = Duration::from_secs(20);
actions!(client, [SignIn, SignOut, Reconnect]);
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
pub struct ClientSettingsContent {
server_url: Option<String>,
#[derive(Clone, Serialize, Deserialize, JsonSchema)]
#[serde(default)]
pub struct ClientSettings {
/// The server to connect to. If the environment variable
/// ZED_SERVER_URL is set, it will override this setting.
pub server_url: String,
}
#[derive(Deserialize)]
pub struct ClientSettings {
pub server_url: String,
impl Default for ClientSettings {
fn default() -> Self {
Self {
server_url: "https://zed.dev".to_owned(),
}
}
}
impl Settings for ClientSettings {
const KEY: Option<&'static str> = None;
type FileContent = ClientSettingsContent;
type FileContent = Self;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
let mut result = sources.json_merge::<Self>()?;
@@ -124,19 +130,37 @@ impl Settings for ClientSettings {
}
#[derive(Default, Clone, Serialize, Deserialize, JsonSchema)]
pub struct ProxySettingsContent {
proxy: Option<String>,
#[serde(default)]
pub struct ProxySettings {
/// Set a proxy to use. The proxy protocol is specified by the URI scheme.
///
/// Supported URI scheme: `http`, `https`, `socks4`, `socks4a`, `socks5`,
/// `socks5h`. `http` will be used when no scheme is specified.
///
/// By default no proxy will be used, or Zed will try get proxy settings from
/// environment variables.
///
/// Examples:
/// - "proxy": "socks5://localhost:10808"
/// - "proxy": "http://127.0.0.1:10809"
#[schemars(example = "Self::example_1")]
#[schemars(example = "Self::example_2")]
pub proxy: Option<String>,
}
#[derive(Deserialize, Default)]
pub struct ProxySettings {
pub proxy: Option<String>,
impl ProxySettings {
fn example_1() -> String {
"http://127.0.0.1:10809".to_owned()
}
fn example_2() -> String {
"socks5://localhost:10808".to_owned()
}
}
impl Settings for ProxySettings {
const KEY: Option<&'static str> = None;
type FileContent = ProxySettingsContent;
type FileContent = Self;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
Ok(Self {

View File

@@ -30,7 +30,7 @@ struct CheckIsContributorParams {
}
impl CheckIsContributorParams {
fn as_contributor_selector(self) -> Result<ContributorSelector> {
fn into_contributor_selector(self) -> Result<ContributorSelector> {
if let Some(github_user_id) = self.github_user_id {
return Ok(ContributorSelector::GitHubUserId { github_user_id });
}
@@ -54,7 +54,7 @@ async fn check_is_contributor(
Extension(app): Extension<Arc<AppState>>,
Query(params): Query<CheckIsContributorParams>,
) -> Result<Json<CheckIsContributorResponse>> {
let params = params.as_contributor_selector()?;
let params = params.into_contributor_selector()?;
if RenovateBot::is_renovate_bot(&params) {
return Ok(Json(CheckIsContributorResponse {

View File

@@ -1326,9 +1326,7 @@ impl ActionEventRow {
}
pub fn calculate_json_checksum(app: Arc<AppState>, json: &impl AsRef<[u8]>) -> Option<Vec<u8>> {
let Some(checksum_seed) = app.config.zed_client_checksum_seed.as_ref() else {
return None;
};
let checksum_seed = app.config.zed_client_checksum_seed.as_ref()?;
let mut summer = Sha256::new();
summer.update(checksum_seed);

View File

@@ -52,7 +52,7 @@ async fn get_extensions(
let extension_id = filter.to_lowercase();
let mut exact_match = None;
extensions.retain(|extension| {
if extension.id.as_ref() == &extension_id {
if extension.id.as_ref() == extension_id {
exact_match = Some(extension.clone());
false
} else {

View File

@@ -872,7 +872,7 @@ fn operation_from_storage(
})
}
fn version_to_storage(version: &Vec<proto::VectorClockEntry>) -> Vec<storage::VectorClockEntry> {
fn version_to_storage(version: &[proto::VectorClockEntry]) -> Vec<storage::VectorClockEntry> {
version
.iter()
.map(|entry| storage::VectorClockEntry {
@@ -882,7 +882,7 @@ fn version_to_storage(version: &Vec<proto::VectorClockEntry>) -> Vec<storage::Ve
.collect()
}
fn version_from_storage(version: &Vec<storage::VectorClockEntry>) -> Vec<proto::VectorClockEntry> {
fn version_from_storage(version: &[storage::VectorClockEntry]) -> Vec<proto::VectorClockEntry> {
version
.iter()
.map(|entry| proto::VectorClockEntry {

View File

@@ -146,11 +146,11 @@ impl Database {
pub async fn update_dev_server_project(
&self,
id: DevServerProjectId,
paths: &Vec<String>,
paths: &[String],
user_id: UserId,
) -> crate::Result<(dev_server_project::Model, proto::DevServerProjectsUpdate)> {
self.transaction(move |tx| async move {
let paths = paths.clone();
let paths = paths.to_owned();
let Some((project, Some(dev_server))) = dev_server_project::Entity::find_by_id(id)
.find_also_related(dev_server::Entity)
.one(&*tx)

View File

@@ -5,7 +5,7 @@ use super::*;
impl Database {
pub async fn get_hosted_projects(
&self,
channel_ids: &Vec<ChannelId>,
channel_ids: &[ChannelId],
roles: &HashMap<ChannelId, ChannelRole>,
tx: &DatabaseTransaction,
) -> Result<Vec<proto::HostedProject>> {

View File

@@ -88,7 +88,7 @@ async fn main() -> Result<()> {
.route("/healthz", get(handle_liveness_probe))
.layer(Extension(mode));
let listener = TcpListener::bind(&format!("0.0.0.0:{}", config.http_port))
let listener = TcpListener::bind(format!("0.0.0.0:{}", config.http_port))
.expect("failed to bind TCP listener");
let mut on_shutdown = None;

View File

@@ -2261,11 +2261,11 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
cx_a.update(editor::init);
cx_b.update(editor::init);
// Turn inline-blame-off by default so no state is transferred without us explicitly doing so
let inline_blame_off_settings = Some(InlineBlameSettings {
let inline_blame_off_settings = InlineBlameSettings {
enabled: false,
delay_ms: None,
min_column: None,
});
delay_ms: 0,
min_column: 0,
};
cx_a.update(|cx| {
SettingsStore::update_global(cx, |store, cx| {
store.update_user_settings::<ProjectSettings>(cx, |settings| {

View File

@@ -1649,7 +1649,7 @@ async fn test_following_into_excluded_file(
cx.update(|cx| {
cx.update_global::<SettingsStore, _>(|store, cx| {
store.update_user_settings::<WorktreeSettings>(cx, |settings| {
settings.file_scan_exclusions = Some(vec!["**/.git".to_string()]);
settings.file_scan_exclusions = vec!["**/.git".to_string()];
});
});
});

View File

@@ -2328,11 +2328,11 @@ async fn test_propagate_saves_and_fs_changes(
.unwrap();
buffer_b.read_with(cx_b, |buffer, _| {
assert_eq!(&*buffer.language().unwrap().name(), "Rust");
assert_eq!(buffer.language().unwrap().name(), "Rust".into());
});
buffer_c.read_with(cx_c, |buffer, _| {
assert_eq!(&*buffer.language().unwrap().name(), "Rust");
assert_eq!(buffer.language().unwrap().name(), "Rust".into());
});
buffer_b.update(cx_b, |buf, cx| buf.edit([(0..0, "i-am-b, ")], None, cx));
buffer_c.update(cx_c, |buf, cx| buf.edit([(0..0, "i-am-c, ")], None, cx));
@@ -2432,17 +2432,17 @@ async fn test_propagate_saves_and_fs_changes(
buffer_a.read_with(cx_a, |buffer, _| {
assert_eq!(buffer.file().unwrap().path().to_str(), Some("file1.js"));
assert_eq!(&*buffer.language().unwrap().name(), "JavaScript");
assert_eq!(buffer.language().unwrap().name(), "JavaScript".into());
});
buffer_b.read_with(cx_b, |buffer, _| {
assert_eq!(buffer.file().unwrap().path().to_str(), Some("file1.js"));
assert_eq!(&*buffer.language().unwrap().name(), "JavaScript");
assert_eq!(buffer.language().unwrap().name(), "JavaScript".into());
});
buffer_c.read_with(cx_c, |buffer, _| {
assert_eq!(buffer.file().unwrap().path().to_str(), Some("file1.js"));
assert_eq!(&*buffer.language().unwrap().name(), "JavaScript");
assert_eq!(buffer.language().unwrap().name(), "JavaScript".into());
});
let new_buffer_a = project_a
@@ -3324,7 +3324,7 @@ async fn test_local_settings(
let store = cx.global::<SettingsStore>();
assert_eq!(
store
.local_settings(worktree_b.entity_id().as_u64() as _)
.local_settings(worktree_b.read(cx).id())
.collect::<Vec<_>>(),
&[
(Path::new("").into(), r#"{"tab_size":2}"#.to_string()),
@@ -3343,7 +3343,7 @@ async fn test_local_settings(
let store = cx.global::<SettingsStore>();
assert_eq!(
store
.local_settings(worktree_b.entity_id().as_u64() as _)
.local_settings(worktree_b.read(cx).id())
.collect::<Vec<_>>(),
&[
(Path::new("").into(), r#"{}"#.to_string()),
@@ -3372,7 +3372,7 @@ async fn test_local_settings(
let store = cx.global::<SettingsStore>();
assert_eq!(
store
.local_settings(worktree_b.entity_id().as_u64() as _)
.local_settings(worktree_b.read(cx).id())
.collect::<Vec<_>>(),
&[
(Path::new("a").into(), r#"{"tab_size":8}"#.to_string()),
@@ -3404,7 +3404,7 @@ async fn test_local_settings(
let store = cx.global::<SettingsStore>();
assert_eq!(
store
.local_settings(worktree_b.entity_id().as_u64() as _)
.local_settings(worktree_b.read(cx).id())
.collect::<Vec<_>>(),
&[(Path::new("a").into(), r#"{"hard_tabs":true}"#.to_string()),]
)

View File

@@ -100,7 +100,7 @@ async fn test_sharing_an_ssh_remote_project(
let file = buffer_b.read(cx).file();
assert_eq!(
all_language_settings(file, cx)
.language(Some("Rust"))
.language(Some(&("Rust".into())))
.language_servers,
["override-rust-analyzer".into()]
)

View File

@@ -1108,7 +1108,7 @@ impl Panel for ChatPanel {
settings::update_settings_file::<ChatPanelSettings>(
self.fs.clone(),
cx,
move |settings, _| settings.dock = Some(position),
move |settings, _| settings.dock = position,
);
}

View File

@@ -113,9 +113,7 @@ impl MessageEditor {
editor.set_show_indent_guides(false, cx);
editor.set_completion_provider(Box::new(MessageEditorCompletionProvider(this)));
editor.set_auto_replace_emoji_shortcode(
MessageEditorSettings::get_global(cx)
.auto_replace_emoji_shortcode
.unwrap_or_default(),
MessageEditorSettings::get_global(cx).auto_replace_emoji_shortcode,
);
});
@@ -130,9 +128,7 @@ impl MessageEditor {
cx.observe_global::<settings::SettingsStore>(|view, cx| {
view.editor.update(cx, |editor, cx| {
editor.set_auto_replace_emoji_shortcode(
MessageEditorSettings::get_global(cx)
.auto_replace_emoji_shortcode
.unwrap_or_default(),
MessageEditorSettings::get_global(cx).auto_replace_emoji_shortcode,
)
})
})
@@ -346,7 +342,7 @@ impl MessageEditor {
) -> Option<(Anchor, String, Vec<StringMatchCandidate>)> {
let end_offset = end_anchor.to_offset(buffer.read(cx));
let Some(query) = buffer.update(cx, |buffer, _| {
let query = buffer.update(cx, |buffer, _| {
let mut query = String::new();
for ch in buffer.reversed_chars_at(end_offset).take(100) {
if ch == '@' {
@@ -358,9 +354,7 @@ impl MessageEditor {
query.push(ch);
}
None
}) else {
return None;
};
})?;
let start_offset = end_offset - query.len();
let start_anchor = buffer.read(cx).anchor_before(start_offset);
@@ -414,7 +408,7 @@ impl MessageEditor {
let end_offset = end_anchor.to_offset(buffer.read(cx));
let Some(query) = buffer.update(cx, |buffer, _| {
let query = buffer.update(cx, |buffer, _| {
let mut query = String::new();
for ch in buffer.reversed_chars_at(end_offset).take(100) {
if ch == ':' {
@@ -450,9 +444,7 @@ impl MessageEditor {
query.push(ch);
}
None
}) else {
return None;
};
})?;
let start_offset = end_offset - query.len() - 1;
let start_anchor = buffer.read(cx).anchor_before(start_offset);

View File

@@ -2813,7 +2813,7 @@ impl Panel for CollabPanel {
settings::update_settings_file::<CollaborationPanelSettings>(
self.fs.clone(),
cx,
move |settings, _| settings.dock = Some(position),
move |settings, _| settings.dock = position,
);
}

View File

@@ -672,7 +672,7 @@ impl Panel for NotificationPanel {
settings::update_settings_file::<NotificationPanelSettings>(
self.fs.clone(),
cx,
move |settings, _| settings.dock = Some(position),
move |settings, _| settings.dock = position,
);
}

View File

@@ -2,58 +2,84 @@ use gpui::Pixels;
use schemars::JsonSchema;
use serde_derive::{Deserialize, Serialize};
use settings::{Settings, SettingsSources};
use ui::px;
use workspace::dock::DockPosition;
#[derive(Deserialize, Debug)]
#[derive(Clone, Deserialize, Debug, JsonSchema, Serialize)]
#[serde(default)]
pub struct CollaborationPanelSettings {
pub button: bool,
pub dock: DockPosition,
pub default_width: Pixels,
}
#[derive(Deserialize, Debug)]
pub struct ChatPanelSettings {
pub button: bool,
pub dock: DockPosition,
pub default_width: Pixels,
}
#[derive(Deserialize, Debug)]
pub struct NotificationPanelSettings {
pub button: bool,
pub dock: DockPosition,
pub default_width: Pixels,
}
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
pub struct PanelSettingsContent {
/// Whether to show the panel button in the status bar.
///
/// Default: true
pub button: Option<bool>,
pub button: bool,
/// Where to dock the panel.
///
/// Default: left
pub dock: Option<DockPosition>,
pub dock: DockPosition,
/// Default width of the panel in pixels.
///
/// Default: 240
pub default_width: Option<f32>,
pub default_width: Pixels,
}
impl Default for CollaborationPanelSettings {
fn default() -> Self {
Self {
button: true,
dock: DockPosition::Left,
default_width: px(240.),
}
}
}
#[derive(Clone, Deserialize, Debug, JsonSchema, Serialize)]
#[serde(default)]
pub struct ChatPanelSettings {
/// Whether to show the panel button in the status bar.
pub button: bool,
/// Where to dock the panel.
pub dock: DockPosition,
/// Default width of the panel in pixels.
pub default_width: Pixels,
}
impl Default for ChatPanelSettings {
fn default() -> Self {
Self {
button: true,
dock: DockPosition::Right,
default_width: px(240.),
}
}
}
#[derive(Clone, Deserialize, Debug, JsonSchema, Serialize)]
#[serde(default)]
pub struct NotificationPanelSettings {
/// Whether to show the panel button in the status bar.
pub button: bool,
/// Where to dock the panel.
pub dock: DockPosition,
/// Default width of the panel in pixels.
pub default_width: Pixels,
}
impl Default for NotificationPanelSettings {
fn default() -> Self {
Self {
button: true,
dock: DockPosition::Right,
default_width: px(380.),
}
}
}
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
#[serde(default)]
pub struct MessageEditorSettings {
/// Whether to automatically replace emoji shortcodes with emoji characters.
/// For example: typing `:wave:` gets replaced with `👋`.
///
/// Default: false
pub auto_replace_emoji_shortcode: Option<bool>,
pub auto_replace_emoji_shortcode: bool,
}
impl Settings for CollaborationPanelSettings {
const KEY: Option<&'static str> = Some("collaboration_panel");
type FileContent = PanelSettingsContent;
type FileContent = Self;
fn load(
sources: SettingsSources<Self::FileContent>,
@@ -66,7 +92,7 @@ impl Settings for CollaborationPanelSettings {
impl Settings for ChatPanelSettings {
const KEY: Option<&'static str> = Some("chat_panel");
type FileContent = PanelSettingsContent;
type FileContent = Self;
fn load(
sources: SettingsSources<Self::FileContent>,
@@ -79,7 +105,7 @@ impl Settings for ChatPanelSettings {
impl Settings for NotificationPanelSettings {
const KEY: Option<&'static str> = Some("notification_panel");
type FileContent = PanelSettingsContent;
type FileContent = Self;
fn load(
sources: SettingsSources<Self::FileContent>,
@@ -92,7 +118,7 @@ impl Settings for NotificationPanelSettings {
impl Settings for MessageEditorSettings {
const KEY: Option<&'static str> = Some("message_editor");
type FileContent = MessageEditorSettings;
type FileContent = Self;
fn load(
sources: SettingsSources<Self::FileContent>,

View File

@@ -1250,8 +1250,8 @@ mod tests {
unimplemented!()
}
fn worktree_id(&self) -> usize {
0
fn worktree_id(&self, _: &AppContext) -> settings::WorktreeId {
settings::WorktreeId::from_usize(0)
}
fn is_private(&self) -> bool {

View File

@@ -4,23 +4,25 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources};
#[derive(Deserialize, Debug)]
#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug)]
#[serde(default)]
/// Diagnostics configuration.
pub struct ProjectDiagnosticsSettings {
/// Whether to show warnings or not by default.
pub include_warnings: bool,
}
/// Diagnostics configuration.
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
pub struct ProjectDiagnosticsSettingsContent {
/// Whether to show warnings or not by default.
///
/// Default: true
include_warnings: Option<bool>,
impl Default for ProjectDiagnosticsSettings {
fn default() -> Self {
Self {
include_warnings: true,
}
}
}
impl Settings for ProjectDiagnosticsSettings {
const KEY: Option<&'static str> = Some("diagnostics");
type FileContent = ProjectDiagnosticsSettingsContent;
type FileContent = Self;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
sources.json_merge()

View File

@@ -9,7 +9,7 @@ use settings::Settings;
use std::hash::Hash;
use theme::ThemeSettings;
use time::UtcOffset;
use ui::{prelude::*, tooltip_container, Avatar};
use ui::{prelude::*, tooltip_container, Avatar, Divider, IconButtonShape};
use workspace::Workspace;
use crate::git::blame::{CommitDetails, GitRemote};
@@ -160,6 +160,7 @@ impl Render for BlameEntryTooltip {
.gap_4()
.child(
h_flex()
.pb_1p5()
.gap_x_2()
.overflow_x_hidden()
.flex_wrap()
@@ -173,7 +174,7 @@ impl Render for BlameEntryTooltip {
)
})
.border_b_1()
.border_color(cx.theme().colors().border),
.border_color(cx.theme().colors().border_variant),
)
.child(
div()
@@ -189,10 +190,13 @@ impl Render for BlameEntryTooltip {
.text_color(cx.theme().colors().text_muted)
.w_full()
.justify_between()
.pt_1p5()
.border_t_1()
.border_color(cx.theme().colors().border_variant)
.child(absolute_timestamp)
.child(
h_flex()
.gap_2()
.gap_1p5()
.when_some(pull_request, |this, pr| {
this.child(
Button::new(
@@ -203,19 +207,20 @@ impl Render for BlameEntryTooltip {
.icon(IconName::PullRequest)
.icon_color(Color::Muted)
.icon_position(IconPosition::Start)
.style(ButtonStyle::Transparent)
.style(ButtonStyle::Subtle)
.on_click(move |_, cx| {
cx.stop_propagation();
cx.open_url(pr.url.as_str())
}),
)
})
.child(Divider::vertical())
.child(
Button::new(
"commit-sha-button",
short_commit_id.clone(),
)
.style(ButtonStyle::Transparent)
.style(ButtonStyle::Subtle)
.color(Color::Muted)
.icon(IconName::FileGit)
.icon_color(Color::Muted)
@@ -239,6 +244,8 @@ impl Render for BlameEntryTooltip {
)
.child(
IconButton::new("copy-sha-button", IconName::Copy)
.shape(IconButtonShape::Square)
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
.on_click(move |_, cx| {
cx.stop_propagation();

View File

@@ -12,7 +12,7 @@ use crate::{element::register_action, Editor, SwitchSourceHeader};
static CLANGD_SERVER_NAME: &str = "clangd";
fn is_c_language(language: &Language) -> bool {
return language.name().as_ref() == "C++" || language.name().as_ref() == "C";
return language.name() == "C++".into() || language.name() == "C".into();
}
pub fn switch_source_header(

View File

@@ -1,10 +1,11 @@
use collections::HashMap;
use gpui::{AnyElement, IntoElement};
use multi_buffer::{Anchor, AnchorRangeExt, MultiBufferRow, MultiBufferSnapshot, ToPoint};
use serde::{Deserialize, Serialize};
use std::{cmp::Ordering, ops::Range, sync::Arc};
use sum_tree::{Bias, SeekTarget, SumTree};
use text::Point;
use ui::WindowContext;
use ui::{IconName, SharedString, WindowContext};
use crate::FoldPlaceholder;
@@ -49,6 +50,31 @@ impl CreaseSnapshot {
None
}
pub fn creases_in_range<'a>(
&'a self,
range: Range<MultiBufferRow>,
snapshot: &'a MultiBufferSnapshot,
) -> impl '_ + Iterator<Item = &'a Crease> {
let start = snapshot.anchor_before(Point::new(range.start.0, 0));
let mut cursor = self.creases.cursor::<ItemSummary>();
cursor.seek(&start, Bias::Left, snapshot);
std::iter::from_fn(move || {
while let Some(item) = cursor.item() {
cursor.next(snapshot);
let crease_start = item.crease.range.start.to_point(snapshot);
let crease_end = item.crease.range.end.to_point(snapshot);
if crease_end.row > range.end.0 {
continue;
}
if crease_start.row >= range.start.0 && crease_end.row < range.end.0 {
return Some(&item.crease);
}
}
None
})
}
pub fn crease_items_with_offsets(
&self,
snapshot: &MultiBufferSnapshot,
@@ -87,6 +113,14 @@ pub struct Crease {
pub placeholder: FoldPlaceholder,
pub render_toggle: RenderToggleFn,
pub render_trailer: RenderTrailerFn,
pub metadata: Option<CreaseMetadata>,
}
/// Metadata about a [`Crease`], that is used for serialization.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct CreaseMetadata {
pub icon: IconName,
pub label: SharedString,
}
impl Crease {
@@ -124,8 +158,14 @@ impl Crease {
render_trailer: Arc::new(move |row, folded, cx| {
render_trailer(row, folded, cx).into_any_element()
}),
metadata: None,
}
}
pub fn with_metadata(mut self, metadata: CreaseMetadata) -> Self {
self.metadata = Some(metadata);
self
}
}
impl std::fmt::Debug for Crease {
@@ -304,4 +344,54 @@ mod test {
.query_row(MultiBufferRow(3), &snapshot)
.is_none());
}
#[gpui::test]
fn test_creases_in_range(cx: &mut AppContext) {
let text = "line1\nline2\nline3\nline4\nline5\nline6\nline7";
let buffer = MultiBuffer::build_simple(text, cx);
let snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
let mut crease_map = CreaseMap::default();
let creases = [
Crease::new(
snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(1, 5)),
FoldPlaceholder::test(),
|_row, _folded, _toggle, _cx| div(),
|_row, _folded, _cx| div(),
),
Crease::new(
snapshot.anchor_before(Point::new(3, 0))..snapshot.anchor_after(Point::new(3, 5)),
FoldPlaceholder::test(),
|_row, _folded, _toggle, _cx| div(),
|_row, _folded, _cx| div(),
),
Crease::new(
snapshot.anchor_before(Point::new(5, 0))..snapshot.anchor_after(Point::new(5, 5)),
FoldPlaceholder::test(),
|_row, _folded, _toggle, _cx| div(),
|_row, _folded, _cx| div(),
),
];
crease_map.insert(creases, &snapshot);
let crease_snapshot = crease_map.snapshot();
let range = MultiBufferRow(0)..MultiBufferRow(7);
let creases: Vec<_> = crease_snapshot.creases_in_range(range, &snapshot).collect();
assert_eq!(creases.len(), 3);
let range = MultiBufferRow(2)..MultiBufferRow(5);
let creases: Vec<_> = crease_snapshot.creases_in_range(range, &snapshot).collect();
assert_eq!(creases.len(), 1);
assert_eq!(creases[0].range.start.to_point(&snapshot).row, 3);
let range = MultiBufferRow(0)..MultiBufferRow(2);
let creases: Vec<_> = crease_snapshot.creases_in_range(range, &snapshot).collect();
assert_eq!(creases.len(), 1);
assert_eq!(creases[0].range.start.to_point(&snapshot).row, 1);
let range = MultiBufferRow(6)..MultiBufferRow(7);
let creases: Vec<_> = crease_snapshot.creases_in_range(range, &snapshot).collect();
assert_eq!(creases.len(), 0);
}
}

View File

@@ -59,7 +59,9 @@ use convert_case::{Case, Casing};
use debounced_delay::DebouncedDelay;
use display_map::*;
pub use display_map::{DisplayPoint, FoldPlaceholder};
pub use editor_settings::{CurrentLineHighlight, EditorSettings, ScrollBeyondLastLine};
pub use editor_settings::{
CurrentLineHighlight, EditorSettings, ScrollBeyondLastLine, SearchSettings,
};
pub use editor_settings_controls::*;
use element::LineWithInvisibles;
pub use element::{
@@ -116,14 +118,14 @@ use parking_lot::{Mutex, RwLock};
use project::project_settings::{GitGutterSetting, ProjectSettings};
use project::{
CodeAction, Completion, CompletionIntent, FormatTrigger, Item, Location, Project, ProjectPath,
ProjectTransaction, TaskSourceKind, WorktreeId,
ProjectTransaction, TaskSourceKind,
};
use rand::prelude::*;
use rpc::{proto::*, ErrorExt};
use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
use selections_collection::{resolve_multiple, MutableSelectionsCollection, SelectionsCollection};
use serde::{Deserialize, Serialize};
use settings::{update_settings_file, Settings, SettingsStore};
use settings::{update_settings_file, Settings, SettingsLocation, SettingsStore};
use smallvec::SmallVec;
use snippet::Snippet;
use std::{
@@ -4973,9 +4975,10 @@ impl Editor {
let cursor = self.selections.newest_anchor().head();
let (buffer, cursor_buffer_position) =
self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
if !user_requested
&& self.enable_inline_completions
&& !self.should_show_inline_completions(&buffer, cursor_buffer_position, cx)
&& (!self.enable_inline_completions
|| !self.should_show_inline_completions(&buffer, cursor_buffer_position, cx))
{
self.discard_inline_completion(false, cx);
return None;
@@ -8742,7 +8745,7 @@ impl Editor {
let (worktree_id, file) = project
.buffer_for_id(runnable.buffer, cx)
.and_then(|buffer| buffer.read(cx).file())
.map(|file| (WorktreeId::from_usize(file.worktree_id()), file.clone()))
.map(|file| (file.worktree_id(cx), file.clone()))
.unzip();
(project.task_inventory().clone(), worktree_id, file)
@@ -10637,7 +10640,7 @@ impl Editor {
let fs = workspace.read(cx).app_state().fs.clone();
let current_show = TabBarSettings::get_global(cx).show;
update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
setting.show = Some(!current_show);
setting.show = !current_show;
});
}
@@ -11421,7 +11424,14 @@ impl Editor {
.redacted_ranges(search_range, |file| {
if let Some(file) = file {
file.is_private()
&& EditorSettings::get(Some(file.as_ref().into()), cx).redact_private_values
&& EditorSettings::get(
Some(SettingsLocation {
worktree_id: file.worktree_id(cx),
path: file.path().as_ref(),
}),
cx,
)
.redact_private_values
} else {
false
}
@@ -12456,7 +12466,7 @@ fn inlay_hint_settings(
let language = snapshot.language_at(location);
let settings = all_language_settings(file, cx);
settings
.language(language.map(|l| l.name()).as_deref())
.language(language.map(|l| l.name()).as_ref())
.inlay_hints
}
@@ -12553,7 +12563,7 @@ impl EditorSnapshot {
let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
matches!(
ProjectSettings::get_global(cx).git.git_gutter,
Some(GitGutterSetting::TrackedFiles)
GitGutterSetting::TrackedFiles
)
});
let gutter_settings = EditorSettings::get_global(cx).gutter;

View File

@@ -3,36 +3,105 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources};
#[derive(Deserialize, Clone)]
#[derive(Clone, Serialize, Deserialize, JsonSchema)]
#[serde(default)]
pub struct EditorSettings {
/// Whether the cursor blinks in the editor.
pub cursor_blink: bool,
/// How to highlight the current line in the editor.
pub current_line_highlight: CurrentLineHighlight,
/// Whether to show the informational hover box when moving the mouse
/// over symbols in the editor.
pub hover_popover_enabled: bool,
/// Whether to pop the completions menu while typing in an editor without
/// explicitly requesting it.
pub show_completions_on_input: bool,
/// Whether to display inline and alongside documentation for items in the
/// completions menu.
pub show_completion_documentation: bool,
/// The debounce delay before re-querying the language server for completion
/// documentation when not included in original completion list.
pub completion_documentation_secondary_query_debounce: u64,
/// Whether to use additional LSP queries to format (and amend) the code after
/// every "trigger" symbol input, defined by LSP server capabilities.
pub use_on_type_format: bool,
/// Toolbar related settings
pub toolbar: Toolbar,
/// Scrollbar related settings
pub scrollbar: Scrollbar,
/// Gutter related settings
pub gutter: Gutter,
/// Whether the editor will scroll beyond the last line.
pub scroll_beyond_last_line: ScrollBeyondLastLine,
/// The number of lines to keep above/below the cursor when auto-scrolling.
pub vertical_scroll_margin: f32,
/// Scroll sensitivity multiplier. This multiplier is applied
/// to both the horizontal and vertical delta values while scrolling.
pub scroll_sensitivity: f32,
/// Whether the line numbers on editors gutter are relative or not.
pub relative_line_numbers: bool,
/// When to populate a new search's query based on the text under the cursor.
pub seed_search_query_from_cursor: SeedQuerySetting,
pub use_smartcase_search: bool,
/// The key to use for adding multiple cursors
pub multi_cursor_modifier: MultiCursorModifier,
/// Hide the values of variables in `private` files, as defined by the
/// private_files setting. This only changes the visual representation,
/// the values are still present in the file and can be selected / copied / pasted
pub redact_private_values: bool,
/// How many lines to expand the multibuffer excerpts by default
pub expand_excerpt_lines: u32,
pub middle_click_paste: bool,
/// What to do when multibuffer is double clicked in some of its excerpts
/// (parts of singleton buffers).
#[serde(default)]
pub double_click_in_multibuffer: DoubleClickInMultibuffer,
/// Whether the editor search results will loop
pub search_wrap: bool,
#[serde(default)]
pub search: SearchSettings,
/// Show method signatures in the editor, when inside parentheses.
pub auto_signature_help: bool,
/// Whether to show the signature help after completion or a bracket pair inserted.
/// If `auto_signature_help` is enabled, this setting will be treated as enabled also.
pub show_signature_help_after_edits: bool,
/// Jupyter REPL settings.
pub jupyter: Jupyter,
}
impl Default for EditorSettings {
fn default() -> Self {
Self {
cursor_blink: true,
current_line_highlight: CurrentLineHighlight::All,
hover_popover_enabled: true,
show_completions_on_input: true,
show_completion_documentation: true,
completion_documentation_secondary_query_debounce: 300,
use_on_type_format: true,
toolbar: Default::default(),
scrollbar: Default::default(),
gutter: Default::default(),
scroll_beyond_last_line: ScrollBeyondLastLine::OnePage,
vertical_scroll_margin: 3.,
scroll_sensitivity: 1.0,
relative_line_numbers: false,
seed_search_query_from_cursor: SeedQuerySetting::Always,
multi_cursor_modifier: MultiCursorModifier::Alt,
redact_private_values: false,
expand_excerpt_lines: 3,
double_click_in_multibuffer: DoubleClickInMultibuffer::Select,
search_wrap: true,
auto_signature_help: false,
show_signature_help_after_edits: true,
jupyter: Default::default(),
use_smartcase_search: false,
middle_click_paste: true,
search: SearchSettings::default(),
}
}
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum CurrentLineHighlight {
@@ -70,48 +139,93 @@ pub enum DoubleClickInMultibuffer {
Open,
}
#[derive(Debug, Clone, Deserialize)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
pub struct Jupyter {
/// Whether the Jupyter feature is enabled.
///
/// Default: true
pub enabled: bool,
}
#[derive(Default, Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct JupyterContent {
/// Whether the Jupyter feature is enabled.
///
/// Default: true
pub enabled: Option<bool>,
impl Default for Jupyter {
fn default() -> Self {
Self { enabled: true }
}
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(default)]
pub struct Toolbar {
/// Whether to display breadcrumbs in the editor toolbar.
pub breadcrumbs: bool,
/// Whether to display quick action buttons in the editor toolbar.
pub quick_actions: bool,
/// Whether to show the selections menu in the editor toolbar
pub selections_menu: bool,
}
impl Default for Toolbar {
fn default() -> Self {
Self {
breadcrumbs: true,
quick_actions: true,
selections_menu: true,
}
}
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
pub struct Scrollbar {
/// When to show the scrollbar in the editor.
pub show: ShowScrollbar,
/// Whether to show git diff indicators in the scrollbar.
pub git_diff: bool,
/// Whether to show buffer search result indicators in the scrollbar.
pub selected_symbol: bool,
/// Whether to show selected symbol occurrences in the scrollbar.
pub search_results: bool,
/// Whether to show diagnostic indicators in the scrollbar.
pub diagnostics: bool,
/// Whether to show cursor positions in the scrollbar.
pub cursors: bool,
}
impl Default for Scrollbar {
fn default() -> Self {
Self {
show: ShowScrollbar::Auto,
git_diff: true,
selected_symbol: true,
search_results: true,
diagnostics: true,
cursors: true,
}
}
}
/// Gutter-related settings.
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(default)]
pub struct Gutter {
/// Whether to show line numbers in the gutter.
pub line_numbers: bool,
/// Whether to show code action buttons in the gutter.
pub code_actions: bool,
/// Whether to show runnable buttons in the gutter.
pub runnables: bool,
/// Whether to show fold buttons in the gutter.
pub folds: bool,
}
impl Default for Gutter {
fn default() -> Self {
Self {
line_numbers: true,
code_actions: true,
runnables: true,
folds: true,
}
}
}
/// When to show the scrollbar in the editor.
///
/// Default: auto
@@ -156,181 +270,17 @@ pub enum ScrollBeyondLastLine {
VerticalScrollMargin,
}
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
pub struct EditorSettingsContent {
/// Whether the cursor blinks in the editor.
///
/// Default: true
pub cursor_blink: Option<bool>,
/// How to highlight the current line in the editor.
///
/// Default: all
pub current_line_highlight: Option<CurrentLineHighlight>,
/// Whether to show the informational hover box when moving the mouse
/// over symbols in the editor.
///
/// Default: true
pub hover_popover_enabled: Option<bool>,
/// Whether to pop the completions menu while typing in an editor without
/// explicitly requesting it.
///
/// Default: true
pub show_completions_on_input: Option<bool>,
/// Whether to display inline and alongside documentation for items in the
/// completions menu.
///
/// Default: true
pub show_completion_documentation: Option<bool>,
/// The debounce delay before re-querying the language server for completion
/// documentation when not included in original completion list.
///
/// Default: 300 ms
pub completion_documentation_secondary_query_debounce: Option<u64>,
/// Whether to use additional LSP queries to format (and amend) the code after
/// every "trigger" symbol input, defined by LSP server capabilities.
///
/// Default: true
pub use_on_type_format: Option<bool>,
/// Toolbar related settings
pub toolbar: Option<ToolbarContent>,
/// Scrollbar related settings
pub scrollbar: Option<ScrollbarContent>,
/// Gutter related settings
pub gutter: Option<GutterContent>,
/// Whether the editor will scroll beyond the last line.
///
/// Default: one_page
pub scroll_beyond_last_line: Option<ScrollBeyondLastLine>,
/// The number of lines to keep above/below the cursor when auto-scrolling.
///
/// Default: 3.
pub vertical_scroll_margin: Option<f32>,
/// Scroll sensitivity multiplier. This multiplier is applied
/// to both the horizontal and vertical delta values while scrolling.
///
/// Default: 1.0
pub scroll_sensitivity: Option<f32>,
/// Whether the line numbers on editors gutter are relative or not.
///
/// Default: false
pub relative_line_numbers: Option<bool>,
/// When to populate a new search's query based on the text under the cursor.
///
/// Default: always
pub seed_search_query_from_cursor: Option<SeedQuerySetting>,
pub use_smartcase_search: Option<bool>,
/// The key to use for adding multiple cursors
///
/// Default: alt
pub multi_cursor_modifier: Option<MultiCursorModifier>,
/// Hide the values of variables in `private` files, as defined by the
/// private_files setting. This only changes the visual representation,
/// the values are still present in the file and can be selected / copied / pasted
///
/// Default: false
pub redact_private_values: Option<bool>,
/// How many lines to expand the multibuffer excerpts by default
///
/// Default: 3
pub expand_excerpt_lines: Option<u32>,
/// Whether to enable middle-click paste on Linux
///
/// Default: true
pub middle_click_paste: Option<bool>,
/// What to do when multibuffer is double clicked in some of its excerpts
/// (parts of singleton buffers).
///
/// Default: select
pub double_click_in_multibuffer: Option<DoubleClickInMultibuffer>,
/// Whether the editor search results will loop
///
/// Default: true
pub search_wrap: Option<bool>,
/// Whether to automatically show a signature help pop-up or not.
///
/// Default: false
pub auto_signature_help: Option<bool>,
/// Whether to show the signature help pop-up after completions or bracket pairs inserted.
///
/// Default: true
pub show_signature_help_after_edits: Option<bool>,
/// Jupyter REPL settings.
pub jupyter: Option<JupyterContent>,
}
// Toolbar related settings
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
pub struct ToolbarContent {
/// Whether to display breadcrumbs in the editor toolbar.
///
/// Default: true
pub breadcrumbs: Option<bool>,
/// Whether to display quick action buttons in the editor toolbar.
///
/// Default: true
pub quick_actions: Option<bool>,
/// Whether to show the selections menu in the editor toolbar
///
/// Default: true
pub selections_menu: Option<bool>,
}
/// Scrollbar related settings
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
pub struct ScrollbarContent {
/// When to show the scrollbar in the editor.
///
/// Default: auto
pub show: Option<ShowScrollbar>,
/// Whether to show git diff indicators in the scrollbar.
///
/// Default: true
pub git_diff: Option<bool>,
/// Whether to show buffer search result indicators in the scrollbar.
///
/// Default: true
pub search_results: Option<bool>,
/// Whether to show selected symbol occurrences in the scrollbar.
///
/// Default: true
pub selected_symbol: Option<bool>,
/// Whether to show diagnostic indicators in the scrollbar.
///
/// Default: true
pub diagnostics: Option<bool>,
/// Whether to show cursor positions in the scrollbar.
///
/// Default: true
pub cursors: Option<bool>,
}
/// Gutter related settings
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
pub struct GutterContent {
/// Whether to show line numbers in the gutter.
///
/// Default: true
pub line_numbers: Option<bool>,
/// Whether to show code action buttons in the gutter.
///
/// Default: true
pub code_actions: Option<bool>,
/// Whether to show runnable buttons in the gutter.
///
/// Default: true
pub runnables: Option<bool>,
/// Whether to show fold buttons in the gutter.
///
/// Default: true
pub folds: Option<bool>,
/// Default options for buffer and project search items.
#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
pub struct SearchSettings {
#[serde(default)]
pub whole_word: bool,
#[serde(default)]
pub case_sensitive: bool,
#[serde(default)]
pub include_ignored: bool,
#[serde(default)]
pub regex: bool,
}
impl EditorSettings {
@@ -342,7 +292,7 @@ impl EditorSettings {
impl Settings for EditorSettings {
const KEY: Option<&'static str> = None;
type FileContent = EditorSettingsContent;
type FileContent = Self;
fn load(
sources: SettingsSources<Self::FileContent>,

View File

@@ -1,7 +1,7 @@
use std::sync::Arc;
use gpui::{AppContext, FontFeatures, FontWeight};
use project::project_settings::{InlineBlameSettings, ProjectSettings};
use project::project_settings::ProjectSettings;
use settings::{EditableSettingControl, Settings};
use theme::{FontFamilyCache, ThemeSettings};
use ui::{
@@ -296,14 +296,7 @@ impl EditableSettingControl for InlineGitBlameControl {
value: Self::Value,
_cx: &AppContext,
) {
if let Some(inline_blame) = settings.git.inline_blame.as_mut() {
inline_blame.enabled = value;
} else {
settings.git.inline_blame = Some(InlineBlameSettings {
enabled: false,
..Default::default()
});
}
settings.git.inline_blame.enabled = value;
}
}
@@ -349,14 +342,7 @@ impl EditableSettingControl for LineNumbersControl {
value: Self::Value,
_cx: &AppContext,
) {
if let Some(gutter) = settings.gutter.as_mut() {
gutter.line_numbers = Some(value);
} else {
settings.gutter = Some(crate::editor_settings::GutterContent {
line_numbers: Some(value),
..Default::default()
});
}
settings.gutter.line_numbers = value;
}
}
@@ -402,7 +388,7 @@ impl EditableSettingControl for RelativeLineNumbersControl {
value: Self::Value,
_cx: &AppContext,
) {
settings.relative_line_numbers = Some(value);
settings.relative_line_numbers = value;
}
}

View File

@@ -20,8 +20,8 @@ use language::{
},
BracketPairConfig,
Capability::ReadWrite,
FakeLspAdapter, IndentGuide, LanguageConfig, LanguageConfigOverride, LanguageMatcher, Override,
ParsedMarkdown, Point,
FakeLspAdapter, IndentGuide, LanguageConfig, LanguageConfigOverride, LanguageMatcher,
LanguageName, Override, ParsedMarkdown, Point,
};
use language_settings::{Formatter, FormatterList, IndentGuideSettings};
use multi_buffer::MultiBufferIndentGuide;
@@ -6964,7 +6964,7 @@ async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
cx.update(|cx| {
cx.update_global::<SettingsStore, _>(|settings, cx| {
settings.update_user_settings::<EditorSettings>(cx, |settings| {
settings.auto_signature_help = Some(true);
settings.auto_signature_help = true;
});
});
});
@@ -7105,8 +7105,8 @@ async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui:
cx.update(|cx| {
cx.update_global::<SettingsStore, _>(|settings, cx| {
settings.update_user_settings::<EditorSettings>(cx, |settings| {
settings.auto_signature_help = Some(false);
settings.show_signature_help_after_edits = Some(false);
settings.auto_signature_help = false;
settings.show_signature_help_after_edits = false;
});
});
});
@@ -7232,8 +7232,8 @@ async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui:
cx.update(|cx| {
cx.update_global::<SettingsStore, _>(|settings, cx| {
settings.update_user_settings::<EditorSettings>(cx, |settings| {
settings.auto_signature_help = Some(false);
settings.show_signature_help_after_edits = Some(true);
settings.auto_signature_help = false;
settings.show_signature_help_after_edits = true;
});
});
});
@@ -7274,8 +7274,8 @@ async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui:
cx.update(|cx| {
cx.update_global::<SettingsStore, _>(|settings, cx| {
settings.update_user_settings::<EditorSettings>(cx, |settings| {
settings.auto_signature_help = Some(true);
settings.show_signature_help_after_edits = Some(false);
settings.auto_signature_help = true;
settings.show_signature_help_after_edits = false;
});
});
});
@@ -7318,7 +7318,7 @@ async fn test_signature_help(cx: &mut gpui::TestAppContext) {
cx.update(|cx| {
cx.update_global::<SettingsStore, _>(|settings, cx| {
settings.update_user_settings::<EditorSettings>(cx, |settings| {
settings.auto_signature_help = Some(true);
settings.auto_signature_help = true;
});
});
});
@@ -7759,7 +7759,7 @@ async fn test_completion(cx: &mut gpui::TestAppContext) {
cx.update(|cx| {
cx.update_global::<SettingsStore, _>(|settings, cx| {
settings.update_user_settings::<EditorSettings>(cx, |settings| {
settings.show_completions_on_input = Some(false);
settings.show_completions_on_input = false;
});
})
});
@@ -9587,12 +9587,12 @@ async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::Test
let server_restarts = Arc::new(AtomicUsize::new(0));
let closure_restarts = Arc::clone(&server_restarts);
let language_server_name = "test language server";
let language_name: Arc<str> = "Rust".into();
let language_name: LanguageName = "Rust".into();
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
language_registry.add(Arc::new(Language::new(
LanguageConfig {
name: Arc::clone(&language_name),
name: language_name.clone(),
matcher: LanguageMatcher {
path_suffixes: vec!["rs".to_string()],
..Default::default()
@@ -9629,7 +9629,7 @@ async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::Test
let _fake_server = fake_servers.next().await.unwrap();
update_test_language_settings(cx, |language_settings| {
language_settings.languages.insert(
Arc::clone(&language_name),
language_name.clone(),
LanguageSettingsContent {
tab_size: NonZeroU32::new(8),
..Default::default()

View File

@@ -1283,10 +1283,7 @@ impl EditorElement {
.row,
);
let git_gutter_setting = ProjectSettings::get_global(cx)
.git
.git_gutter
.unwrap_or_default();
let git_gutter_setting = ProjectSettings::get_global(cx).git.git_gutter;
let display_hunks = buffer_snapshot
.git_diff_hunks_in_range(buffer_start_row..buffer_end_row)
.map(|hunk| diff_hunk_to_display(&hunk, snapshot))
@@ -1366,12 +1363,10 @@ impl EditorElement {
};
let padded_line_end = line_end + em_width * INLINE_BLAME_PADDING_EM_WIDTHS;
let min_column_in_pixels = ProjectSettings::get_global(cx)
.git
.inline_blame
.and_then(|settings| settings.min_column)
.map(|col| self.column_pixels(col as usize, cx))
.unwrap_or(px(0.));
let min_column_in_pixels = self.column_pixels(
ProjectSettings::get_global(cx).git.inline_blame.min_column as usize,
cx,
);
let min_start = content_origin.x - scroll_pixel_position.x + min_column_in_pixels;
cmp::max(padded_line_end, min_start)
@@ -2732,7 +2727,7 @@ impl EditorElement {
.position(position)
.child(context_menu)
.anchor(AnchorCorner::TopLeft)
.snap_to_window(),
.snap_to_window_with_margin(px(8.)),
)
.with_priority(1)
.into_any(),
@@ -3331,7 +3326,7 @@ impl EditorElement {
.unwrap_or_else(|| {
matches!(
ProjectSettings::get_global(cx).git.git_gutter,
Some(GitGutterSetting::TrackedFiles)
GitGutterSetting::TrackedFiles
)
});
if show_git_gutter {

View File

@@ -1,4 +1,5 @@
use gpui::ViewContext;
use language::CursorShape;
use crate::{Editor, RangeToAnchorExt};
@@ -13,11 +14,18 @@ pub fn refresh_matching_bracket_highlights(editor: &mut Editor, cx: &mut ViewCon
return;
}
let head = newest_selection.head();
let snapshot = editor.snapshot(cx);
let head = newest_selection.head();
let mut tail = head;
if (editor.cursor_shape == CursorShape::Block || editor.cursor_shape == CursorShape::Hollow)
&& head < snapshot.buffer_snapshot.len()
{
tail += 1;
}
if let Some((opening_range, closing_range)) = snapshot
.buffer_snapshot
.innermost_enclosing_bracket_ranges(head..head, None)
.innermost_enclosing_bracket_ranges(head..tail, None)
{
editor.highlight_background::<MatchingBracketHighlight>(
&[

View File

@@ -1054,28 +1054,12 @@ impl SerializableItem for Editor {
let buffer = buffer_task.await?;
pane.update(&mut cx, |_, cx| {
let editor = cx.new_view(|cx| {
cx.new_view(|cx| {
let mut editor = Editor::for_buffer(buffer, Some(project), cx);
editor.read_scroll_position_from_db(item_id, workspace_id, cx);
editor
});
// let weak = editor.model.downgrade();
// cx.spawn(|_, cx| async move {
// for i in 0..5 {
// println!("{i}/5: going to sleep for 5 seconds");
// cx.background_executor()
// .timer(std::time::Duration::from_secs(5))
// .await;
// }
// println!("done sleeping");
// weak.assert_released();
// })
// .detach();
editor
})
})
})
}
@@ -1721,8 +1705,8 @@ mod tests {
let buffer = editor.buffer().read(cx).as_singleton().unwrap().read(cx);
assert_eq!(
buffer.language().map(|lang| lang.name()).as_deref(),
Some("Rust")
buffer.language().map(|lang| lang.name()),
Some("Rust".into())
); // Language should be set to Rust
assert!(buffer.file().is_none()); // The buffer should not have an associated file
});

View File

@@ -13,7 +13,7 @@ use crate::{
static RUST_ANALYZER_NAME: &str = "rust-analyzer";
fn is_rust_language(language: &Language) -> bool {
language.name().as_ref() == "Rust"
language.name() == "Rust".into()
}
pub fn apply_related_actions(editor: &View<Editor>, cx: &mut WindowContext) {

View File

@@ -58,7 +58,7 @@ impl EditorLspTestContext {
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
let mut fake_servers = language_registry.register_fake_lsp_adapter(
language.name().as_ref(),
language.name(),
FakeLspAdapter {
capabilities,
..Default::default()

View File

@@ -38,7 +38,6 @@ impl LspAdapter for ExtensionLspAdapter {
fn get_language_server_command<'a>(
self: Arc<Self>,
_: Arc<Language>,
_: Arc<Path>,
delegate: Arc<dyn LspAdapterDelegate>,
_: futures::lock::MutexGuard<'a, Option<LanguageServerBinary>>,

View File

@@ -1,7 +1,7 @@
use anyhow::{anyhow, Context, Result};
use collections::{BTreeMap, HashMap};
use fs::Fs;
use language::LanguageServerName;
use language::{LanguageName, LanguageServerName};
use semantic_version::SemanticVersion;
use serde::{Deserialize, Serialize};
use std::{
@@ -106,10 +106,10 @@ pub struct GrammarManifestEntry {
pub struct LanguageServerManifestEntry {
/// Deprecated in favor of `languages`.
#[serde(default)]
language: Option<Arc<str>>,
language: Option<LanguageName>,
/// The list of languages this language server should work with.
#[serde(default)]
languages: Vec<Arc<str>>,
languages: Vec<LanguageName>,
#[serde(default)]
pub language_ids: HashMap<String, String>,
#[serde(default)]
@@ -124,7 +124,7 @@ impl LanguageServerManifestEntry {
///
/// We can replace this with just field access for the `languages` field once
/// we have removed `language`.
pub fn languages(&self) -> impl IntoIterator<Item = Arc<str>> + '_ {
pub fn languages(&self) -> impl IntoIterator<Item = LanguageName> + '_ {
let language = if self.languages.is_empty() {
self.language.clone()
} else {

View File

@@ -6,18 +6,25 @@ use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources};
use std::sync::Arc;
#[derive(Deserialize, Serialize, Debug, Default, Clone, JsonSchema)]
#[derive(Deserialize, Serialize, Debug, Clone, JsonSchema)]
#[serde(default)]
pub struct ExtensionSettings {
/// The extensions that should be automatically installed by Zed.
///
/// This is used to make functionality provided by extensions (e.g., language support)
/// available out-of-the-box.
#[serde(default)]
pub auto_install_extensions: HashMap<Arc<str>, bool>,
#[serde(default)]
pub auto_update_extensions: HashMap<Arc<str>, bool>,
}
impl Default for ExtensionSettings {
fn default() -> Self {
Self {
auto_install_extensions: HashMap::from_iter([("html".into(), true)]),
auto_update_extensions: Default::default(),
}
}
}
impl ExtensionSettings {
/// Returns whether the given extension should be auto-installed.
pub fn should_auto_install(&self, extension_id: &str) -> bool {

View File

@@ -36,7 +36,8 @@ use gpui::{
use http_client::{AsyncBody, HttpClient, HttpClientWithUrl};
use indexed_docs::{IndexedDocsRegistry, ProviderId};
use language::{
LanguageConfig, LanguageMatcher, LanguageQueries, LanguageRegistry, QUERY_FILENAME_PREFIXES,
LanguageConfig, LanguageMatcher, LanguageName, LanguageQueries, LanguageRegistry,
QUERY_FILENAME_PREFIXES,
};
use node_runtime::NodeRuntime;
use project::ContextProviderWithTasks;
@@ -148,7 +149,7 @@ impl Global for GlobalExtensionStore {}
pub struct ExtensionIndex {
pub extensions: BTreeMap<Arc<str>, ExtensionIndexEntry>,
pub themes: BTreeMap<Arc<str>, ExtensionIndexThemeEntry>,
pub languages: BTreeMap<Arc<str>, ExtensionIndexLanguageEntry>,
pub languages: BTreeMap<LanguageName, ExtensionIndexLanguageEntry>,
}
#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
@@ -369,9 +370,9 @@ impl ExtensionStore {
let installed_dir = this.installed_dir.clone();
async move {
let (mut paths, _) = fs.watch(&installed_dir, FS_WATCH_LATENCY).await;
while let Some(paths) = paths.next().await {
for path in paths {
let Ok(event_path) = path.strip_prefix(&installed_dir) else {
while let Some(events) = paths.next().await {
for event in events {
let Ok(event_path) = event.path.strip_prefix(&installed_dir) else {
continue;
};

View File

@@ -609,7 +609,7 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
.await
.unwrap();
let mut fake_servers = language_registry.fake_language_servers("Gleam");
let mut fake_servers = language_registry.fake_language_servers("Gleam".into());
let buffer = project
.update(cx, |project, cx| {

View File

@@ -1,6 +1,6 @@
use crate::wasm_host::{wit::ToWasmtimeResult, WasmState};
use ::http_client::AsyncBody;
use ::settings::Settings;
use ::settings::{Settings, WorktreeId};
use anyhow::{anyhow, bail, Context, Result};
use async_compression::futures::bufread::GzipDecoder;
use async_tar::Archive;
@@ -9,6 +9,7 @@ use futures::{io::BufReader, FutureExt as _};
use futures::{lock::Mutex, AsyncReadExt};
use indexed_docs::IndexedDocsDatabase;
use isahc::config::{Configurable, RedirectPolicy};
use language::LanguageName;
use language::{
language_settings::AllLanguageSettings, LanguageServerBinaryStatus, LspAdapterDelegate,
};
@@ -76,7 +77,7 @@ impl HostWorktree for WasmState {
delegate: Resource<Arc<dyn LspAdapterDelegate>>,
) -> wasmtime::Result<u64> {
let delegate = self.table.get(&delegate)?;
Ok(delegate.worktree_id())
Ok(delegate.worktree_id().to_proto())
}
async fn root_path(
@@ -393,14 +394,15 @@ impl ExtensionImports for WasmState {
let location = location
.as_ref()
.map(|location| ::settings::SettingsLocation {
worktree_id: location.worktree_id as usize,
worktree_id: WorktreeId::from_proto(location.worktree_id),
path: Path::new(&location.path),
});
cx.update(|cx| match category.as_str() {
"language" => {
let key = key.map(|k| LanguageName::new(&k));
let settings =
AllLanguageSettings::get(location, cx).language(key.as_deref());
AllLanguageSettings::get(location, cx).language(key.as_ref());
Ok(serde_json::to_string(&settings::LanguageSettings {
tab_size: settings.tab_size,
})?)

View File

@@ -1000,7 +1000,7 @@ impl ExtensionsPage {
this.update_settings::<VimModeSetting>(
selection,
cx,
|setting, value| *setting = Some(value),
|setting, value| *setting = VimModeSetting(value),
);
}),
)),

View File

@@ -448,7 +448,7 @@ fn history_file_exists(abs_path: &PathBuf) -> bool {
}
#[cfg(test)]
fn history_file_exists(abs_path: &PathBuf) -> bool {
fn history_file_exists(abs_path: &Path) -> bool {
!abs_path.ends_with("nonexistent.rs")
}

View File

@@ -2012,7 +2012,7 @@ fn init_test(cx: &mut TestAppContext) -> Arc<AppState> {
}
fn test_path_position(test_str: &str) -> FileSearchQuery {
let path_position = PathWithPosition::parse_str(&test_str);
let path_position = PathWithPosition::parse_str(test_str);
FileSearchQuery {
raw_query: test_str.to_owned(),

View File

@@ -181,7 +181,7 @@ impl PickerDelegate for OpenPathDelegate {
}
let matches = fuzzy::match_strings(
&match_candidates.as_slice(),
match_candidates.as_slice(),
&suffix,
false,
100,

View File

@@ -45,6 +45,25 @@ pub trait Watcher: Send + Sync {
fn remove(&self, path: &Path) -> Result<()>;
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum PathEventKind {
Removed,
Created,
Changed,
}
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct PathEvent {
pub path: PathBuf,
pub kind: Option<PathEventKind>,
}
impl From<PathEvent> for PathBuf {
fn from(event: PathEvent) -> Self {
event.path
}
}
#[async_trait::async_trait]
pub trait Fs: Send + Sync {
async fn create_dir(&self, path: &Path) -> Result<()>;
@@ -92,7 +111,7 @@ pub trait Fs: Send + Sync {
path: &Path,
latency: Duration,
) -> (
Pin<Box<dyn Send + Stream<Item = Vec<PathBuf>>>>,
Pin<Box<dyn Send + Stream<Item = Vec<PathEvent>>>>,
Arc<dyn Watcher>,
);
@@ -469,17 +488,38 @@ impl Fs for RealFs {
path: &Path,
latency: Duration,
) -> (
Pin<Box<dyn Send + Stream<Item = Vec<PathBuf>>>>,
Pin<Box<dyn Send + Stream<Item = Vec<PathEvent>>>>,
Arc<dyn Watcher>,
) {
use fsevent::EventStream;
use fsevent::{EventStream, StreamFlags};
let (tx, rx) = smol::channel::unbounded();
let (stream, handle) = EventStream::new(&[path], latency);
std::thread::spawn(move || {
stream.run(move |events| {
smol::block_on(tx.send(events.into_iter().map(|event| event.path).collect()))
.is_ok()
smol::block_on(
tx.send(
events
.into_iter()
.map(|event| {
let kind = if event.flags.contains(StreamFlags::ITEM_REMOVED) {
Some(PathEventKind::Removed)
} else if event.flags.contains(StreamFlags::ITEM_CREATED) {
Some(PathEventKind::Created)
} else if event.flags.contains(StreamFlags::ITEM_MODIFIED) {
Some(PathEventKind::Changed)
} else {
None
};
PathEvent {
path: event.path,
kind,
}
})
.collect(),
),
)
.is_ok()
});
});
@@ -498,32 +538,46 @@ impl Fs for RealFs {
path: &Path,
latency: Duration,
) -> (
Pin<Box<dyn Send + Stream<Item = Vec<PathBuf>>>>,
Pin<Box<dyn Send + Stream<Item = Vec<PathEvent>>>>,
Arc<dyn Watcher>,
) {
use notify::EventKind;
use parking_lot::Mutex;
let (tx, rx) = smol::channel::unbounded();
let pending_paths: Arc<Mutex<Vec<PathBuf>>> = Default::default();
let pending_paths: Arc<Mutex<Vec<PathEvent>>> = Default::default();
let root_path = path.to_path_buf();
watcher::global(|g| {
let tx = tx.clone();
let pending_paths = pending_paths.clone();
g.add(move |event: &notify::Event| {
let kind = match event.kind {
EventKind::Create(_) => Some(PathEventKind::Created),
EventKind::Modify(_) => Some(PathEventKind::Changed),
EventKind::Remove(_) => Some(PathEventKind::Removed),
_ => None,
};
let mut paths = event
.paths
.iter()
.filter(|path| path.starts_with(&root_path))
.cloned()
.filter_map(|path| {
path.starts_with(&root_path).then(|| PathEvent {
path: path.clone(),
kind,
})
})
.collect::<Vec<_>>();
if !paths.is_empty() {
paths.sort();
let mut pending_paths = pending_paths.lock();
if pending_paths.is_empty() {
tx.try_send(()).ok();
}
util::extend_sorted(&mut *pending_paths, paths, usize::MAX, PathBuf::cmp);
util::extend_sorted(&mut *pending_paths, paths, usize::MAX, |a, b| {
a.path.cmp(&b.path)
});
}
})
})
@@ -561,10 +615,10 @@ impl Fs for RealFs {
path: &Path,
_latency: Duration,
) -> (
Pin<Box<dyn Send + Stream<Item = Vec<PathBuf>>>>,
Pin<Box<dyn Send + Stream<Item = Vec<PathEvent>>>>,
Arc<dyn Watcher>,
) {
use notify::Watcher;
use notify::{EventKind, Watcher};
let (tx, rx) = smol::channel::unbounded();
@@ -572,7 +626,21 @@ impl Fs for RealFs {
let tx = tx.clone();
move |event: Result<notify::Event, _>| {
if let Some(event) = event.log_err() {
tx.try_send(event.paths).ok();
let kind = match event.kind {
EventKind::Create(_) => Some(PathEventKind::Created),
EventKind::Modify(_) => Some(PathEventKind::Changed),
EventKind::Remove(_) => Some(PathEventKind::Removed),
_ => None,
};
tx.try_send(
event
.paths
.into_iter()
.map(|path| PathEvent { path, kind })
.collect::<Vec<_>>(),
)
.ok();
}
}
})
@@ -682,9 +750,9 @@ struct FakeFsState {
root: Arc<Mutex<FakeFsEntry>>,
next_inode: u64,
next_mtime: SystemTime,
event_txs: Vec<smol::channel::Sender<Vec<PathBuf>>>,
event_txs: Vec<smol::channel::Sender<Vec<PathEvent>>>,
events_paused: bool,
buffered_events: Vec<PathBuf>,
buffered_events: Vec<PathEvent>,
metadata_call_count: usize,
read_dir_call_count: usize,
}
@@ -793,11 +861,14 @@ impl FakeFsState {
fn emit_event<I, T>(&mut self, paths: I)
where
I: IntoIterator<Item = T>,
I: IntoIterator<Item = (T, Option<PathEventKind>)>,
T: Into<PathBuf>,
{
self.buffered_events
.extend(paths.into_iter().map(Into::into));
.extend(paths.into_iter().map(|(path, kind)| PathEvent {
path: path.into(),
kind,
}));
if !self.events_paused {
self.flush_events(self.buffered_events.len());
@@ -872,7 +943,7 @@ impl FakeFs {
Ok(())
})
.unwrap();
state.emit_event([path.to_path_buf()]);
state.emit_event([(path.to_path_buf(), None)]);
}
pub async fn insert_file(&self, path: impl AsRef<Path>, content: Vec<u8>) {
@@ -895,7 +966,7 @@ impl FakeFs {
}
})
.unwrap();
state.emit_event([path]);
state.emit_event([(path, None)]);
}
fn write_file_internal(&self, path: impl AsRef<Path>, content: Vec<u8>) -> Result<()> {
@@ -910,18 +981,24 @@ impl FakeFs {
mtime,
content,
}));
state.write_path(path, move |entry| {
match entry {
btree_map::Entry::Vacant(e) => {
e.insert(file);
}
btree_map::Entry::Occupied(mut e) => {
*e.get_mut() = file;
let mut kind = None;
state.write_path(path, {
let kind = &mut kind;
move |entry| {
match entry {
btree_map::Entry::Vacant(e) => {
*kind = Some(PathEventKind::Created);
e.insert(file);
}
btree_map::Entry::Occupied(mut e) => {
*kind = Some(PathEventKind::Changed);
*e.get_mut() = file;
}
}
Ok(())
}
Ok(())
})?;
state.emit_event([path]);
state.emit_event([(path, kind)]);
Ok(())
}
@@ -1030,7 +1107,7 @@ impl FakeFs {
f(&mut repo_state);
if emit_git_event {
state.emit_event([dot_git]);
state.emit_event([(dot_git, None)]);
}
} else {
panic!("not a directory");
@@ -1081,7 +1158,7 @@ impl FakeFs {
self.state.lock().emit_event(
statuses
.iter()
.map(|(path, _)| dot_git.parent().unwrap().join(path)),
.map(|(path, _)| (dot_git.parent().unwrap().join(path), None)),
);
}
@@ -1251,7 +1328,7 @@ impl Fs for FakeFs {
state.next_inode += 1;
state.write_path(&cur_path, |entry| {
entry.or_insert_with(|| {
created_dirs.push(cur_path.clone());
created_dirs.push((cur_path.clone(), Some(PathEventKind::Created)));
Arc::new(Mutex::new(FakeFsEntry::Dir {
inode,
mtime,
@@ -1263,7 +1340,7 @@ impl Fs for FakeFs {
})?
}
self.state.lock().emit_event(&created_dirs);
self.state.lock().emit_event(created_dirs);
Ok(())
}
@@ -1279,10 +1356,12 @@ impl Fs for FakeFs {
mtime,
content: Vec::new(),
}));
let mut kind = Some(PathEventKind::Created);
state.write_path(path, |entry| {
match entry {
btree_map::Entry::Occupied(mut e) => {
if options.overwrite {
kind = Some(PathEventKind::Changed);
*e.get_mut() = file;
} else if !options.ignore_if_exists {
return Err(anyhow!("path already exists: {}", path.display()));
@@ -1294,7 +1373,7 @@ impl Fs for FakeFs {
}
Ok(())
})?;
state.emit_event([path]);
state.emit_event([(path, kind)]);
Ok(())
}
@@ -1313,7 +1392,8 @@ impl Fs for FakeFs {
}
})
.unwrap();
state.emit_event([path]);
state.emit_event([(path, None)]);
Ok(())
}
@@ -1388,7 +1468,10 @@ impl Fs for FakeFs {
})
.unwrap();
state.emit_event(&[old_path, new_path]);
state.emit_event([
(old_path, Some(PathEventKind::Removed)),
(new_path, Some(PathEventKind::Created)),
]);
Ok(())
}
@@ -1403,9 +1486,11 @@ impl Fs for FakeFs {
state.next_mtime += Duration::from_nanos(1);
let source_entry = state.read_path(&source)?;
let content = source_entry.lock().file_content(&source)?.clone();
let mut kind = Some(PathEventKind::Created);
let entry = state.write_path(&target, |e| match e {
btree_map::Entry::Occupied(e) => {
if options.overwrite {
kind = Some(PathEventKind::Changed);
Ok(Some(e.get().clone()))
} else if !options.ignore_if_exists {
return Err(anyhow!("{target:?} already exists"));
@@ -1425,7 +1510,7 @@ impl Fs for FakeFs {
if let Some(entry) = entry {
entry.lock().set_file_content(&target, content)?;
}
state.emit_event(&[target]);
state.emit_event([(target, kind)]);
Ok(())
}
@@ -1462,7 +1547,7 @@ impl Fs for FakeFs {
e.remove();
}
}
state.emit_event(&[path]);
state.emit_event([(path, Some(PathEventKind::Removed))]);
Ok(())
}
@@ -1491,7 +1576,7 @@ impl Fs for FakeFs {
e.remove();
}
}
state.emit_event(&[path]);
state.emit_event([(path, Some(PathEventKind::Removed))]);
Ok(())
}
@@ -1632,7 +1717,7 @@ impl Fs for FakeFs {
path: &Path,
_: Duration,
) -> (
Pin<Box<dyn Send + Stream<Item = Vec<PathBuf>>>>,
Pin<Box<dyn Send + Stream<Item = Vec<PathEvent>>>>,
Arc<dyn Watcher>,
) {
self.simulate_random_delay().await;
@@ -1642,7 +1727,9 @@ impl Fs for FakeFs {
let executor = self.executor.clone();
(
Box::pin(futures::StreamExt::filter(rx, move |events| {
let result = events.iter().any(|evt_path| evt_path.starts_with(&path));
let result = events
.iter()
.any(|evt_path| evt_path.path.starts_with(&path));
let executor = executor.clone();
async move {
executor.simulate_random_delay().await;

View File

@@ -180,18 +180,10 @@ pub(crate) enum LineIndicatorFormat {
Long,
}
/// Whether or not to automatically check for updates.
///
/// Values: short, long
/// Default: short
#[derive(Clone, Copy, Default, JsonSchema, Deserialize, Serialize)]
#[serde(transparent)]
pub(crate) struct LineIndicatorFormatContent(LineIndicatorFormat);
impl Settings for LineIndicatorFormat {
const KEY: Option<&'static str> = Some("line_indicator_format");
type FileContent = Option<LineIndicatorFormatContent>;
type FileContent = Self;
fn load(
sources: SettingsSources<Self::FileContent>,
@@ -199,9 +191,9 @@ impl Settings for LineIndicatorFormat {
) -> anyhow::Result<Self> {
let format = [sources.release_channel, sources.user]
.into_iter()
.find_map(|value| value.copied().flatten())
.unwrap_or(sources.default.ok_or_else(Self::missing_default)?);
.find_map(|value| value.copied())
.unwrap_or(*sources.default);
Ok(format.0)
Ok(format)
}
}

View File

@@ -304,7 +304,12 @@ pub enum Model {
#[serde(rename = "gemini-1.5-flash")]
Gemini15Flash,
#[serde(rename = "custom")]
Custom { name: String, max_tokens: usize },
Custom {
name: String,
/// The name displayed in the UI, such as in the assistant panel model dropdown menu.
display_name: Option<String>,
max_tokens: usize,
},
}
impl Model {
@@ -320,7 +325,9 @@ impl Model {
match self {
Model::Gemini15Pro => "Gemini 1.5 Pro",
Model::Gemini15Flash => "Gemini 1.5 Flash",
Model::Custom { name, .. } => name,
Self::Custom {
name, display_name, ..
} => display_name.as_ref().unwrap_or(name),
}
}

View File

@@ -13,6 +13,7 @@ workspace = true
[features]
default = []
test-support = [
"backtrace",
"collections/test-support",
"rand",
"util/test-support",
@@ -28,7 +29,7 @@ doctest = false
[dependencies]
anyhow.workspace = true
async-task = "4.7"
backtrace = { version = "0.3" }
backtrace = { version = "0.3", optional = true }
blade-graphics = { workspace = true, optional = true }
blade-macros = { workspace = true, optional = true }
blade-util = { workspace = true, optional = true }

View File

@@ -6,7 +6,7 @@ use std::{
path::{Path, PathBuf},
rc::{Rc, Weak},
sync::{atomic::Ordering::SeqCst, Arc},
time::{Duration, Instant},
time::Duration,
};
use anyhow::{anyhow, Result};
@@ -142,12 +142,6 @@ impl App {
self
}
/// Sets a start time for tracking time to first window draw.
pub fn measure_time_to_first_window_draw(self, start: Instant) -> Self {
self.0.borrow_mut().time_to_first_window_draw = Some(TimeToFirstWindowDraw::Pending(start));
self
}
/// Start the application. The provided callback will be called once the
/// app is fully launched.
pub fn run<F>(self, on_finish_launching: F)
@@ -253,7 +247,6 @@ pub struct AppContext {
pub(crate) layout_id_buffer: Vec<LayoutId>, // We recycle this memory across layout requests.
pub(crate) propagate_event: bool,
pub(crate) prompt_builder: Option<PromptBuilder>,
pub(crate) time_to_first_window_draw: Option<TimeToFirstWindowDraw>,
}
impl AppContext {
@@ -307,7 +300,6 @@ impl AppContext {
layout_id_buffer: Default::default(),
propagate_event: true,
prompt_builder: Some(PromptBuilder::Default),
time_to_first_window_draw: None,
}),
});
@@ -362,7 +354,6 @@ impl AppContext {
let result = update(self);
if !self.flushing_effects && self.pending_updates == 1 {
self.flushing_effects = true;
println!("flushing effects");
self.flush_effects();
self.flushing_effects = false;
}
@@ -666,6 +657,11 @@ impl AppContext {
self.platform.reveal_path(path)
}
/// Opens the specified path with the system's default application.
pub fn open_with_system(&self, path: &Path) {
self.platform.open_with_system(path)
}
/// Returns whether the user has configured scrollbars to auto-hide at the platform level.
pub fn should_auto_hide_scrollbars(&self) -> bool {
self.platform.should_auto_hide_scrollbars()
@@ -707,7 +703,6 @@ impl AppContext {
};
self.pending_effects.push_back(effect);
// If we're not in an update call; schedule one (somehow)
}
/// Called at the end of [`AppContext::update`] to complete any side effects
@@ -767,7 +762,6 @@ impl AppContext {
/// reference count has become zero. We invoke any release observers before dropping
/// each entity.
fn release_dropped_entities(&mut self) {
// println!("releasing dropped entities");
loop {
let dropped = self.entities.take_dropped();
if dropped.is_empty() {
@@ -1313,14 +1307,6 @@ impl AppContext {
(task, is_first)
}
/// Returns the time to first window draw, if available.
pub fn time_to_first_window_draw(&self) -> Option<Duration> {
match self.time_to_first_window_draw {
Some(TimeToFirstWindowDraw::Done(duration)) => Some(duration),
_ => None,
}
}
}
impl Context for AppContext {
@@ -1401,7 +1387,6 @@ impl Context for AppContext {
if window.removed {
cx.window_handles.remove(&handle.id);
cx.windows.remove(handle.id);
cx.flush_effects();
} else {
cx.windows
.get_mut(handle.id)
@@ -1485,15 +1470,6 @@ impl<G: Global> DerefMut for GlobalLease<G> {
}
}
/// Represents the initialization duration of the application.
#[derive(Clone, Copy)]
pub enum TimeToFirstWindowDraw {
/// The application is still initializing, and contains the start time.
Pending(Instant),
/// The application has finished initializing, and contains the total duration.
Done(Duration),
}
/// Contains state associated with an active drag operation, started by dragging an element
/// within the window or by dragging into the app from the underlying platform.
pub struct AnyDrag {
@@ -1528,3 +1504,9 @@ pub struct KeystrokeEvent {
/// The action that was resolved for the keystroke, if any
pub action: Option<Box<dyn Action>>,
}
impl Drop for AppContext {
fn drop(&mut self) {
println!("Dropping the App Context");
}
}

View File

@@ -17,7 +17,7 @@ use std::{
thread::panicking,
};
// #[cfg(any(test, feature = "test-support"))]
#[cfg(any(test, feature = "test-support"))]
use collections::HashMap;
slotmap::new_key_type! {
@@ -57,7 +57,7 @@ pub(crate) struct EntityMap {
struct EntityRefCounts {
counts: SlotMap<EntityId, AtomicUsize>,
dropped_entity_ids: Vec<EntityId>,
// #[cfg(any(test, feature = "test-support"))]
#[cfg(any(test, feature = "test-support"))]
leak_detector: LeakDetector,
}
@@ -68,7 +68,7 @@ impl EntityMap {
ref_counts: Arc::new(RwLock::new(EntityRefCounts {
counts: SlotMap::with_key(),
dropped_entity_ids: Vec::new(),
// #[cfg(any(test, feature = "test-support"))]
#[cfg(any(test, feature = "test-support"))]
leak_detector: LeakDetector {
next_handle_id: 0,
entity_handles: HashMap::default(),
@@ -193,7 +193,7 @@ pub struct AnyModel {
pub(crate) entity_id: EntityId,
pub(crate) entity_type: TypeId,
entity_map: Weak<RwLock<EntityRefCounts>>,
// #[cfg(any(test, feature = "test-support"))]
#[cfg(any(test, feature = "test-support"))]
handle_id: HandleId,
}
@@ -203,7 +203,7 @@ impl AnyModel {
entity_id: id,
entity_type,
entity_map: entity_map.clone(),
// #[cfg(any(test, feature = "test-support"))]
#[cfg(any(test, feature = "test-support"))]
handle_id: entity_map
.upgrade()
.unwrap()
@@ -262,7 +262,7 @@ impl Clone for AnyModel {
entity_id: self.entity_id,
entity_type: self.entity_type,
entity_map: self.entity_map.clone(),
// #[cfg(any(test, feature = "test-support"))]
#[cfg(any(test, feature = "test-support"))]
handle_id: self
.entity_map
.upgrade()
@@ -291,9 +291,8 @@ impl Drop for AnyModel {
}
}
// #[cfg(any(test, feature = "test-support"))]
#[cfg(any(test, feature = "test-support"))]
if let Some(entity_map) = self.entity_map.upgrade() {
// println!("dropped model");
entity_map
.write()
.leak_detector
@@ -505,7 +504,7 @@ impl AnyWeakModel {
entity_id: self.entity_id,
entity_type: self.entity_type,
entity_map: self.entity_ref_counts.clone(),
// #[cfg(any(test, feature = "test-support"))]
#[cfg(any(test, feature = "test-support"))]
handle_id: self
.entity_ref_counts
.upgrade()
@@ -517,7 +516,7 @@ impl AnyWeakModel {
}
/// Assert that model referenced by this weak handle has been released.
// #[cfg(any(test, feature = "test-support"))]
#[cfg(any(test, feature = "test-support"))]
pub fn assert_released(&self) {
self.entity_ref_counts
.upgrade()
@@ -642,23 +641,23 @@ impl<T> PartialEq<Model<T>> for WeakModel<T> {
}
}
// #[cfg(any(test, feature = "test-support"))]
#[cfg(any(test, feature = "test-support"))]
static LEAK_BACKTRACE: std::sync::LazyLock<bool> =
std::sync::LazyLock::new(|| std::env::var("LEAK_BACKTRACE").map_or(false, |b| !b.is_empty()));
// #[cfg(any(test, feature = "test-support"))]
#[cfg(any(test, feature = "test-support"))]
#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq)]
pub(crate) struct HandleId {
id: u64, // id of the handle itself, not the pointed at object
}
// #[cfg(any(test, feature = "test-support"))]
#[cfg(any(test, feature = "test-support"))]
pub(crate) struct LeakDetector {
next_handle_id: u64,
entity_handles: HashMap<EntityId, HashMap<HandleId, Option<backtrace::Backtrace>>>,
}
// #[cfg(any(test, feature = "test-support"))]
#[cfg(any(test, feature = "test-support"))]
impl LeakDetector {
#[track_caller]
pub fn handle_created(&mut self, entity_id: EntityId) -> HandleId {
@@ -667,7 +666,7 @@ impl LeakDetector {
let handles = self.entity_handles.entry(entity_id).or_default();
handles.insert(
handle_id,
LEAK_BACKTRACE.then(|| backtrace::Backtrace::new_unresolved()),
LEAK_BACKTRACE.then(backtrace::Backtrace::new_unresolved),
);
handle_id
}
@@ -680,7 +679,7 @@ impl LeakDetector {
pub fn assert_released(&mut self, entity_id: EntityId) {
let handles = self.entity_handles.entry(entity_id).or_default();
if !handles.is_empty() {
for (_, backtrace) in handles {
for backtrace in handles.values_mut() {
if let Some(mut backtrace) = backtrace.take() {
backtrace.resolve();
eprintln!("Leaked handle: {:#?}", backtrace);

View File

@@ -2,8 +2,8 @@ use smallvec::SmallVec;
use taffy::style::{Display, Position};
use crate::{
point, AnyElement, Bounds, Element, GlobalElementId, IntoElement, LayoutId, ParentElement,
Pixels, Point, Size, Style, WindowContext,
point, AnyElement, Bounds, Edges, Element, GlobalElementId, IntoElement, LayoutId,
ParentElement, Pixels, Point, Size, Style, WindowContext,
};
/// The state that the anchored element element uses to track its children.
@@ -60,6 +60,12 @@ impl Anchored {
self.fit_mode = AnchoredFitMode::SnapToWindow;
self
}
/// Snap to window edge and leave some margins.
pub fn snap_to_window_with_margin(mut self, edges: impl Into<Edges<Pixels>>) -> Self {
self.fit_mode = AnchoredFitMode::SnapToWindowWithMargin(edges.into());
self
}
}
impl ParentElement for Anchored {
@@ -153,22 +159,27 @@ impl Element for Anchored {
}
}
let edges = match self.fit_mode {
AnchoredFitMode::SnapToWindowWithMargin(edges) => edges,
_ => Edges::default(),
};
// Snap the horizontal edges of the anchored element to the horizontal edges of the window if
// its horizontal bounds overflow, aligning to the left if it is wider than the limits.
if desired.right() > limits.right() {
desired.origin.x -= desired.right() - limits.right();
desired.origin.x -= desired.right() - limits.right() + edges.right;
}
if desired.left() < limits.left() {
desired.origin.x = limits.origin.x;
desired.origin.x = limits.origin.x + edges.left;
}
// Snap the vertical edges of the anchored element to the vertical edges of the window if
// its vertical bounds overflow, aligning to the top if it is taller than the limits.
if desired.bottom() > limits.bottom() {
desired.origin.y -= desired.bottom() - limits.bottom();
desired.origin.y -= desired.bottom() - limits.bottom() + edges.bottom;
}
if desired.top() < limits.top() {
desired.origin.y = limits.origin.y;
desired.origin.y = limits.origin.y + edges.top;
}
let offset = desired.origin - bounds.origin;
@@ -211,18 +222,20 @@ enum Axis {
/// Which algorithm to use when fitting the anchored element to be inside the window.
#[derive(Copy, Clone, PartialEq)]
pub enum AnchoredFitMode {
/// Snap the anchored element to the window edge
/// Snap the anchored element to the window edge.
SnapToWindow,
/// Switch which corner anchor this anchored element is attached to
/// Snap to window edge and leave some margins.
SnapToWindowWithMargin(Edges<Pixels>),
/// Switch which corner anchor this anchored element is attached to.
SwitchAnchor,
}
/// Which algorithm to use when positioning the anchored element.
#[derive(Copy, Clone, PartialEq)]
pub enum AnchoredPositionMode {
/// Position the anchored element relative to the window
/// Position the anchored element relative to the window.
Window,
/// Position the anchored element relative to its parent
/// Position the anchored element relative to its parent.
Local,
}

View File

@@ -5,6 +5,7 @@
use core::fmt::Debug;
use derive_more::{Add, AddAssign, Div, DivAssign, Mul, Neg, Sub, SubAssign};
use refineable::Refineable;
use schemars::JsonSchema;
use serde_derive::{Deserialize, Serialize};
use std::{
cmp::{self, PartialOrd},
@@ -1836,11 +1837,18 @@ impl Edges<Pixels> {
impl From<f32> for Edges<Pixels> {
fn from(val: f32) -> Self {
let val: Pixels = val.into();
val.into()
}
}
impl From<Pixels> for Edges<Pixels> {
fn from(val: Pixels) -> Self {
Edges {
top: val.into(),
right: val.into(),
bottom: val.into(),
left: val.into(),
top: val,
right: val,
bottom: val,
left: val,
}
}
}
@@ -2194,6 +2202,7 @@ impl From<Percentage> for Radians {
PartialEq,
Serialize,
Deserialize,
JsonSchema,
)]
#[repr(transparent)]
pub struct Pixels(pub f32);

View File

@@ -16,7 +16,6 @@ mod blade;
#[cfg(any(test, feature = "test-support"))]
mod test;
mod fps;
#[cfg(target_os = "windows")]
mod windows;
@@ -52,7 +51,6 @@ use strum::EnumIter;
use uuid::Uuid;
pub use app_menu::*;
pub use fps::*;
pub use keystroke::*;
#[cfg(target_os = "linux")]
@@ -151,6 +149,7 @@ pub(crate) trait Platform: 'static {
) -> oneshot::Receiver<Result<Option<Vec<PathBuf>>>>;
fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Result<Option<PathBuf>>>;
fn reveal_path(&self, path: &Path);
fn open_with_system(&self, path: &Path);
fn on_quit(&self, callback: Box<dyn FnMut()>);
fn on_reopen(&self, callback: Box<dyn FnMut()>);
@@ -356,7 +355,7 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>);
fn on_close(&self, callback: Box<dyn FnOnce()>);
fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
fn draw(&self, scene: &Scene, on_complete: Option<oneshot::Sender<()>>);
fn draw(&self, scene: &Scene);
fn completed_frame(&self) {}
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
@@ -381,7 +380,6 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
}
fn set_client_inset(&self, _inset: Pixels) {}
fn gpu_specs(&self) -> Option<GPUSpecs>;
fn fps(&self) -> Option<f32>;
fn update_ime_position(&self, _bounds: Bounds<Pixels>);

View File

@@ -9,7 +9,6 @@ use crate::{
};
use bytemuck::{Pod, Zeroable};
use collections::HashMap;
use futures::channel::oneshot;
#[cfg(target_os = "macos")]
use media::core_video::CVMetalTextureCache;
#[cfg(target_os = "macos")]
@@ -337,6 +336,17 @@ impl BladePipelines {
}),
}
}
fn destroy(&mut self, gpu: &gpu::Context) {
gpu.destroy_render_pipeline(&mut self.quads);
gpu.destroy_render_pipeline(&mut self.shadows);
gpu.destroy_render_pipeline(&mut self.path_rasterization);
gpu.destroy_render_pipeline(&mut self.paths);
gpu.destroy_render_pipeline(&mut self.underlines);
gpu.destroy_render_pipeline(&mut self.mono_sprites);
gpu.destroy_render_pipeline(&mut self.poly_sprites);
gpu.destroy_render_pipeline(&mut self.surfaces);
}
}
pub struct BladeSurfaceConfig {
@@ -439,6 +449,7 @@ impl BladeRenderer {
self.wait_for_gpu();
self.surface_config.transparent = transparent;
let surface_info = self.gpu.resize(self.surface_config);
self.pipelines.destroy(&self.gpu);
self.pipelines = BladePipelines::new(&self.gpu, surface_info);
self.alpha_mode = surface_info.alpha;
}
@@ -539,16 +550,13 @@ impl BladeRenderer {
pub fn destroy(&mut self) {
self.wait_for_gpu();
self.atlas.destroy();
self.gpu.destroy_sampler(self.atlas_sampler);
self.instance_belt.destroy(&self.gpu);
self.gpu.destroy_command_encoder(&mut self.command_encoder);
self.pipelines.destroy(&self.gpu);
}
pub fn draw(
&mut self,
scene: &Scene,
// Required to compile on macOS, but not currently supported.
_on_complete: Option<oneshot::Sender<()>>,
) {
pub fn draw(&mut self, scene: &Scene) {
self.command_encoder.start();
self.atlas.before_frame(&mut self.command_encoder);
self.rasterize_paths(scene.paths());
@@ -777,10 +785,4 @@ impl BladeRenderer {
self.wait_for_gpu();
self.last_sync_point = Some(sync_point);
}
/// Required to compile on macOS, but not currently supported.
#[cfg_attr(any(target_os = "linux", target_os = "windows"), allow(dead_code))]
pub fn fps(&self) -> f32 {
0.0
}
}

View File

@@ -1,94 +0,0 @@
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
use std::sync::Arc;
const NANOS_PER_SEC: u64 = 1_000_000_000;
const WINDOW_SIZE: usize = 128;
/// Represents a rolling FPS (Frames Per Second) counter.
///
/// This struct provides a lock-free mechanism to measure and calculate FPS
/// continuously, updating with every frame. It uses atomic operations to
/// ensure thread-safety without the need for locks.
pub struct FpsCounter {
frame_times: [AtomicU64; WINDOW_SIZE],
head: AtomicUsize,
tail: AtomicUsize,
}
impl FpsCounter {
/// Creates a new `Fps` counter.
///
/// Returns an `Arc<Fps>` for safe sharing across threads.
pub fn new() -> Arc<Self> {
Arc::new(Self {
frame_times: std::array::from_fn(|_| AtomicU64::new(0)),
head: AtomicUsize::new(0),
tail: AtomicUsize::new(0),
})
}
/// Increments the FPS counter with a new frame timestamp.
///
/// This method updates the internal state to maintain a rolling window
/// of frame data for the last second. It uses atomic operations to
/// ensure thread-safety.
///
/// # Arguments
///
/// * `timestamp_ns` - The timestamp of the new frame in nanoseconds.
pub fn increment(&self, timestamp_ns: u64) {
let mut head = self.head.load(Ordering::Relaxed);
let mut tail = self.tail.load(Ordering::Relaxed);
// Add new timestamp
self.frame_times[head].store(timestamp_ns, Ordering::Relaxed);
// Increment head and wrap around to 0 if it reaches WINDOW_SIZE
head = (head + 1) % WINDOW_SIZE;
self.head.store(head, Ordering::Relaxed);
// Remove old timestamps (older than 1 second)
while tail != head {
let oldest = self.frame_times[tail].load(Ordering::Relaxed);
if timestamp_ns.wrapping_sub(oldest) <= NANOS_PER_SEC {
break;
}
// Increment tail and wrap around to 0 if it reaches WINDOW_SIZE
tail = (tail + 1) % WINDOW_SIZE;
self.tail.store(tail, Ordering::Relaxed);
}
}
/// Calculates and returns the current FPS.
///
/// This method computes the FPS based on the frames recorded in the last second.
/// It uses atomic loads to ensure thread-safety.
///
/// # Returns
///
/// The calculated FPS as a `f32`, or 0.0 if no frames have been recorded.
pub fn fps(&self) -> f32 {
let head = self.head.load(Ordering::Relaxed);
let tail = self.tail.load(Ordering::Relaxed);
if head == tail {
return 0.0;
}
let newest =
self.frame_times[head.wrapping_sub(1) & (WINDOW_SIZE - 1)].load(Ordering::Relaxed);
let oldest = self.frame_times[tail].load(Ordering::Relaxed);
let time_diff = newest.wrapping_sub(oldest) as f32;
if time_diff == 0.0 {
return 0.0;
}
let frame_count = if head > tail {
head - tail
} else {
WINDOW_SIZE - tail + head
};
(frame_count as f32 - 1.0) * NANOS_PER_SEC as f32 / time_diff
}
}

View File

@@ -351,6 +351,19 @@ impl<P: LinuxClient + 'static> Platform for P {
self.reveal_path(path.to_owned());
}
fn open_with_system(&self, path: &Path) {
let executor = self.background_executor().clone();
let path = path.to_owned();
executor
.spawn(async move {
let _ = std::process::Command::new("xdg-open")
.arg(path)
.spawn()
.expect("Failed to open file with xdg-open");
})
.detach();
}
fn on_quit(&self, callback: Box<dyn FnMut()>) {
self.with_common(|common| {
common.callbacks.quit = Some(callback);

View File

@@ -476,7 +476,8 @@ impl WaylandClient {
.as_ref()
.map(|primary_selection_manager| primary_selection_manager.get_device(&seat, &qh, ()));
let mut cursor = Cursor::new(&conn, &globals, 24);
// FIXME: Determine the scaling factor dynamically by the compositor
let mut cursor = Cursor::new(&conn, &globals, 24, 2);
handle
.insert_source(XDPEventSource::new(&common.background_executor), {

View File

@@ -11,6 +11,7 @@ pub(crate) struct Cursor {
theme_name: Option<String>,
surface: WlSurface,
size: u32,
scale: u32,
shm: WlShm,
connection: Connection,
}
@@ -23,7 +24,7 @@ impl Drop for Cursor {
}
impl Cursor {
pub fn new(connection: &Connection, globals: &Globals, size: u32) -> Self {
pub fn new(connection: &Connection, globals: &Globals, size: u32, scale: u32) -> Self {
Self {
theme: CursorTheme::load(&connection, globals.shm.clone(), size).log_err(),
theme_name: None,
@@ -31,6 +32,7 @@ impl Cursor {
shm: globals.shm.clone(),
connection: connection.clone(),
size,
scale,
}
}
@@ -38,14 +40,18 @@ impl Cursor {
if let Some(size) = size {
self.size = size;
}
if let Some(theme) =
CursorTheme::load_from_name(&self.connection, self.shm.clone(), theme_name, self.size)
.log_err()
if let Some(theme) = CursorTheme::load_from_name(
&self.connection,
self.shm.clone(),
theme_name,
self.size * self.scale,
)
.log_err()
{
self.theme = Some(theme);
self.theme_name = Some(theme_name.to_string());
} else if let Some(theme) =
CursorTheme::load(&self.connection, self.shm.clone(), self.size).log_err()
CursorTheme::load(&self.connection, self.shm.clone(), self.size * self.scale).log_err()
{
self.theme = Some(theme);
self.theme_name = None;
@@ -91,9 +97,22 @@ impl Cursor {
let (width, height) = buffer.dimensions();
let (hot_x, hot_y) = buffer.hotspot();
wl_pointer.set_cursor(serial_id, Some(&self.surface), hot_x as i32, hot_y as i32);
let scaled_width = width / self.scale;
let scaled_height = height / self.scale;
let scaled_hot_x = hot_x / self.scale;
let scaled_hot_y = hot_y / self.scale;
self.surface.set_buffer_scale(self.scale as i32);
wl_pointer.set_cursor(
serial_id,
Some(&self.surface),
scaled_hot_x as i32,
scaled_hot_y as i32,
);
self.surface.attach(Some(&buffer), 0, 0);
self.surface.damage(0, 0, width as i32, height as i32);
self.surface
.damage(0, 0, scaled_width as i32, scaled_height as i32);
self.surface.commit();
}
} else {

View File

@@ -6,7 +6,7 @@ use std::sync::Arc;
use blade_graphics as gpu;
use collections::HashMap;
use futures::channel::oneshot;
use futures::channel::oneshot::Receiver;
use raw_window_handle as rwh;
use wayland_backend::client::ObjectId;
@@ -831,7 +831,7 @@ impl PlatformWindow for WaylandWindow {
_msg: &str,
_detail: Option<&str>,
_answers: &[&str],
) -> Option<oneshot::Receiver<usize>> {
) -> Option<Receiver<usize>> {
None
}
@@ -938,9 +938,9 @@ impl PlatformWindow for WaylandWindow {
self.0.callbacks.borrow_mut().appearance_changed = Some(callback);
}
fn draw(&self, scene: &Scene, on_complete: Option<oneshot::Sender<()>>) {
fn draw(&self, scene: &Scene) {
let mut state = self.borrow_mut();
state.renderer.draw(scene, on_complete);
state.renderer.draw(scene);
}
fn completed_frame(&self) {
@@ -1018,10 +1018,6 @@ impl PlatformWindow for WaylandWindow {
fn gpu_specs(&self) -> Option<GPUSpecs> {
self.borrow().renderer.gpu_specs().into()
}
fn fps(&self) -> Option<f32> {
None
}
}
fn update_window(mut state: RefMut<WaylandWindowState>) {

View File

@@ -1,3 +1,4 @@
use core::str;
use std::cell::RefCell;
use std::collections::HashSet;
use std::ops::Deref;
@@ -9,6 +10,8 @@ use calloop::generic::{FdWrapper, Generic};
use calloop::{EventLoop, LoopHandle, RegistrationToken};
use collections::HashMap;
use http_client::Url;
use smallvec::SmallVec;
use util::ResultExt;
use x11rb::connection::{Connection, RequestConnection};
@@ -17,9 +20,13 @@ use x11rb::errors::ConnectionError;
use x11rb::protocol::randr::ConnectionExt as _;
use x11rb::protocol::xinput::ConnectionExt;
use x11rb::protocol::xkb::ConnectionExt as _;
use x11rb::protocol::xproto::{ChangeWindowAttributesAux, ConnectionExt as _, KeyPressEvent};
use x11rb::protocol::xproto::{
AtomEnum, ChangeWindowAttributesAux, ClientMessageData, ClientMessageEvent, ConnectionExt as _,
EventMask, KeyPressEvent,
};
use x11rb::protocol::{randr, render, xinput, xkb, xproto, Event};
use x11rb::resource_manager::Database;
use x11rb::wrapper::ConnectionExt as _;
use x11rb::xcb_ffi::XCBConnection;
use xim::{x11rb::X11rbClient, Client};
use xim::{AttributeName, InputStyle};
@@ -30,8 +37,8 @@ use crate::platform::linux::LinuxClient;
use crate::platform::{LinuxCommon, PlatformWindow};
use crate::{
modifiers_from_xinput_info, point, px, AnyWindowHandle, Bounds, ClipboardItem, CursorStyle,
DisplayId, Keystroke, Modifiers, ModifiersChangedEvent, Pixels, Platform, PlatformDisplay,
PlatformInput, Point, ScrollDelta, Size, TouchPhase, WindowParams, X11Window,
DisplayId, FileDropEvent, Keystroke, Modifiers, ModifiersChangedEvent, Pixels, Platform,
PlatformDisplay, PlatformInput, Point, ScrollDelta, Size, TouchPhase, WindowParams, X11Window,
};
use super::{button_of_key, modifiers_from_state, pressed_button_from_mask};
@@ -101,6 +108,14 @@ struct XKBStateNotiy {
locked_layout: LayoutIndex,
}
#[derive(Debug, Default)]
pub struct Xdnd {
other_window: xproto::Window,
drag_type: u32,
retrieved: bool,
position: Point<Pixels>,
}
pub struct X11ClientState {
pub(crate) loop_handle: LoopHandle<'static, X11Client>,
pub(crate) event_loop: Option<calloop::EventLoop<'static, X11Client>>,
@@ -142,6 +157,7 @@ pub struct X11ClientState {
pub(crate) common: LinuxCommon,
pub(crate) clipboard: x11_clipboard::Clipboard,
pub(crate) clipboard_item: Option<ClipboardItem>,
pub(crate) xdnd_state: Xdnd,
}
#[derive(Clone)]
@@ -423,6 +439,7 @@ impl X11Client {
clipboard,
clipboard_item: None,
xdnd_state: Xdnd::default(),
})))
}
@@ -611,7 +628,7 @@ impl X11Client {
match event {
Event::ClientMessage(event) => {
let window = self.get_window(event.window)?;
let [atom, _arg1, arg2, arg3, _arg4] = event.data.as_data32();
let [atom, arg1, arg2, arg3, arg4] = event.data.as_data32();
let mut state = self.0.borrow_mut();
if atom == state.atoms.WM_DELETE_WINDOW {
@@ -627,6 +644,106 @@ impl X11Client {
hi: arg3 as i32,
})
}
if event.type_ == state.atoms.XdndEnter {
state.xdnd_state.other_window = atom;
if (arg1 & 0x1) == 0x1 {
state.xdnd_state.drag_type = xdnd_get_supported_atom(
&state.xcb_connection,
&state.atoms,
state.xdnd_state.other_window,
);
} else {
if let Some(atom) = [arg2, arg3, arg4]
.into_iter()
.find(|atom| xdnd_is_atom_supported(*atom, &state.atoms))
{
state.xdnd_state.drag_type = atom;
}
}
} else if event.type_ == state.atoms.XdndLeave {
window.handle_input(PlatformInput::FileDrop(FileDropEvent::Pending {
position: state.xdnd_state.position,
}));
window.handle_input(PlatformInput::FileDrop(FileDropEvent::Exited {}));
state.xdnd_state = Xdnd::default();
} else if event.type_ == state.atoms.XdndPosition {
if let Ok(pos) = state
.xcb_connection
.query_pointer(event.window)
.unwrap()
.reply()
{
state.xdnd_state.position =
Point::new(Pixels(pos.win_x as f32), Pixels(pos.win_y as f32));
}
if !state.xdnd_state.retrieved {
state
.xcb_connection
.convert_selection(
event.window,
state.atoms.XdndSelection,
state.xdnd_state.drag_type,
state.atoms.XDND_DATA,
arg3,
)
.unwrap();
}
xdnd_send_status(
&state.xcb_connection,
&state.atoms,
event.window,
state.xdnd_state.other_window,
arg4,
);
window.handle_input(PlatformInput::FileDrop(FileDropEvent::Pending {
position: state.xdnd_state.position,
}));
} else if event.type_ == state.atoms.XdndDrop {
xdnd_send_finished(
&state.xcb_connection,
&state.atoms,
event.window,
state.xdnd_state.other_window,
);
window.handle_input(PlatformInput::FileDrop(FileDropEvent::Submit {
position: state.xdnd_state.position,
}));
state.xdnd_state = Xdnd::default();
}
}
Event::SelectionNotify(event) => {
let window = self.get_window(event.requestor)?;
let mut state = self.0.borrow_mut();
let property = state.xcb_connection.get_property(
false,
event.requestor,
state.atoms.XDND_DATA,
AtomEnum::ANY,
0,
1024,
);
if property.as_ref().log_err().is_none() {
return Some(());
}
if let Ok(reply) = property.unwrap().reply() {
match str::from_utf8(&reply.value) {
Ok(file_list) => {
let paths: SmallVec<[_; 2]> = file_list
.lines()
.filter_map(|path| Url::parse(path).log_err())
.filter_map(|url| url.to_file_path().log_err())
.collect();
let input = PlatformInput::FileDrop(FileDropEvent::Entered {
position: state.xdnd_state.position,
paths: crate::ExternalPaths(paths),
});
window.handle_input(input);
state.xdnd_state.retrieved = true;
}
Err(_) => {}
}
}
}
Event::ConfigureNotify(event) => {
let bounds = Bounds {
@@ -1179,6 +1296,16 @@ impl LinuxClient for X11Client {
state.scale_factor,
state.common.appearance,
)?;
state
.xcb_connection
.change_property32(
xproto::PropMode::REPLACE,
x_window,
state.atoms.XdndAware,
state.atoms.XA_ATOM,
&[5],
)
.unwrap();
let screen_resources = state
.xcb_connection
@@ -1540,3 +1667,78 @@ fn check_gtk_frame_extents_supported(
supported_atoms.contains(&atoms._GTK_FRAME_EXTENTS)
}
fn xdnd_is_atom_supported(atom: u32, atoms: &XcbAtoms) -> bool {
return atom == atoms.TEXT
|| atom == atoms.STRING
|| atom == atoms.UTF8_STRING
|| atom == atoms.TEXT_PLAIN
|| atom == atoms.TEXT_PLAIN_UTF8
|| atom == atoms.TextUriList;
}
fn xdnd_get_supported_atom(
xcb_connection: &XCBConnection,
supported_atoms: &XcbAtoms,
target: xproto::Window,
) -> u32 {
let property = xcb_connection
.get_property(
false,
target,
supported_atoms.XdndTypeList,
AtomEnum::ANY,
0,
1024,
)
.unwrap();
if let Ok(reply) = property.reply() {
if let Some(atoms) = reply.value32() {
for atom in atoms {
if xdnd_is_atom_supported(atom, &supported_atoms) {
return atom;
}
}
}
}
return 0;
}
fn xdnd_send_finished(
xcb_connection: &XCBConnection,
atoms: &XcbAtoms,
source: xproto::Window,
target: xproto::Window,
) {
let message = ClientMessageEvent {
format: 32,
window: target,
type_: atoms.XdndFinished,
data: ClientMessageData::from([source, 1, atoms.XdndActionCopy, 0, 0]),
sequence: 0,
response_type: xproto::CLIENT_MESSAGE_EVENT,
};
xcb_connection
.send_event(false, target, EventMask::default(), message)
.unwrap();
}
fn xdnd_send_status(
xcb_connection: &XCBConnection,
atoms: &XcbAtoms,
source: xproto::Window,
target: xproto::Window,
action: u32,
) {
let message = ClientMessageEvent {
format: 32,
window: target,
type_: atoms.XdndStatus,
data: ClientMessageData::from([source, 1, 0, 0, action]),
sequence: 0,
response_type: xproto::CLIENT_MESSAGE_EVENT,
};
xcb_connection
.send_event(false, target, EventMask::default(), message)
.unwrap();
}

View File

@@ -1,3 +1,5 @@
use anyhow::Context;
use crate::{
platform::blade::{BladeRenderer, BladeSurfaceConfig},
px, size, AnyWindowHandle, Bounds, Decorations, DevicePixels, ForegroundExecutor, GPUSpecs,
@@ -7,9 +9,7 @@ use crate::{
X11ClientStatePtr,
};
use anyhow::Context;
use blade_graphics as gpu;
use futures::channel::oneshot;
use raw_window_handle as rwh;
use util::{maybe, ResultExt};
use x11rb::{
@@ -32,7 +32,24 @@ use std::{
use super::{X11Display, XINPUT_MASTER_DEVICE};
x11rb::atom_manager! {
pub XcbAtoms: AtomsCookie {
XA_ATOM,
XdndAware,
XdndStatus,
XdndEnter,
XdndLeave,
XdndPosition,
XdndSelection,
XdndDrop,
XdndFinished,
XdndTypeList,
XdndActionCopy,
TextUriList: b"text/uri-list",
UTF8_STRING,
TEXT,
STRING,
TEXT_PLAIN_UTF8: b"text/plain;charset=utf-8",
TEXT_PLAIN: b"text/plain",
XDND_DATA,
WM_PROTOCOLS,
WM_DELETE_WINDOW,
WM_CHANGE_STATE,
@@ -1210,10 +1227,9 @@ impl PlatformWindow for X11Window {
self.0.callbacks.borrow_mut().appearance_changed = Some(callback);
}
// TODO: on_complete not yet supported for X11 windows
fn draw(&self, scene: &Scene, on_complete: Option<oneshot::Sender<()>>) {
fn draw(&self, scene: &Scene) {
let mut inner = self.0.state.borrow_mut();
inner.renderer.draw(scene, on_complete);
inner.renderer.draw(scene);
}
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
@@ -1406,8 +1422,4 @@ impl PlatformWindow for X11Window {
fn gpu_specs(&self) -> Option<GPUSpecs> {
self.0.state.borrow().renderer.gpu_specs().into()
}
fn fps(&self) -> Option<f32> {
None
}
}

View File

@@ -1,7 +1,7 @@
use super::metal_atlas::MetalAtlas;
use crate::{
point, size, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, ContentMask, DevicePixels,
FpsCounter, Hsla, MonochromeSprite, PaintSurface, Path, PathId, PathVertex, PolychromeSprite,
Hsla, MonochromeSprite, PaintSurface, Path, PathId, PathVertex, PolychromeSprite,
PrimitiveBatch, Quad, ScaledPixels, Scene, Shadow, Size, Surface, Underline,
};
use anyhow::{anyhow, Result};
@@ -14,7 +14,6 @@ use cocoa::{
use collections::HashMap;
use core_foundation::base::TCFType;
use foreign_types::ForeignType;
use futures::channel::oneshot;
use media::core_video::CVMetalTextureCache;
use metal::{CAMetalLayer, CommandQueue, MTLPixelFormat, MTLResourceOptions, NSRange};
use objc::{self, msg_send, sel, sel_impl};
@@ -106,7 +105,6 @@ pub(crate) struct MetalRenderer {
instance_buffer_pool: Arc<Mutex<InstanceBufferPool>>,
sprite_atlas: Arc<MetalAtlas>,
core_video_texture_cache: CVMetalTextureCache,
fps_counter: Arc<FpsCounter>,
}
impl MetalRenderer {
@@ -252,7 +250,6 @@ impl MetalRenderer {
instance_buffer_pool,
sprite_atlas,
core_video_texture_cache,
fps_counter: FpsCounter::new(),
}
}
@@ -295,8 +292,7 @@ impl MetalRenderer {
// nothing to do
}
pub fn draw(&mut self, scene: &Scene, on_complete: Option<oneshot::Sender<()>>) {
let on_complete = Arc::new(Mutex::new(on_complete));
pub fn draw(&mut self, scene: &Scene) {
let layer = self.layer.clone();
let viewport_size = layer.drawable_size();
let viewport_size: Size<DevicePixels> = size(
@@ -323,24 +319,13 @@ impl MetalRenderer {
Ok(command_buffer) => {
let instance_buffer_pool = self.instance_buffer_pool.clone();
let instance_buffer = Cell::new(Some(instance_buffer));
let device = self.device.clone();
let fps_counter = self.fps_counter.clone();
let completed_handler =
ConcreteBlock::new(move |_: &metal::CommandBufferRef| {
let mut cpu_timestamp = 0;
let mut gpu_timestamp = 0;
device.sample_timestamps(&mut cpu_timestamp, &mut gpu_timestamp);
fps_counter.increment(gpu_timestamp);
if let Some(on_complete) = on_complete.lock().take() {
on_complete.send(()).ok();
}
if let Some(instance_buffer) = instance_buffer.take() {
instance_buffer_pool.lock().release(instance_buffer);
}
});
let completed_handler = completed_handler.copy();
command_buffer.add_completed_handler(&completed_handler);
let block = ConcreteBlock::new(move |_| {
if let Some(instance_buffer) = instance_buffer.take() {
instance_buffer_pool.lock().release(instance_buffer);
}
});
let block = block.copy();
command_buffer.add_completed_handler(&block);
if self.presents_with_transaction {
command_buffer.commit();
@@ -1132,10 +1117,6 @@ impl MetalRenderer {
}
true
}
pub fn fps(&self) -> f32 {
self.fps_counter.fps()
}
}
fn build_pipeline_state(

View File

@@ -718,6 +718,20 @@ impl Platform for MacPlatform {
}
}
fn open_with_system(&self, path: &Path) {
let path = path.to_path_buf();
self.0
.lock()
.background_executor
.spawn(async move {
std::process::Command::new("open")
.arg(path)
.spawn()
.expect("Failed to open file");
})
.detach();
}
fn on_quit(&self, callback: Box<dyn FnMut()>) {
self.0.lock().quit = Some(callback);
}

View File

@@ -783,14 +783,14 @@ impl PlatformWindow for MacWindow {
self.0.as_ref().lock().bounds()
}
fn is_maximized(&self) -> bool {
self.0.as_ref().lock().is_maximized()
}
fn window_bounds(&self) -> WindowBounds {
self.0.as_ref().lock().window_bounds()
}
fn is_maximized(&self) -> bool {
self.0.as_ref().lock().is_maximized()
}
fn content_size(&self) -> Size<Pixels> {
self.0.as_ref().lock().content_size()
}
@@ -974,6 +974,8 @@ impl PlatformWindow for MacWindow {
}
}
fn set_app_id(&mut self, _app_id: &str) {}
fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance) {
let mut this = self.0.as_ref().lock();
this.renderer
@@ -1004,6 +1006,30 @@ impl PlatformWindow for MacWindow {
}
}
fn set_edited(&mut self, edited: bool) {
unsafe {
let window = self.0.lock().native_window;
msg_send![window, setDocumentEdited: edited as BOOL]
}
// Changing the document edited state resets the traffic light position,
// so we have to move it again.
self.0.lock().move_traffic_light();
}
fn show_character_palette(&self) {
let this = self.0.lock();
let window = this.native_window;
this.executor
.spawn(async move {
unsafe {
let app = NSApplication::sharedApplication(nil);
let _: () = msg_send![app, orderFrontCharacterPalette: window];
}
})
.detach();
}
fn minimize(&self) {
let window = self.0.lock().native_window;
unsafe {
@@ -1080,41 +1106,15 @@ impl PlatformWindow for MacWindow {
self.0.lock().appearance_changed_callback = Some(callback);
}
fn draw(&self, scene: &crate::Scene, on_complete: Option<oneshot::Sender<()>>) {
fn draw(&self, scene: &crate::Scene) {
let mut this = self.0.lock();
this.renderer.draw(scene, on_complete);
this.renderer.draw(scene);
}
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
self.0.lock().renderer.sprite_atlas().clone()
}
fn set_edited(&mut self, edited: bool) {
unsafe {
let window = self.0.lock().native_window;
msg_send![window, setDocumentEdited: edited as BOOL]
}
// Changing the document edited state resets the traffic light position,
// so we have to move it again.
self.0.lock().move_traffic_light();
}
fn show_character_palette(&self) {
let this = self.0.lock();
let window = this.native_window;
this.executor
.spawn(async move {
unsafe {
let app = NSApplication::sharedApplication(nil);
let _: () = msg_send![app, orderFrontCharacterPalette: window];
}
})
.detach();
}
fn set_app_id(&mut self, _app_id: &str) {}
fn gpu_specs(&self) -> Option<crate::GPUSpecs> {
None
}
@@ -1125,10 +1125,6 @@ impl PlatformWindow for MacWindow {
let _: () = msg_send![input_context, invalidateCharacterCoordinates];
}
}
fn fps(&self) -> Option<f32> {
Some(self.0.lock().renderer.fps())
}
}
impl rwh::HasWindowHandle for MacWindow {

View File

@@ -318,6 +318,10 @@ impl Platform for TestPlatform {
fn register_url_scheme(&self, _: &str) -> Task<anyhow::Result<()>> {
unimplemented!()
}
fn open_with_system(&self, _path: &Path) {
unimplemented!()
}
}
#[cfg(target_os = "windows")]

View File

@@ -251,12 +251,7 @@ impl PlatformWindow for TestWindow {
fn on_appearance_changed(&self, _callback: Box<dyn FnMut()>) {}
fn draw(
&self,
_scene: &crate::Scene,
_on_complete: Option<futures::channel::oneshot::Sender<()>>,
) {
}
fn draw(&self, _scene: &crate::Scene) {}
fn sprite_atlas(&self) -> sync::Arc<dyn crate::PlatformAtlas> {
self.0.lock().sprite_atlas.clone()
@@ -284,10 +279,6 @@ impl PlatformWindow for TestWindow {
fn gpu_specs(&self) -> Option<GPUSpecs> {
None
}
fn fps(&self) -> Option<f32> {
None
}
}
pub(crate) struct TestAtlasState {

View File

@@ -400,6 +400,19 @@ impl Platform for WindowsPlatform {
.detach();
}
fn open_with_system(&self, path: &Path) {
let executor = self.background_executor().clone();
let path = path.to_owned();
executor
.spawn(async move {
let _ = std::process::Command::new("cmd")
.args(&["/c", "start", "", path.to_str().expect("path to string")])
.spawn()
.expect("Failed to open file");
})
.detach();
}
fn on_quit(&self, callback: Box<dyn FnMut()>) {
self.state.borrow_mut().callbacks.quit = Some(callback);
}

View File

@@ -660,8 +660,8 @@ impl PlatformWindow for WindowsWindow {
self.0.state.borrow_mut().callbacks.appearance_changed = Some(callback);
}
fn draw(&self, scene: &Scene, on_complete: Option<oneshot::Sender<()>>) {
self.0.state.borrow_mut().renderer.draw(scene, on_complete)
fn draw(&self, scene: &Scene) {
self.0.state.borrow_mut().renderer.draw(scene)
}
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
@@ -679,10 +679,6 @@ impl PlatformWindow for WindowsWindow {
fn update_ime_position(&self, _bounds: Bounds<Pixels>) {
// todo(windows)
}
fn fps(&self) -> Option<f32> {
None
}
}
#[implement(IDropTarget)]

View File

@@ -153,7 +153,7 @@ impl LineWrapper {
matches!(c, '\u{0400}'..='\u{04FF}') ||
// Some other known special characters that should be treated as word characters,
// e.g. `a-b`, `var_name`, `I'm`, '@mention`, `#hashtag`, `100%`, `3.1415`, `2^3`, `a~b`, etc.
matches!(c, '-' | '_' | '.' | '\'' | '$' | '%' | '@' | '#' | '^' | '~') ||
matches!(c, '-' | '_' | '.' | '\'' | '$' | '%' | '@' | '#' | '^' | '~' | ',') ||
// Characters that used in URL, e.g. `https://github.com/zed-industries/zed?a=1&b=2` for better wrapping a long URL.
matches!(c, '/' | ':' | '?' | '&' | '=') ||
// `⋯` character is special used in Zed, to keep this at the end of the line.

View File

@@ -11,9 +11,9 @@ use crate::{
PromptLevel, Quad, Render, RenderGlyphParams, RenderImage, RenderImageParams, RenderSvgParams,
Replay, ResizeEdge, ScaledPixels, Scene, Shadow, SharedString, Size, StrikethroughStyle, Style,
SubscriberSet, Subscription, TaffyLayoutEngine, Task, TextStyle, TextStyleRefinement,
TimeToFirstWindowDraw, TransformationMatrix, Underline, UnderlineStyle, View, VisualContext,
WeakView, WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowControls,
WindowDecorations, WindowOptions, WindowParams, WindowTextSystem, SUBPIXEL_VARIANTS,
TransformationMatrix, Underline, UnderlineStyle, View, VisualContext, WeakView,
WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowControls, WindowDecorations,
WindowOptions, WindowParams, WindowTextSystem, SUBPIXEL_VARIANTS,
};
use anyhow::{anyhow, Context as _, Result};
use collections::{FxHashMap, FxHashSet};
@@ -545,8 +545,6 @@ pub struct Window {
hovered: Rc<Cell<bool>>,
pub(crate) dirty: Rc<Cell<bool>>,
pub(crate) needs_present: Rc<Cell<bool>>,
/// We assign this to be notified when the platform graphics backend fires the next completion callback for drawing the window.
present_completed: RefCell<Option<oneshot::Sender<()>>>,
pub(crate) last_input_timestamp: Rc<Cell<Instant>>,
pub(crate) refreshing: bool,
pub(crate) draw_phase: DrawPhase,
@@ -824,7 +822,6 @@ impl Window {
hovered,
dirty,
needs_present,
present_completed: RefCell::default(),
last_input_timestamp,
refreshing: false,
draw_phase: DrawPhase::None,
@@ -926,15 +923,7 @@ impl<'a> WindowContext<'a> {
/// Close this window.
pub fn remove_window(&mut self) {
println!("remove window!");
self.window.removed = true;
// self.spawn(|cx| {
// for _ in 0..10 {
// cx.background_executor().timer(Duration::from_secs(1)).await;
// AsyncAppContext::update(&mut cx, |cx| cx.update(|cx| {})).ok();
// })
// .detach()
}
/// Obtain a new [`FocusHandle`], which allows you to track and manipulate the keyboard focus
@@ -1502,29 +1491,13 @@ impl<'a> WindowContext<'a> {
self.window.refreshing = false;
self.window.draw_phase = DrawPhase::None;
self.window.needs_present.set(true);
if let Some(TimeToFirstWindowDraw::Pending(start)) = self.app.time_to_first_window_draw {
let (tx, rx) = oneshot::channel();
*self.window.present_completed.borrow_mut() = Some(tx);
self.spawn(|mut cx| async move {
rx.await.ok();
cx.update(|cx| {
let duration = start.elapsed();
cx.time_to_first_window_draw = Some(TimeToFirstWindowDraw::Done(duration));
log::info!("time to first window draw: {:?}", duration);
cx.push_effect(Effect::Refresh);
})
})
.detach();
}
}
#[profiling::function]
fn present(&self) {
let on_complete = self.window.present_completed.take();
self.window
.platform_window
.draw(&self.window.rendered_frame.scene, on_complete);
.draw(&self.window.rendered_frame.scene);
self.window.needs_present.set(false);
profiling::finish_frame!();
}
@@ -3806,12 +3779,6 @@ impl<'a> WindowContext<'a> {
pub fn gpu_specs(&self) -> Option<GPUSpecs> {
self.window.platform_window.gpu_specs()
}
/// Get the current FPS (frames per second) of the window.
/// This is only supported on macOS currently.
pub fn fps(&self) -> Option<f32> {
self.window.platform_window.fps()
}
}
#[cfg(target_os = "windows")]

View File

@@ -0,0 +1,25 @@
use gpui::{HighlightStyle, Hsla};
use std::sync::Arc;
use theme::{AccentColors, SyntaxTheme};
#[derive(Clone, Debug)]
pub struct BracketMap(Vec<Hsla>);
impl BracketMap {
pub(crate) fn new(colors: AccentColors) -> Self {
// For each capture name in the highlight query, find the longest
// key in the theme's syntax styles that matches all of the
// dot-separated components of the capture name.
BracketMap(colors.0)
}
pub fn get(&self, depth: u32) -> Hsla {
self.0.get(depth % self.0.len()).copied().unwrap()
}
}
impl Default for BracketMap {
fn default() -> Self {
Self(vec![])
}
}

View File

@@ -27,6 +27,7 @@ use gpui::{
use lsp::LanguageServerId;
use parking_lot::Mutex;
use serde_json::Value;
use settings::WorktreeId;
use similar::{ChangeTag, TextDiff};
use smallvec::SmallVec;
use smol::future::yield_now;
@@ -361,7 +362,7 @@ pub trait File: Send + Sync {
/// Returns the id of the worktree to which this file belongs.
///
/// This is needed for looking up project-specific settings.
fn worktree_id(&self) -> usize;
fn worktree_id(&self, cx: &AppContext) -> WorktreeId;
/// Returns whether the file has been deleted.
fn is_deleted(&self) -> bool;
@@ -2573,16 +2574,20 @@ impl BufferSnapshot {
None
}
fn get_highlights(&self, range: Range<usize>) -> (SyntaxMapCaptures, Vec<HighlightMap>) {
fn get_highlights(&self, range: Range<usize>) -> (SyntaxMapCaptures, Vec<HighlightMap>, usize) {
//let settings = self.settings_at(range.start, cx).rainbow_brackets;
let captures = self.syntax.captures(range, &self.text, |grammar| {
grammar.highlights_query.as_ref()
grammar
.highlights_with_brackets_query
.as_ref()
.map(|(query, _)| query)
});
let highlight_maps = captures
.grammars()
.iter()
.map(|grammar| grammar.highlight_map())
.collect();
(captures, highlight_maps)
(captures, highlight_maps, bracket_patterns_count)
}
/// Iterates over chunks of text in the given range of the buffer. Text is chunked
/// in an arbitrary way due to being stored in a [`Rope`](text::Rope). The text is also
@@ -4172,8 +4177,8 @@ impl File for TestFile {
self.path().file_name().unwrap_or(self.root_name.as_ref())
}
fn worktree_id(&self) -> usize {
0
fn worktree_id(&self, _: &AppContext) -> WorktreeId {
WorktreeId::from_usize(0)
}
fn is_deleted(&self) -> bool {
@@ -4301,7 +4306,7 @@ pub fn trailing_whitespace_ranges(rope: &Rope) -> Vec<Range<usize>> {
let mut prev_line_trailing_whitespace_range = 0..0;
for (i, line) in chunk.split('\n').enumerate() {
let line_end_offset = offset + line.len();
let trimmed_line_len = line.trim_end_matches(|c| matches!(c, ' ' | '\t')).len();
let trimmed_line_len = line.trim_end_matches([' ', '\t']).len();
let mut trailing_whitespace_range = (offset + trimmed_line_len)..line_end_offset;
if i == 0 && trimmed_line_len == 0 {

View File

@@ -72,7 +72,7 @@ fn test_select_language(cx: &mut AppContext) {
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
registry.add(Arc::new(Language::new(
LanguageConfig {
name: "Rust".into(),
name: LanguageName::new("Rust"),
matcher: LanguageMatcher {
path_suffixes: vec!["rs".to_string()],
..Default::default()
@@ -83,7 +83,7 @@ fn test_select_language(cx: &mut AppContext) {
)));
registry.add(Arc::new(Language::new(
LanguageConfig {
name: "Make".into(),
name: LanguageName::new("Make"),
matcher: LanguageMatcher {
path_suffixes: vec!["Makefile".to_string(), "mk".to_string()],
..Default::default()
@@ -97,15 +97,13 @@ fn test_select_language(cx: &mut AppContext) {
assert_eq!(
registry
.language_for_file(&file("src/lib.rs"), None, cx)
.now_or_never()
.and_then(|l| Some(l.ok()?.name())),
.map(|l| l.name()),
Some("Rust".into())
);
assert_eq!(
registry
.language_for_file(&file("src/lib.mk"), None, cx)
.now_or_never()
.and_then(|l| Some(l.ok()?.name())),
.map(|l| l.name()),
Some("Make".into())
);
@@ -113,8 +111,7 @@ fn test_select_language(cx: &mut AppContext) {
assert_eq!(
registry
.language_for_file(&file("src/Makefile"), None, cx)
.now_or_never()
.and_then(|l| Some(l.ok()?.name())),
.map(|l| l.name()),
Some("Make".into())
);
@@ -122,22 +119,19 @@ fn test_select_language(cx: &mut AppContext) {
assert_eq!(
registry
.language_for_file(&file("zed/cars"), None, cx)
.now_or_never()
.and_then(|l| Some(l.ok()?.name())),
.map(|l| l.name()),
None
);
assert_eq!(
registry
.language_for_file(&file("zed/a.cars"), None, cx)
.now_or_never()
.and_then(|l| Some(l.ok()?.name())),
.map(|l| l.name()),
None
);
assert_eq!(
registry
.language_for_file(&file("zed/sumk"), None, cx)
.now_or_never()
.and_then(|l| Some(l.ok()?.name())),
.map(|l| l.name()),
None
);
}
@@ -158,23 +152,22 @@ async fn test_first_line_pattern(cx: &mut TestAppContext) {
..Default::default()
});
cx.read(|cx| languages.language_for_file(&file("the/script"), None, cx))
.await
.unwrap_err();
cx.read(|cx| languages.language_for_file(&file("the/script"), Some(&"nothing".into()), cx))
.await
.unwrap_err();
assert!(cx
.read(|cx| languages.language_for_file(&file("the/script"), None, cx))
.is_none());
assert!(cx
.read(|cx| languages.language_for_file(&file("the/script"), Some(&"nothing".into()), cx))
.is_none());
assert_eq!(
cx.read(|cx| languages.language_for_file(
&file("the/script"),
Some(&"#!/bin/env node".into()),
cx
))
.await
.unwrap()
.name()
.as_ref(),
"JavaScript"
.name(),
"JavaScript".into()
);
}
@@ -242,19 +235,16 @@ async fn test_language_for_file_with_custom_file_types(cx: &mut TestAppContext)
let language = cx
.read(|cx| languages.language_for_file(&file("foo.js"), None, cx))
.await
.unwrap();
assert_eq!(language.name().as_ref(), "TypeScript");
assert_eq!(language.name(), "TypeScript".into());
let language = cx
.read(|cx| languages.language_for_file(&file("foo.c"), None, cx))
.await
.unwrap();
assert_eq!(language.name().as_ref(), "C++");
assert_eq!(language.name(), "C++".into());
let language = cx
.read(|cx| languages.language_for_file(&file("Dockerfile.dev"), None, cx))
.await
.unwrap();
assert_eq!(language.name().as_ref(), "Dockerfile");
assert_eq!(language.name(), "Dockerfile".into());
}
fn file(path: &str) -> Arc<dyn File> {
@@ -2245,10 +2235,10 @@ fn test_language_at_with_hidden_languages(cx: &mut AppContext) {
for point in [Point::new(0, 4), Point::new(0, 16)] {
let config = snapshot.language_scope_at(point).unwrap();
assert_eq!(config.language_name().as_ref(), "Markdown");
assert_eq!(config.language_name(), "Markdown".into());
let language = snapshot.language_at(point).unwrap();
assert_eq!(language.name().as_ref(), "Markdown");
assert_eq!(language.name().0.as_ref(), "Markdown");
}
buffer
@@ -2757,7 +2747,7 @@ fn ruby_lang() -> Language {
fn html_lang() -> Language {
Language::new(
LanguageConfig {
name: "HTML".into(),
name: LanguageName::new("HTML"),
block_comment: Some(("<!--".into(), "-->".into())),
..Default::default()
},

View File

@@ -28,6 +28,7 @@ use futures::Future;
use gpui::{AppContext, AsyncAppContext, Model, SharedString, Task};
pub use highlight_map::HighlightMap;
use http_client::HttpClient;
pub use language_registry::LanguageName;
use lsp::{CodeActionKind, LanguageServerBinary};
use parking_lot::Mutex;
use regex::Regex;
@@ -38,6 +39,7 @@ use schemars::{
};
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use serde_json::Value;
use settings::WorktreeId;
use smol::future::FutureExt as _;
use std::num::NonZeroU32;
use std::{
@@ -60,14 +62,14 @@ use task::RunnableTag;
pub use task_context::{ContextProvider, RunnableRange};
use theme::SyntaxTheme;
use tree_sitter::{self, wasmtime, Query, QueryCursor, WasmStore};
use util::serde::default_true;
use util::{maybe, serde::default_true};
pub use buffer::Operation;
pub use buffer::*;
pub use diagnostic_set::DiagnosticEntry;
pub use language_registry::{
LanguageNotFound, LanguageQueries, LanguageRegistry, LanguageServerBinaryStatus,
PendingLanguageServer, QUERY_FILENAME_PREFIXES,
AvailableLanguage, LanguageNotFound, LanguageQueries, LanguageRegistry,
LanguageServerBinaryStatus, PendingLanguageServer, QUERY_FILENAME_PREFIXES,
};
pub use lsp::LanguageServerId;
pub use outline::*;
@@ -139,6 +141,12 @@ pub trait ToLspPosition {
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
pub struct LanguageServerName(pub Arc<str>);
impl LanguageServerName {
pub fn from_proto(s: String) -> Self {
Self(Arc::from(s))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Location {
pub buffer: Model<Buffer>,
@@ -194,9 +202,12 @@ impl CachedLspAdapter {
})
}
pub fn name(&self) -> Arc<str> {
self.adapter.name().0.clone()
}
pub async fn get_language_server_command(
self: Arc<Self>,
language: Arc<Language>,
container_dir: Arc<Path>,
delegate: Arc<dyn LspAdapterDelegate>,
cx: &mut AsyncAppContext,
@@ -204,18 +215,10 @@ impl CachedLspAdapter {
let cached_binary = self.cached_binary.lock().await;
self.adapter
.clone()
.get_language_server_command(language, container_dir, delegate, cached_binary, cx)
.get_language_server_command(container_dir, delegate, cached_binary, cx)
.await
}
pub fn will_start_server(
&self,
delegate: &Arc<dyn LspAdapterDelegate>,
cx: &mut AsyncAppContext,
) -> Option<Task<Result<()>>> {
self.adapter.will_start_server(delegate, cx)
}
pub fn can_be_reinstalled(&self) -> bool {
self.adapter.can_be_reinstalled()
}
@@ -261,11 +264,11 @@ impl CachedLspAdapter {
.await
}
pub fn language_id(&self, language: &Language) -> String {
pub fn language_id(&self, language_name: &LanguageName) -> String {
self.language_ids
.get(language.name().as_ref())
.get(language_name.0.as_ref())
.cloned()
.unwrap_or_else(|| language.lsp_id())
.unwrap_or_else(|| language_name.lsp_id())
}
#[cfg(any(test, feature = "test-support"))]
@@ -280,7 +283,7 @@ impl CachedLspAdapter {
pub trait LspAdapterDelegate: Send + Sync {
fn show_notification(&self, message: &str, cx: &mut AppContext);
fn http_client(&self) -> Arc<dyn HttpClient>;
fn worktree_id(&self) -> u64;
fn worktree_id(&self) -> WorktreeId;
fn worktree_root_path(&self) -> &Path;
fn update_status(&self, language: LanguageServerName, status: LanguageServerBinaryStatus);
@@ -295,7 +298,6 @@ pub trait LspAdapter: 'static + Send + Sync {
fn get_language_server_command<'a>(
self: Arc<Self>,
language: Arc<Language>,
container_dir: Arc<Path>,
delegate: Arc<dyn LspAdapterDelegate>,
mut cached_binary: futures::lock::MutexGuard<'a, Option<LanguageServerBinary>>,
@@ -316,7 +318,7 @@ pub trait LspAdapter: 'static + Send + Sync {
if let Some(binary) = self.check_if_user_installed(delegate.as_ref(), cx).await {
log::info!(
"found user-installed language server for {}. path: {:?}, arguments: {:?}",
language.name(),
self.name().0,
binary.path,
binary.arguments
);
@@ -386,14 +388,6 @@ pub trait LspAdapter: 'static + Send + Sync {
None
}
fn will_start_server(
&self,
_: &Arc<dyn LspAdapterDelegate>,
_: &mut AsyncAppContext,
) -> Option<Task<Result<()>>> {
None
}
async fn fetch_server_binary(
&self,
latest_version: Box<dyn 'static + Send + Any>,
@@ -561,7 +555,7 @@ pub struct CodeLabel {
#[derive(Clone, Deserialize, JsonSchema)]
pub struct LanguageConfig {
/// Human-readable name of the language.
pub name: Arc<str>,
pub name: LanguageName,
/// The name of this language for a Markdown code fence block
pub code_fence_block_name: Option<Arc<str>>,
// The name of the grammar in a WASM bundle (experimental).
@@ -698,7 +692,7 @@ impl<T> Override<T> {
impl Default for LanguageConfig {
fn default() -> Self {
Self {
name: Arc::default(),
name: LanguageName::new(""),
code_fence_block_name: None,
grammar: None,
matcher: LanguageMatcher::default(),
@@ -854,6 +848,7 @@ impl GrammarId {
}
}
struct HighlighBracketsQuery(Query, usize);
pub struct Grammar {
id: GrammarId,
pub ts_language: tree_sitter::Language,
@@ -868,6 +863,7 @@ pub struct Grammar {
pub(crate) injection_config: Option<InjectionConfig>,
pub(crate) override_config: Option<OverrideConfig>,
pub(crate) highlight_map: Mutex<HighlightMap>,
pub(crate) highlights_with_brackets_query: Option<(Query, usize)>,
}
struct IndentConfig {
@@ -968,6 +964,7 @@ impl Language {
error_query: Query::new(&ts_language, "(ERROR) @error").unwrap(),
ts_language,
highlight_map: Default::default(),
highlights_with_brackets_query: None,
})
}),
context_provider: None,
@@ -980,12 +977,12 @@ impl Language {
}
pub fn with_queries(mut self, queries: LanguageQueries) -> Result<Self> {
if let Some(query) = queries.highlights {
if let Some(query) = queries.highlights.as_ref() {
self = self
.with_highlights_query(query.as_ref())
.context("Error loading highlights query")?;
}
if let Some(query) = queries.brackets {
if let Some(query) = queries.brackets.as_ref() {
self = self
.with_brackets_query(query.as_ref())
.context("Error loading brackets query")?;
@@ -1025,6 +1022,34 @@ impl Language {
.with_runnable_query(query.as_ref())
.context("Error loading tests query")?;
}
if let Some((brackets, highlights)) = queries.brackets.zip(queries.highlights) {
self = self
.with_highlights_and_brackets_query(&brackets, &highlights)
.context("Error loading rainbow brackets query")?;
}
Ok(self)
}
pub fn with_highlights_and_brackets_query(
mut self,
brackets: &str,
highlights: &str,
) -> Result<Self> {
let grammar = self
.grammar_mut()
.ok_or_else(|| anyhow!("cannot mutate grammar"))?;
let merged_query = format!("{brackets}\n{highlights}");
let brackets_count = grammar
.brackets_config
.as_ref()
.expect("Expected bracket query to be initialized")
.query
.pattern_count();
grammar.highlights_with_brackets_query = Some((
Query::new(&grammar.ts_language, &merged_query)?,
brackets_count,
));
Ok(self)
}
@@ -1334,7 +1359,7 @@ impl Language {
Arc::get_mut(self.grammar.as_mut()?)
}
pub fn name(&self) -> Arc<str> {
pub fn name(&self) -> LanguageName {
self.config.name.clone()
}
@@ -1342,7 +1367,7 @@ impl Language {
self.config
.code_fence_block_name
.clone()
.unwrap_or_else(|| self.config.name.to_lowercase().into())
.unwrap_or_else(|| self.config.name.0.to_lowercase().into())
}
pub fn context_provider(&self) -> Option<Arc<dyn ContextProvider>> {
@@ -1407,10 +1432,7 @@ impl Language {
}
pub fn lsp_id(&self) -> String {
match self.config.name.as_ref() {
"Plain Text" => "plaintext".to_string(),
language_name => language_name.to_lowercase(),
}
self.config.name.lsp_id()
}
pub fn prettier_parser_name(&self) -> Option<&str> {
@@ -1419,7 +1441,7 @@ impl Language {
}
impl LanguageScope {
pub fn language_name(&self) -> Arc<str> {
pub fn language_name(&self) -> LanguageName {
self.language.config.name.clone()
}
@@ -1660,9 +1682,16 @@ impl LspAdapter for FakeLspAdapter {
LanguageServerName(self.name.into())
}
async fn check_if_user_installed(
&self,
_: &dyn LspAdapterDelegate,
_: &AsyncAppContext,
) -> Option<LanguageServerBinary> {
Some(self.language_server_binary.clone())
}
fn get_language_server_command<'a>(
self: Arc<Self>,
_: Arc<Language>,
_: Arc<Path>,
_: Arc<dyn LspAdapterDelegate>,
_: futures::lock::MutexGuard<'a, Option<LanguageServerBinary>>,

View File

@@ -6,9 +6,9 @@ use crate::{
with_parser, CachedLspAdapter, File, Language, LanguageConfig, LanguageId, LanguageMatcher,
LanguageServerName, LspAdapter, LspAdapterDelegate, PLAIN_TEXT,
};
use anyhow::{anyhow, Context as _, Result};
use anyhow::{anyhow, Context, Result};
use collections::{hash_map, HashMap, HashSet};
use futures::TryFutureExt;
use futures::{
channel::{mpsc, oneshot},
future::Shared,
@@ -19,8 +19,10 @@ use gpui::{AppContext, BackgroundExecutor, Task};
use lsp::LanguageServerId;
use parking_lot::{Mutex, RwLock};
use postage::watch;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::{
borrow::Cow,
borrow::{Borrow, Cow},
ffi::OsStr,
ops::Not,
path::{Path, PathBuf},
@@ -32,6 +34,48 @@ use theme::Theme;
use unicase::UniCase;
use util::{maybe, paths::PathExt, post_inc, ResultExt};
#[derive(
Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, JsonSchema,
)]
pub struct LanguageName(pub Arc<str>);
impl LanguageName {
pub fn new(s: &str) -> Self {
Self(Arc::from(s))
}
pub fn from_proto(s: String) -> Self {
Self(Arc::from(s))
}
pub fn to_proto(self) -> String {
self.0.to_string()
}
pub fn lsp_id(&self) -> String {
match self.0.as_ref() {
"Plain Text" => "plaintext".to_string(),
language_name => language_name.to_lowercase(),
}
}
}
impl Borrow<str> for LanguageName {
fn borrow(&self) -> &str {
self.0.as_ref()
}
}
impl std::fmt::Display for LanguageName {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl<'a> From<&'a str> for LanguageName {
fn from(str: &'a str) -> LanguageName {
LanguageName(str.into())
}
}
pub struct LanguageRegistry {
state: RwLock<LanguageRegistryState>,
language_server_download_dir: Option<Arc<Path>>,
@@ -46,7 +90,7 @@ struct LanguageRegistryState {
language_settings: AllLanguageSettingsContent,
available_languages: Vec<AvailableLanguage>,
grammars: HashMap<Arc<str>, AvailableGrammar>,
lsp_adapters: HashMap<Arc<str>, Vec<Arc<CachedLspAdapter>>>,
lsp_adapters: HashMap<LanguageName, Vec<Arc<CachedLspAdapter>>>,
available_lsp_adapters:
HashMap<LanguageServerName, Arc<dyn Fn() -> Arc<CachedLspAdapter> + 'static + Send + Sync>>,
loading_languages: HashMap<LanguageId, Vec<oneshot::Sender<Result<Arc<Language>>>>>,
@@ -56,8 +100,10 @@ struct LanguageRegistryState {
reload_count: usize,
#[cfg(any(test, feature = "test-support"))]
fake_server_txs:
HashMap<Arc<str>, Vec<futures::channel::mpsc::UnboundedSender<lsp::FakeLanguageServer>>>,
fake_server_txs: HashMap<
LanguageName,
Vec<futures::channel::mpsc::UnboundedSender<lsp::FakeLanguageServer>>,
>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
@@ -75,9 +121,9 @@ pub struct PendingLanguageServer {
}
#[derive(Clone)]
struct AvailableLanguage {
pub struct AvailableLanguage {
id: LanguageId,
name: Arc<str>,
name: LanguageName,
grammar: Option<Arc<str>>,
matcher: LanguageMatcher,
load: Arc<
@@ -93,6 +139,16 @@ struct AvailableLanguage {
loaded: bool,
}
impl AvailableLanguage {
pub fn name(&self) -> LanguageName {
self.name.clone()
}
pub fn matcher(&self) -> &LanguageMatcher {
&self.matcher
}
}
enum AvailableGrammar {
Native(tree_sitter::Language),
Loaded(#[allow(unused)] PathBuf, tree_sitter::Language),
@@ -196,7 +252,7 @@ impl LanguageRegistry {
/// appended to the end.
pub fn reorder_language_servers(
&self,
language: &Arc<Language>,
language: &LanguageName,
ordered_lsp_adapters: Vec<Arc<CachedLspAdapter>>,
) {
self.state
@@ -207,7 +263,7 @@ impl LanguageRegistry {
/// Removes the specified languages and grammars from the registry.
pub fn remove_languages(
&self,
languages_to_remove: &[Arc<str>],
languages_to_remove: &[LanguageName],
grammars_to_remove: &[Arc<str>],
) {
self.state
@@ -215,7 +271,7 @@ impl LanguageRegistry {
.remove_languages(languages_to_remove, grammars_to_remove)
}
pub fn remove_lsp_adapter(&self, language_name: &str, name: &LanguageServerName) {
pub fn remove_lsp_adapter(&self, language_name: &LanguageName, name: &LanguageServerName) {
let mut state = self.state.write();
if let Some(adapters) = state.lsp_adapters.get_mut(language_name) {
adapters.retain(|adapter| &adapter.name != name)
@@ -267,7 +323,7 @@ impl LanguageRegistry {
Some(load_lsp_adapter())
}
pub fn register_lsp_adapter(&self, language_name: Arc<str>, adapter: Arc<dyn LspAdapter>) {
pub fn register_lsp_adapter(&self, language_name: LanguageName, adapter: Arc<dyn LspAdapter>) {
self.state
.write()
.lsp_adapters
@@ -279,13 +335,14 @@ impl LanguageRegistry {
#[cfg(any(feature = "test-support", test))]
pub fn register_fake_lsp_adapter(
&self,
language_name: &str,
language_name: impl Into<LanguageName>,
adapter: crate::FakeLspAdapter,
) -> futures::channel::mpsc::UnboundedReceiver<lsp::FakeLanguageServer> {
let language_name = language_name.into();
self.state
.write()
.lsp_adapters
.entry(language_name.into())
.entry(language_name.clone())
.or_default()
.push(CachedLspAdapter::new(Arc::new(adapter)));
self.fake_language_servers(language_name)
@@ -294,13 +351,13 @@ impl LanguageRegistry {
#[cfg(any(feature = "test-support", test))]
pub fn fake_language_servers(
&self,
language_name: &str,
language_name: LanguageName,
) -> futures::channel::mpsc::UnboundedReceiver<lsp::FakeLanguageServer> {
let (servers_tx, servers_rx) = futures::channel::mpsc::unbounded();
self.state
.write()
.fake_server_txs
.entry(language_name.into())
.entry(language_name)
.or_default()
.push(servers_tx);
servers_rx
@@ -309,7 +366,7 @@ impl LanguageRegistry {
/// Adds a language to the registry, which can be loaded if needed.
pub fn register_language(
&self,
name: Arc<str>,
name: LanguageName,
grammar_name: Option<Arc<str>>,
matcher: LanguageMatcher,
load: impl Fn() -> Result<(
@@ -445,7 +502,7 @@ impl LanguageRegistry {
) -> impl Future<Output = Result<Arc<Language>>> {
let name = UniCase::new(name);
let rx = self.get_or_load_language(|language_name, _| {
if UniCase::new(language_name) == name {
if UniCase::new(&language_name.0) == name {
1
} else {
0
@@ -460,7 +517,7 @@ impl LanguageRegistry {
) -> impl Future<Output = Result<Arc<Language>>> {
let string = UniCase::new(string);
let rx = self.get_or_load_language(|name, config| {
if UniCase::new(name) == string
if UniCase::new(&name.0) == string
|| config
.path_suffixes
.iter()
@@ -474,13 +531,26 @@ impl LanguageRegistry {
async move { rx.await? }
}
pub fn available_language_for_name(
self: &Arc<Self>,
name: &LanguageName,
) -> Option<AvailableLanguage> {
let state = self.state.read();
state
.available_languages
.iter()
.find(|l| &l.name == name)
.cloned()
}
pub fn language_for_file(
self: &Arc<Self>,
file: &Arc<dyn File>,
content: Option<&Rope>,
cx: &AppContext,
) -> impl Future<Output = Result<Arc<Language>>> {
) -> Option<AvailableLanguage> {
let user_file_types = all_language_settings(Some(file), cx);
self.language_for_file_internal(
&file.full_path(cx),
content,
@@ -492,8 +562,16 @@ impl LanguageRegistry {
self: &Arc<Self>,
path: &'a Path,
) -> impl Future<Output = Result<Arc<Language>>> + 'a {
self.language_for_file_internal(path, None, None)
.map_err(|error| error.context(format!("language for file path {}", path.display())))
let available_language = self.language_for_file_internal(path, None, None);
let this = self.clone();
async move {
if let Some(language) = available_language {
this.load_language(&language).await?
} else {
Err(anyhow!(LanguageNotFound))
}
}
}
fn language_for_file_internal(
@@ -501,19 +579,19 @@ impl LanguageRegistry {
path: &Path,
content: Option<&Rope>,
user_file_types: Option<&HashMap<Arc<str>, GlobSet>>,
) -> impl Future<Output = Result<Arc<Language>>> {
) -> Option<AvailableLanguage> {
let filename = path.file_name().and_then(|name| name.to_str());
let extension = path.extension_or_hidden_file_name();
let path_suffixes = [extension, filename, path.to_str()];
let empty = GlobSet::empty();
let rx = self.get_or_load_language(move |language_name, config| {
self.find_matching_language(move |language_name, config| {
let path_matches_default_suffix = config
.path_suffixes
.iter()
.any(|suffix| path_suffixes.contains(&Some(suffix.as_str())));
let custom_suffixes = user_file_types
.and_then(|types| types.get(language_name))
.and_then(|types| types.get(&language_name.0))
.unwrap_or(&empty);
let path_matches_custom_suffix = path_suffixes
.iter()
@@ -535,18 +613,15 @@ impl LanguageRegistry {
} else {
0
}
});
async move { rx.await? }
})
}
fn get_or_load_language(
fn find_matching_language(
self: &Arc<Self>,
callback: impl Fn(&str, &LanguageMatcher) -> usize,
) -> oneshot::Receiver<Result<Arc<Language>>> {
let (tx, rx) = oneshot::channel();
let mut state = self.state.write();
let Some((language, _)) = state
callback: impl Fn(&LanguageName, &LanguageMatcher) -> usize,
) -> Option<AvailableLanguage> {
let state = self.state.read();
let available_language = state
.available_languages
.iter()
.filter_map(|language| {
@@ -559,15 +634,23 @@ impl LanguageRegistry {
})
.max_by_key(|e| e.1)
.clone()
else {
let _ = tx.send(Err(anyhow!(LanguageNotFound)));
return rx;
};
.map(|(available_language, _)| available_language);
drop(state);
available_language
}
pub fn load_language(
self: &Arc<Self>,
language: &AvailableLanguage,
) -> oneshot::Receiver<Result<Arc<Language>>> {
let (tx, rx) = oneshot::channel();
let mut state = self.state.write();
// If the language is already loaded, resolve with it immediately.
for loaded_language in state.languages.iter() {
if loaded_language.id == language.id {
let _ = tx.send(Ok(loaded_language.clone()));
tx.send(Ok(loaded_language.clone())).unwrap();
return rx;
}
}
@@ -580,12 +663,15 @@ impl LanguageRegistry {
// Otherwise, start loading the language.
hash_map::Entry::Vacant(entry) => {
let this = self.clone();
let id = language.id;
let name = language.name.clone();
let language_load = language.load.clone();
self.executor
.spawn(async move {
let id = language.id;
let name = language.name.clone();
let language = async {
let (config, queries, provider) = (language.load)()?;
let (config, queries, provider) = (language_load)()?;
if let Some(grammar) = config.grammar.clone() {
let grammar = Some(this.get_or_load_grammar(grammar).await?);
@@ -629,13 +715,28 @@ impl LanguageRegistry {
};
})
.detach();
entry.insert(vec![tx]);
}
}
drop(state);
rx
}
fn get_or_load_language(
self: &Arc<Self>,
callback: impl Fn(&LanguageName, &LanguageMatcher) -> usize,
) -> oneshot::Receiver<Result<Arc<Language>>> {
let Some(language) = self.find_matching_language(callback) else {
let (tx, rx) = oneshot::channel();
let _ = tx.send(Err(anyhow!(LanguageNotFound)));
return rx;
};
self.load_language(&language)
}
fn get_or_load_grammar(
self: &Arc<Self>,
name: Arc<str>,
@@ -702,11 +803,11 @@ impl LanguageRegistry {
self.state.read().languages.to_vec()
}
pub fn lsp_adapters(&self, language: &Arc<Language>) -> Vec<Arc<CachedLspAdapter>> {
pub fn lsp_adapters(&self, language_name: &LanguageName) -> Vec<Arc<CachedLspAdapter>> {
self.state
.read()
.lsp_adapters
.get(&language.config.name)
.get(language_name)
.cloned()
.unwrap_or_default()
}
@@ -723,7 +824,7 @@ impl LanguageRegistry {
pub fn create_pending_language_server(
self: &Arc<Self>,
stderr_capture: Arc<Mutex<Option<String>>>,
language: Arc<Language>,
_language_name_for_tests: LanguageName,
adapter: Arc<CachedLspAdapter>,
root_path: Arc<Path>,
delegate: Arc<dyn LspAdapterDelegate>,
@@ -732,7 +833,7 @@ impl LanguageRegistry {
) -> Option<PendingLanguageServer> {
let server_id = self.state.write().next_language_server_id();
log::info!(
"starting language server {:?}, path: {root_path:?}, id: {server_id}",
"attempting to start language server {:?}, path: {root_path:?}, id: {server_id}",
adapter.name.0
);
@@ -741,7 +842,6 @@ impl LanguageRegistry {
.clone()
.ok_or_else(|| anyhow!("language server download directory has not been assigned before starting server"))
.log_err()?;
let language = language.clone();
let container_dir: Arc<Path> = Arc::from(download_dir.join(adapter.name.0.as_ref()));
let root_path = root_path.clone();
let login_shell_env_loaded = self.login_shell_env_loaded.clone();
@@ -756,12 +856,7 @@ impl LanguageRegistry {
let binary_result = adapter
.clone()
.get_language_server_command(
language.clone(),
container_dir,
delegate.clone(),
&mut cx,
)
.get_language_server_command(container_dir, delegate.clone(), &mut cx)
.await;
delegate.update_status(adapter.name.clone(), LanguageServerBinaryStatus::None);
@@ -785,10 +880,6 @@ impl LanguageRegistry {
.initialization_options(&delegate)
.await?;
if let Some(task) = adapter.will_start_server(&delegate, &mut cx) {
task.await?;
}
#[cfg(any(test, feature = "test-support"))]
if true {
let capabilities = adapter
@@ -825,7 +916,7 @@ impl LanguageRegistry {
.state
.write()
.fake_server_txs
.get_mut(language.name().as_ref())
.get_mut(&_language_name_for_tests)
{
for tx in txs {
tx.unbounded_send(fake_server.clone()).ok();
@@ -935,10 +1026,10 @@ impl LanguageRegistryState {
/// appended to the end.
fn reorder_language_servers(
&mut self,
language: &Arc<Language>,
language_name: &LanguageName,
ordered_lsp_adapters: Vec<Arc<CachedLspAdapter>>,
) {
let Some(lsp_adapters) = self.lsp_adapters.get_mut(&language.config.name) else {
let Some(lsp_adapters) = self.lsp_adapters.get_mut(language_name) else {
return;
};
@@ -959,7 +1050,7 @@ impl LanguageRegistryState {
fn remove_languages(
&mut self,
languages_to_remove: &[Arc<str>],
languages_to_remove: &[LanguageName],
grammars_to_remove: &[Arc<str>],
) {
if languages_to_remove.is_empty() && grammars_to_remove.is_empty() {

View File

@@ -1,6 +1,6 @@
//! Provides `language`-related settings.
use crate::{File, Language, LanguageServerName};
use crate::{File, Language, LanguageName, LanguageServerName};
use anyhow::Result;
use collections::{HashMap, HashSet};
use core::slice;
@@ -20,15 +20,6 @@ use settings::{add_references_to_properties, Settings, SettingsLocation, Setting
use std::{num::NonZeroU32, path::Path, sync::Arc};
use util::serde::default_true;
impl<'a> From<&'a dyn File> for SettingsLocation<'a> {
fn from(val: &'a dyn File) -> Self {
SettingsLocation {
worktree_id: val.worktree_id(),
path: val.path().as_ref(),
}
}
}
/// Initializes the language settings.
pub fn init(cx: &mut AppContext) {
AllLanguageSettings::register(cx);
@@ -41,7 +32,7 @@ pub fn language_settings<'a>(
cx: &'a AppContext,
) -> &'a LanguageSettings {
let language_name = language.map(|l| l.name());
all_language_settings(file, cx).language(language_name.as_deref())
all_language_settings(file, cx).language(language_name.as_ref())
}
/// Returns the settings for all languages from the provided file.
@@ -49,7 +40,10 @@ pub fn all_language_settings<'a>(
file: Option<&Arc<dyn File>>,
cx: &'a AppContext,
) -> &'a AllLanguageSettings {
let location = file.map(|f| f.as_ref().into());
let location = file.map(|f| SettingsLocation {
worktree_id: f.worktree_id(cx),
path: f.path().as_ref(),
});
AllLanguageSettings::get(location, cx)
}
@@ -59,7 +53,7 @@ pub struct AllLanguageSettings {
/// The inline completion settings.
pub inline_completions: InlineCompletionSettings,
defaults: LanguageSettings,
languages: HashMap<Arc<str>, LanguageSettings>,
languages: HashMap<LanguageName, LanguageSettings>,
pub(crate) file_types: HashMap<Arc<str>, GlobSet>,
}
@@ -76,10 +70,10 @@ pub struct LanguageSettings {
/// The column at which to soft-wrap lines, for buffers where soft-wrap
/// is enabled.
pub preferred_line_length: u32,
// Whether to show wrap guides (vertical rulers) in the editor.
// Setting this to true will show a guide at the 'preferred_line_length' value
// if softwrap is set to 'preferred_line_length', and will show any
// additional guides as specified by the 'wrap_guides' setting.
/// Whether to show wrap guides (vertical rulers) in the editor.
/// Setting this to true will show a guide at the 'preferred_line_length' value
/// if softwrap is set to 'preferred_line_length', and will show any
/// additional guides as specified by the 'wrap_guides' setting.
pub show_wrap_guides: bool,
/// Character counts at which to show wrap guides (vertical rulers) in the editor.
pub wrap_guides: Vec<usize>,
@@ -127,6 +121,9 @@ pub struct LanguageSettings {
pub linked_edits: bool,
/// Task configuration for this language.
pub tasks: LanguageTaskConfig,
/// Rainbow brackets config
pub rainbow_brackets: bool,
}
impl LanguageSettings {
@@ -210,7 +207,7 @@ pub struct AllLanguageSettingsContent {
pub defaults: LanguageSettingsContent,
/// The settings for individual languages.
#[serde(default)]
pub languages: HashMap<Arc<str>, LanguageSettingsContent>,
pub languages: HashMap<LanguageName, LanguageSettingsContent>,
/// Settings for associating file extensions and filenames
/// with languages.
#[serde(default)]
@@ -351,6 +348,10 @@ pub struct LanguageSettingsContent {
///
/// Default: {}
pub tasks: Option<LanguageTaskConfig>,
/// Task configuration for this language.
///
/// Default: {}
pub rainbow_brackets: Option<bool>,
}
/// The contents of the inline completion settings.
@@ -797,7 +798,7 @@ impl InlayHintSettings {
impl AllLanguageSettings {
/// Returns the [`LanguageSettings`] for the language with the specified name.
pub fn language<'a>(&'a self, language_name: Option<&str>) -> &'a LanguageSettings {
pub fn language<'a>(&'a self, language_name: Option<&LanguageName>) -> &'a LanguageSettings {
if let Some(name) = language_name {
if let Some(overrides) = self.languages.get(name) {
return overrides;
@@ -827,7 +828,7 @@ impl AllLanguageSettings {
}
}
self.language(language.map(|l| l.name()).as_deref())
self.language(language.map(|l| l.name()).as_ref())
.show_inline_completions
}
}

View File

@@ -166,6 +166,7 @@ pub async fn parse_markdown_block(
let mut list_stack = Vec::new();
let mut options = pulldown_cmark::Options::all();
options.remove(pulldown_cmark::Options::ENABLE_DEFINITION_LIST);
options.remove(pulldown_cmark::Options::ENABLE_YAML_STYLE_METADATA_BLOCKS);
for event in Parser::new_ext(markdown, options) {
@@ -384,6 +385,7 @@ public: void format(const int &, const std::tm &, int &dest)
"#;
let mut options = pulldown_cmark::Options::all();
options.remove(pulldown_cmark::Options::ENABLE_DEFINITION_LIST);
options.remove(pulldown_cmark::Options::ENABLE_YAML_STYLE_METADATA_BLOCKS);
let parser = pulldown_cmark::Parser::new_ext(input, options);

View File

@@ -695,7 +695,7 @@ impl Render for ConfigurationView {
)
.child(
Label::new(
"You can also assign the {ANTHROPIC_API_KEY_VAR} environment variable and restart Zed.",
format!("You can also assign the {ANTHROPIC_API_KEY_VAR} environment variable and restart Zed."),
)
.size(LabelSize::Small),
)

View File

@@ -254,11 +254,13 @@ impl LanguageModelProvider for CloudLanguageModelProvider {
}),
AvailableProvider::OpenAi => CloudModel::OpenAi(open_ai::Model::Custom {
name: model.name.clone(),
display_name: model.display_name.clone(),
max_tokens: model.max_tokens,
max_output_tokens: model.max_output_tokens,
}),
AvailableProvider::Google => CloudModel::Google(google_ai::Model::Custom {
name: model.name.clone(),
display_name: model.display_name.clone(),
max_tokens: model.max_tokens,
}),
};

View File

@@ -37,6 +37,7 @@ pub struct GoogleSettings {
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct AvailableModel {
name: String,
display_name: Option<String>,
max_tokens: usize,
}
@@ -170,6 +171,7 @@ impl LanguageModelProvider for GoogleLanguageModelProvider {
model.name.clone(),
google_ai::Model::Custom {
name: model.name.clone(),
display_name: model.display_name.clone(),
max_tokens: model.max_tokens,
},
);

View File

@@ -40,6 +40,7 @@ pub struct OpenAiSettings {
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct AvailableModel {
pub name: String,
pub display_name: Option<String>,
pub max_tokens: usize,
pub max_output_tokens: Option<u32>,
}
@@ -171,6 +172,7 @@ impl LanguageModelProvider for OpenAiLanguageModelProvider {
model.name.clone(),
open_ai::Model::Custom {
name: model.name.clone(),
display_name: model.display_name.clone(),
max_tokens: model.max_tokens,
max_output_tokens: model.max_output_tokens,
},
@@ -489,7 +491,7 @@ impl ConfigurationView {
impl Render for ConfigurationView {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
const OPENAI_CONSOLE_URL: &str = "https://console.anthropic.com/settings/keys";
const OPENAI_CONSOLE_URL: &str = "https://platform.openai.com/api-keys";
const INSTRUCTIONS: [&str; 6] = [
"To use the assistant panel or inline assistant, you need to add your OpenAI API key.",
" - You can create an API key at: ",

View File

@@ -399,7 +399,7 @@ impl LanguageModelRequest {
tool_choice: None,
metadata: None,
stop_sequences: Vec::new(),
temperature: None,
temperature: Some(self.temperature),
top_k: None,
top_p: None,
}

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