Compare commits

...

151 Commits

Author SHA1 Message Date
Will Bradley
1ea0c9634c Merge remote-tracking branch 'origin/main' into look-behind-regex 2024-11-06 09:26:03 -07:00
renovate[bot]
449e20de3d Update Rust crate wasmtime to v24.0.2 [SECURITY] (#20262)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [wasmtime](https://redirect.github.com/bytecodealliance/wasmtime) |
workspace.dependencies | patch | `24.0.1` -> `24.0.2` |

### GitHub Vulnerability Alerts

####
[CVE-2024-51745](https://redirect.github.com/bytecodealliance/wasmtime/security/advisories/GHSA-c2f5-jxjv-2hh8)

### Impact

Wasmtime's filesystem sandbox implementation on Windows blocks access to
special device filenames such as "COM1", "COM2", "LPT0", "LPT1", and so
on, however it did not block access to the special device filenames
which use superscript digits, such as "COM¹", "COM²", "LPT⁰", "LPT¹",
and so on. Untrusted Wasm programs that are given access to any
filesystem directory could bypass the sandbox and access devices through
those special device filenames with superscript digits, and through them
gain access peripheral devices connected to the computer, or network
resources mapped to those devices. This can include modems, printers,
network printers, and any other device connected to a serial or parallel
port, including emulated USB serial ports.

### Patches

Patch releases for Wasmtime have been issued as 24.0.2, 25.0.3, and
26.0.1. Users of Wasmtime 23.0.x and prior are recommended to upgrade to
one of these patched versions.

### Workarounds

There are no known workarounds for this issue. Affected Windows users
are recommended to upgrade.

### References

- [Microsoft's
documentation](https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file#naming-conventions)
of the special device filenames
 - [ISO-8859-1](https://en.wikipedia.org/wiki/ISO/IEC_8859-1)
- [The original PR reporting the
issue](https://redirect.github.com/bytecodealliance/cap-std/pull/371)

---

### Release Notes

<details>
<summary>bytecodealliance/wasmtime (wasmtime)</summary>

###
[`v24.0.2`](https://redirect.github.com/bytecodealliance/wasmtime/releases/tag/v24.0.2)

[Compare
Source](https://redirect.github.com/bytecodealliance/wasmtime/compare/v24.0.1...v24.0.2)

#### 24.0.2

Released 2024-11-05.

##### Fixed

- Update to cap-std 3.4.1, for
[#&#8203;9559](https://redirect.github.com/bytecodealliance/wasmtime/issues/9559),
which fixes a wasi-filesystem sandbox
    escape on Windows.

[CVE-2024-51745](https://redirect.github.com/bytecodealliance/wasmtime/security/advisories/GHSA-c2f5-jxjv-2hh8).

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "" in timezone America/New_York,
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

Release Notes:

- N/A

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOC4xNDIuNyIsInVwZGF0ZWRJblZlciI6IjM4LjE0Mi43IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-06 09:21:50 -07:00
Peter Tripp
1aac35cc1c v0.162.x dev 2024-11-06 11:09:13 -05:00
Peter Tripp
815385cc5f Add autoclosing braces for Shell Scripts (#20278)
Added support for autoclosing braces `{}` and single quotes `''` in Shell Scripts
2024-11-06 11:05:59 -05:00
Lars Diederich
eca3424cb5 Update openSUSE dependencies to build on fresh Tumbleweed installation (#20298) 2024-11-06 11:05:13 -05:00
Danilo Leal
71b3633c1b assistant: Remove automatic diagnostic attachment to tab and file commands (#20297)
This PR returns the `/tab` and `/file` commands to their original
behavior of _not_ automatically including diagnostics. This is an
assistant-only change, though, given that we can already pass the
`/diagnostic` command by itself. The inline assistant will still have
the diagnostics baked in to allow prompts such as "Fix this error."

Release Notes:

- Remove automatic diagnostic attachment to tab and file commands in the
assistant panel

---------

Co-authored-by: Antonio Scandurra <me@as-cii.com>
2024-11-06 11:36:34 -03:00
Antonio Scandurra
21f778c6db Reduce memory footprint for inline transformations (#20296)
Closes https://github.com/zed-industries/zed/issues/18062

This pull request prevents the `scores` matrix for the streaming diff
from growing quadratically.

Previously, we would store rows and columns respectively for all
characters in the old and new text. However, every time we receive a
chunk, we will always advance the position in the matrix to the very
latest character in the new text. This means we can avoid storing scores
for the new characters that were already reported.

Randomized tests still pass and I also made sure that the diffs we
produce are identical.

Release Notes:

- Improved memory footprint for inline transformations
([#18062](https://github.com/zed-industries/zed/issues/18062))
2024-11-06 15:07:16 +01:00
Roshan Padaki
1eb6fb0b5d go: Run ./... tasks against current module (#20190)
In #17108, we updated `go test ./...` to run against the package
directory, to fix cases in which the top-level project is not the go
module root. However, this leads to the confusing behavior of `go test
./...` only running tests in subdirectories of the current package.
Here, we change the behavior to instead walk up the dirtree to find the
closest `go.mod`, and run the `./...` tasks relative to that directory.
This might lead to more predictable behavior for these tasks.

Also see:
https://github.com/zed-industries/zed/pull/19987#issuecomment-2450159099

Release Notes:

- Improved go test and generate `./...` commands to run against the
current go module directory rather than the current package directory
2024-11-06 14:55:57 +01:00
Ilya Sorochan
484e5df2ee Add suggestion for CMake files (#20292)
Release Notes:

- Added NeoCMake extension suggestion for cmake files (`CMakeLists.txt`
and `.cmake`)
2024-11-06 14:51:26 +01:00
Piotr Osiewicz
fef7df667c pane: Update pinned tab counts when unnamed buffer is discarded (#20294)
Closes #19492

Release Notes:

- Fixed a crash that could happen when closing a workspace with pinned
untitled buffers.
2024-11-06 13:58:51 +01:00
Kirill Bulatov
1c84fd1fef Use Zed handler for undo and redo macOS actions (#20293)
Those seem to require a corresponding NSTextView/NSTextField with
explicitly enabled `allowsUndo` property. But Zed does not use any of
these *Text* elements, so there's nothing to allow undo on. Hence, use
the Zed handler, making both actions always enabled instead of being
always disabled.

Closes https://github.com/zed-industries/zed/issues/12335

Release Notes:

- Fixed undo and redo macOS menu items being always disabled
([#12335](https://github.com/zed-industries/zed/issues/12335))
2024-11-06 14:34:46 +02:00
Thorsten Ball
b6adab84a0 rope: Index tab locations for each chunk (#20289)
This is a follow-up to #19913 and adds another "index" to the `Chunk`,
this time indexing the location of tabs.

Release Notes:

- N/A

---------

Co-authored-by: Antonio <antonio@zed.dev>
2024-11-06 13:18:30 +01:00
Danilo Leal
d3a49f6d8f assistant: Rename "new context" to "new chat" (#20043)
This PR is only updating UI strings and pieces of the documentation—it
doesn't touch the actual code, where it's still using things such as
`NewContext` and similar terminology for variables, actions, etc.

Release Notes:

- N/A
2024-11-06 07:24:04 -03:00
Patrick Decat
c2cf4c45c0 Fix path to install-mold script in linux script (#20286)
Release Notes:

- N/A
2024-11-06 12:18:31 +02:00
Thorsten Ball
bd03dea296 git: Add support for opening git worktrees (#20164)
This adds support for [git
worktrees](https://matklad.github.io/2024/07/25/git-worktrees.html). It
fixes the errors that show up (git blame not working) and actually adds
support for detecting git changes in a `.git` folder that's outside of
our path (and not even in the ancestor chain of our root path).

(While working on this we discovered that our `.gitignore` handling is
not 100% correct. For example: we do stop processing `.gitignore` files
once we found a `.git` repository and don't go further up the ancestors,
which is correct, but then we also don't take into account the
`excludesFile` that a user might have configured, see:
https://git-scm.com/docs/gitignore)


Closes https://github.com/zed-industries/zed/issues/19842
Closes https://github.com/zed-industries/zed/issues/4670

Release Notes:

- Added support for git worktrees. Zed can now open git worktrees and
the git status in them is correctly handled.

---------

Co-authored-by: Antonio <antonio@zed.dev>
Co-authored-by: Bennet <bennet@zed.dev>
2024-11-06 09:43:39 +01:00
Danilo Leal
3f777f0c68 Fix project panel losing focus after file creation attempt (#20273)
Closes https://github.com/zed-industries/zed/issues/19771

### Before

When pressing <kbd>esc</kbd> after attempting to create a file, the
focus is lost and you don't know where it went.


https://github.com/user-attachments/assets/2ccd82b7-b7d2-49e4-b1c7-1867331ab9dc

### After

Now, after pressing <kbd>esc</kbd>, the focus returns to where it was
before trying to create a new file.


https://github.com/user-attachments/assets/a8eb1cf1-dfef-42eb-9f3d-2ab6200056c4

Release Notes:

- Fix project panel losing focus after file creation attempt
([#19771](https://github.com/zed-industries/zed/issues/19771))

---------

Co-authored-by: Kirill Bulatov <mail4score@gmail.com>
2024-11-06 09:59:06 +02:00
Will Bradley
4bbdf18280 explicating this idea... 2024-11-05 22:17:53 -07:00
Conrad Irwin
846aec701f Remoting: Fix opening multiple folders on one server (#20281)
Release Notes:

- Remoting: Fix opening multiple folders on one server
2024-11-05 22:16:38 -07:00
Conrad Irwin
cfce6a8fbf Potentially fix invocation of draft-release-notes (#20265)
Release Notes:

- N/A
2024-11-05 21:46:05 -07:00
Conrad Irwin
803e5d4c9f vim: Fix readonly mode (#20264)
Closes #18854

Release Notes:

- vim: Fixed `r`, `~` etc. appearing to modify readonly buffers
2024-11-05 21:45:45 -07:00
AidanV
c10c35ffda Fix duplicate SshProject's in Remote Projects menu (#20271)
Closes #20269

Release Notes:

- Changes SshConnection to use a BTreeSet of SshProject's instead of a
Vec of SshProject's in order to remove duplicate remote projects from
"settings.json" and the Remote Projects menu.
2024-11-05 21:31:57 -07:00
Conrad Irwin
38b1940251 Re-entrant SendKeystrokes (#20277)
Release Notes:

- Improved `workspace::SendKeystrokes` to support re-binding keys. For
example you can now do: `"x": ["workspace::SendKeystrokes", "\" _ x"]`
in vim mode to ensure that `x` does not clobber your clipboard.
- Improved key binding documentation
2024-11-05 21:18:16 -07:00
Will Bradley
83b191cb17 wip 2024-11-05 19:03:29 -07:00
Will Bradley
6cde94d0bc initial idea to start 2024-11-05 19:01:23 -07:00
Max Brunsfeld
50069a2153 Unbind app menu actions (#20268)
Closes #7544

Release Notes:

- Fixed an issue that prevented removing key bindings for actions used
in the macOS application menu.
2024-11-05 16:18:41 -08:00
Kirill Bulatov
b23835b5a5 Disable sccache during dev extension builds (#20270) 2024-11-06 02:01:56 +02:00
Nathan Sobo
e47b305ca7 Use correct context path for focused element in WindowContext::bindings_for_action (#18843)
Previously, we were reaching in and using the context_stack on the dispatch tree, which was incorrect.

/cc @as-cii 
/cc @ConradIrwin

Release Notes:

- N/A

---------

Co-authored-by: Michael Sloan <michael@zed.dev>
2024-11-05 16:42:50 -07:00
Will Bradley
7931342455 Fix typo in Ansible docs (#20267)
fix typo in Ansible docs.

Release Notes:

- N/A
2024-11-05 16:31:39 -07:00
Will Bradley
6823ac624a fix a typo in ansible.md 2024-11-05 16:30:06 -07:00
Kirill Bulatov
282f6241b7 Fix outline panel selection races (#20263)
Follow-up of https://github.com/zed-industries/zed/pull/20211

Release Notes:

- N/A
2024-11-06 00:54:15 +02:00
Conrad Irwin
73bbdd4456 Allow vim macros in visual mode (#20261)
Closes #19764

Release Notes:

- vim: Fixed `q` and `@` in visual mode
2024-11-05 15:52:02 -07:00
Conrad Irwin
236498408f Use zed-style shifted letters (#20254)
Release Notes:

- vim: Fixed some shortcuts to render correctly in Command
2024-11-05 15:51:52 -07:00
Conrad Irwin
86ff6e2c8e vim: Fix paragraphs with softwrap (#20259)
Closes #19778

Release Notes:

- vim: Fixed paragraph object in the presence of softwrap
2024-11-05 15:32:44 -07:00
Conrad Irwin
4bf6fb217e vim: Fix search in the Assistant (#20258)
Closes #17704

Release Notes:

- vim: Fix search in the assistant panel
2024-11-05 15:08:01 -07:00
Mikayla Maki
c527f2e212 Prevent extra line break on long token at start of rewrap (#20256)
Closes #19532

Release Notes:

- Fixed a bug where rewrapping with a long word at the start of the line
would cause a new line to be inserted.

Co-authored-by: Will Bradley <will@zed.dev>
2024-11-05 14:58:07 -07:00
Peter Tripp
47defa2849 docs: Add documentation for configuring clangd in C-only mode (#20255) 2024-11-05 16:43:31 -05:00
Will Bradley
6dfff1b46d Improve rewrap for ideographic writing systems (#20218)
Closes #19733

Before:

https://github.com/user-attachments/assets/5399e8fd-2687-445a-a8ab-023c348aff3f

After:

https://github.com/user-attachments/assets/b4ea5cb6-92ec-49ae-a982-194a1fc68d88

Release Notes:

- improve handling of text wrapping in Rewrap for some ideographic
writing systems

Co-authored-by: Richard <richard@zed.dev>
Co-authored-by: Mikayla <mikayla@zed.dev>
2024-11-05 14:22:21 -07:00
Conrad Irwin
66e06616db Don't write temp files for telemetry logs (#20209)
This still keeps a telemetry.log for the current session, but not one
file per load of zed.

Closes: #20045

Release Notes:

- Fixed a bug where Zed would create a new temporary file on each boot
for telemetry logs
2024-11-05 14:05:51 -07:00
Conrad Irwin
765626a007 Disable /search by default (#20252)
This stops us sending GetCachedEmbeddings requests which frequently time
out
after 10s, and block the collab connection.

Release Notes:

- N/A
2024-11-05 14:05:10 -07:00
Kirill Bulatov
27cdc6cb93 Reuse focused buffer search query in the project search (#20253)
Closes https://github.com/zed-industries/zed/issues/10011

Release Notes:

- Reuse focused buffer search query in the project search
([#10011](https://github.com/zed-industries/zed/issues/10011))
2024-11-05 22:57:08 +02:00
Conrad Irwin
87ba5fd7bc Rebuild SSH installation (#20220)
Closes #ISSUE

This refactors SSH installation to require less shell stuff. We'd like
to
support arbitrary remote hosts, and unfortunately csh/tcsh have quoting
rules
that make it impossible to run multi-line scripts.

The primary changes are:
* The target path now contains the version:
`./zed_server/zed-remote-server-{release_channel}-{version}`
* We do all our processing in a temporary file and `mv` it into place.
* We do fewer calls to `ssh_command` overall. With the previous two
changes we can avoid lock files, and fuser calls. Instead cleanup of old
binaries now happens in `execute_run`.
* We only try to install the remote server when the connection is
established, not on each project open.

This should also put us in a good position if we want to pre-emptively
install new versions when the auto-updater detects an update for the
running version of zed (but that's not wired up yet)

Release Notes:

- Remoting: Fixed remoting when the remote runs `tcsh`
- Remoting: Improved latency of connecting
2024-11-05 13:37:54 -07:00
Kartik Vashistha
7c72929f0b docs: Add Ansible language documentation (#20087)
Co-authored-by: Peter Tripp <peter@zed.dev>
2024-11-05 13:57:15 -05:00
Danilo Leal
9e49894ed6 assistant: Remove the selection action as an extra option (#20234)
This PR should only be merged after
https://github.com/zed-industries/zed/pull/19988. Once the selection
action is added as a "proper" slash command, there's no need to have it
duplicate on the "Add Context" menu anymore. 😄

Release Notes:

- N/A
2024-11-05 15:00:43 -03:00
Tristan Marechaux
2364804f17 assistant: Implement /selection slash command (#19988)
- Closes #18868

## Summary
This PR introduces a new slash command `/selection` to enhance the
usability of the assistant's quote selection feature.

## Changes Made
1. Extracted a function from the `assistant: quote selection` action to
find the selected text and format it as an assistant section.
2. Created a new slash command `/selection` that utilizes the extracted
function to achieve the same effect as the existing `assistant: quote
selection` action.
3. Updated the documentation to include information about the new
`/selection` slash command.


Release Notes:

- Moved the text selection action to a slash command (`/selection`) in
the assistant panel

---------

Co-authored-by: Danilo Leal <67129314+danilo-leal@users.noreply.github.com>
Co-authored-by: Bennet Bo Fenner <bennet@zed.dev>
2024-11-05 18:42:10 +01:00
Peter Tripp
db11a3b554 ci: Update issue templates. Rename defect to bug and improve log formatting (#20246) 2024-11-05 12:07:17 -05:00
Danilo Leal
d0ca49f0e1 Make project search inputs full width (#20242)
Closes https://github.com/zed-industries/zed/issues/13099

This PR main thing is making the inputs in the project search full
width, but it also has some slight design and UI code improvements here
and there, such as extracting the common input styles to its own
variable.

I figure that the reason why the inputs weren't full width before is
just because it'd be hard to reach for the buttons when in a large
monitor with the app maximised _and_ with a single tab open. However, I
do feel like it's common not to have these conditions in place, too,
which make the small inputs too small, like the issue states. At the
very least, we also have the keybindings.

Here's the final result:

| Small window size | Big window size |
|--------|--------|
| <img width="1279" alt="Screenshot 2024-11-05 at 11 18 08"
src="https://github.com/user-attachments/assets/73548300-1ad2-4ed0-b99f-adb3212ac163">
| <img width="2992" alt="Screenshot 2024-11-05 at 11 24 06"
src="https://github.com/user-attachments/assets/3a1ccabd-2350-42f0-8e31-112f27da98a4">
|

Release Notes:

- N/A
2024-11-05 13:04:42 -03:00
Vladimir Varankin
d6fcd9853a keymaps: Fix how single line editor handles new lines in BufferSearch (#19316)
Updates JetBrains keymap to handle the `shift-enter` as the `SelectPrevMatch` action.
2024-11-05 11:00:26 -05:00
Alex Viscreanu
bc3550d991 workspace: Add settings to dim inactive panes and highlight active pane (#18968)
Closes #12529
Closes #8639

Release Notes:

- Added option to dim inactive panes
([#12529](https://github.com/zed-industries/zed/issues/12529))
- Added option to highlight active pane with a border
([#8639](https://github.com/zed-industries/zed/issues/8639))

BREAKING: `active_pane_magnification` value is no longer used, it should
be migrated to `active_pane_modifiers.magnification`


![panes](https://github.com/user-attachments/assets/b19959bc-4c06-4320-be36-412113143af5)

> note: don't know much rust, so I wouldn't be surprised if stuff can be
done much better, happy to update things after the review.
Also, wasn't sure about introducing the new object in the settings, but
it felt better than adding two more keys to the root, let me know what
you think and if there's a better way to do this. Also happy to get
feedback on the text itself, as I didn't spend much thinking how to
document this.
2024-11-05 17:26:07 +02:00
1dNDN
b8501199c2 Fix name of vcpp libs in windows build guide (#20240)
It is not obvious that you need to choose the latest one from the same
libraries, especially in non-English locales

Release Notes:

- N/A
2024-11-05 16:00:22 +02:00
Thorsten Ball
7fb9549098 vim: Fix d shift-g not deleting until EOD if soft-wrap is on (#20160)
This previously didn't work: `d G` would delete to the end of the "first
of the soft-wrapped lines" of the last line.

To fix it, we special case the delete behavior for `shift-g`, which is
what Neovim also seems to do.


Release Notes:

- Fixed `d G` in Vim mode not deleting until the actual end of the
document if soft-wrap is turned on.
2024-11-05 14:47:14 +01:00
Piotr Osiewicz
4097118070 ui: Fix scrollbar content size calculation for non-uniform lists with single element (#20237)
Previously we were always adding the origin coordinate of last item to
the content size, which is incorrect when the list has just one item; in
that case, we should just use the size of that item as the content size
of a list.

Closes #ISSUE

Release Notes:

- N/A
2024-11-05 14:32:13 +01:00
Stanislav Alekseev
a26c0a8537 Fix toolchain detection for worktree-local paths (#20229)
Reimplements `pet::EnvironmentApi`, trying to access the `project_env`
first
Closes #20177 

Release Notes:

- Fixed python toolchain detection when worktree local path is set
2024-11-05 14:25:18 +01:00
Stephan Aßmus
f8bd6c66f4 Fix cursor shape flickering and dead-zone on 1px border around list items in project and outline panels (#20202)
Move click listener to outer div
- Avoids dead area when clicking the 1px border around a list item
- Avoids flickering cursor shape when moving the cursor above the list,
and especially when scrolling the list with a stationary cursor.

Closes #15614

Release Notes:

- Fixed mouse cursor shape flickering in project and outline panels when
crossing items
([#15614](https://github.com/zed-industries/zed/issues/15614))

---------

Co-authored-by: Stephan Aßmus <stephan.assmus@sap.com>
2024-11-05 14:26:20 +02:00
Danilo Leal
02b1e3a3c1 assistant: Adjust the toolbar design (#20101)
This PR's most relevant change is removing the three-dot menu dropdown
from the assistant toolbar. The "Regenerate Title" button is now only
visible on hover and it appears on the far right of the title input.

<img width="700" alt="Screenshot 2024-11-04 at 13 31 37"
src="https://github.com/user-attachments/assets/891703af-7985-4b16-bb5e-d852491abd6f">

Release Notes:

- N/A
2024-11-05 09:07:26 -03:00
Alvaro Parker
83ad28dfe7 Remove duplicate load_system_fontscall (#19374)
Related comment on issue
https://github.com/zed-industries/zed/issues/14222#issuecomment-2418375056

On `crates/gpui/src/platform/linux/text_system.rs` on method
`CosmicTextSystem::new` `load_system_fonts` is being called twice:
```rust
    pub(crate) fn new() -> Self {
        let mut font_system = FontSystem::new();

        // todo(linux) make font loading non-blocking
        font_system.db_mut().load_system_fonts();

        Self(RwLock::new(CosmicTextSystemState {
            font_system,
            swash_cache: SwashCache::new(),
            scratch: ShapeBuffer::default(),
            loaded_fonts_store: Vec::new(),
            font_ids_by_family_cache: HashMap::default(),
            postscript_names: HashMap::default(),
        }))
    }
```

First one on `FontSystem::new()` and second one is explicit on
`font_system.db_mut().load_system_fonts()`. The first call
`FontSystem::new()` is defined as:
```
    pub fn new() -> Self {
        Self::new_with_fonts(core::iter::empty())
    }
```
And `new_with_fonts`: 
```rust
    /// Create a new [`FontSystem`] with a pre-specified set of fonts.
    pub fn new_with_fonts(fonts: impl IntoIterator<Item = fontdb::Source>) -> Self {
        let locale = Self::get_locale();
        log::debug!("Locale: {}", locale);

        let mut db = fontdb::Database::new();

        //TODO: configurable default fonts
        db.set_monospace_family("Fira Mono");
        db.set_sans_serif_family("Fira Sans");
        db.set_serif_family("DejaVu Serif");

        Self::load_fonts(&mut db, fonts.into_iter());

        Self::new_with_locale_and_db(locale, db)
    }
```
Finally `Self::load_fonts(&mut db, fonts.into_iter())` calls
`load_system_fonts`:
```rust
    #[cfg(feature = "std")]
    fn load_fonts(db: &mut fontdb::Database, fonts: impl Iterator<Item = fontdb::Source>) {
        #[cfg(not(target_arch = "wasm32"))]
        let now = std::time::Instant::now();

        db.load_system_fonts();

        for source in fonts {
            db.load_font_source(source);
        }
        ...
```

Release Notes:

- Remove duplicate font loading on Linux
2024-11-05 12:43:22 +02:00
Conrad Irwin
17b9d1976f Fix buffer restoration on ssh projects (#20215)
Closes #20143

Release Notes:

- Remoting: Fixed a panic restoring unsaved untitled buffers over SSH
2024-11-04 23:05:52 -07:00
Kirill Bulatov
3856599853 Improve project search performance (#20211)
Follow-up of https://github.com/zed-industries/zed/pull/20171

Reduces time Zed needs to reach maximum search results by an order of a
magnitude.

Methodology: 
* plugged-in mac with Instruments and Zed open
* Zed is restarted before each measurement, `zed` project is opened, a
*.rs file is opened and rust-analyzer is fully loaded, file is closed
then
* from an "empty" state, a `test` word is searched in the project search
* each version is checked with project panel; and then, separately,
without it
* after we reach maximum test results (the counter stops at `10191+`),
the measurement stops

Zed Dev is compiled and installed with `./script/bundle-mac -li`

------------------------


[measurements.trace.zip](https://github.com/user-attachments/files/17625516/measurements.trace.zip)

Before:

* Zed Nightly with outline panel open

<img width="1113" alt="image"
src="https://github.com/user-attachments/assets/62b29a69-c266-4d46-8c3c-0e9534ca7967">

Took over 30s to load the result set

* Zed Nightly without outline panel

<img width="1109" alt="image"
src="https://github.com/user-attachments/assets/82d8d9d6-e8f2-4e67-af55-3f54a7c1d92d">

Took over 24s to load the result set

* Zed Dev with outline panel open

<img width="1131" alt="image"
src="https://github.com/user-attachments/assets/15605ff8-0787-428e-bbb6-f8496f7e1d43">

Took around 6s to load the result set (the profile was running a bit
longer)

* Zed Dev without outline panel

<img width="1147" alt="image"
src="https://github.com/user-attachments/assets/0715d73e-f41a-4d74-a604-a3a96ad8d585">

Took around 5s to load the result set

---------------------

Improvements in the outline panel:

* https://github.com/zed-industries/zed/pull/20171 ensured we reuse
previous rendered search results from the outline panel
* all search results are now rendered in the background thread
* only the entries that are rendered with gpui are sent to the
background thread for rendering
* FS entries' update logic does nothing before the debounce now

Improvements in the editor:

* cursor update operations are debounced and all calculations start
after the debounce only
* linked edits are now debounced and all work is done after the debounce
only

Further possible improvements:

* we could batch calculations of text coordinates, related to the search
entries: right now, each search match range is expanded around and
clipped, then fitted to the closest surrounding whitespace (if any,
otherwise it's just trimmed).
Each such calculation requires multiple tree traversals, which is
suboptimal and causes more CPU usage than we could use.

* linked edits are always calculated, even if the language settings have
it disabled, or the corresponding language having no corresponding
capabilities

Release Notes:

- Improve large project search performance
2024-11-05 03:49:37 +02:00
Ganesh Gupta
81dd4ca1c9 Fix a typo (#20210)
Release Notes:

- N/A
2024-11-05 02:02:50 +02:00
Piotr Osiewicz
2965119201 project panel: scroll when drag-hovering over the edge of a list (#20207)
Closes [19554](https://github.com/zed-industries/zed/issues/19554)

Release Notes:

- Added auto-scrolling to project panel when a vertical edge of a panel
is hovered with a dragged entry.
2024-11-05 00:56:58 +01:00
Max Brunsfeld
258cf6c746 Add inclusive range scope overrides. Don't auto-close quotes at the ends of line comments (#20206)
Closes #9195
Closes #19787

Release Notes:

- Fixed an issue where single quotation marks were spuriously
auto-closed when typing in line comments
2024-11-04 15:36:39 -08:00
Richard Feldman
369de400be Make rewrapping take tabs more into account (#20196)
Closes #18686


https://github.com/user-attachments/assets/e87b4508-3570-4395-92b4-c5e0e9e19623


Release Notes:

- The Rewrap command now considers the width of each tab character at
the beginning of the line to be the configured tab size.

---------

Co-authored-by: Will <will@zed.dev>
2024-11-04 18:10:40 -05:00
Piotr Osiewicz
dc02894db4 editor: Add scrollbar to info popovers (#20184)
Related to https://github.com/zed-industries/zed/issues/5034

Release Notes:

- Added scrollbar to info popovers in editor.
2024-11-05 00:08:38 +01:00
Patrick Sy
966b18e142 assistant: Fix Gemini 1.5 Pro throwing "missing field 'index' at line N column M" (#20200)
Closes https://github.com/zed-industries/zed/issues/20033

- Fixed deserialization error of `GenerateContentCandidate` where `index` is unexpectedly nil
2024-11-04 17:01:08 -05:00
Elias Müller
2c7e71028d Fix selection extend/shrink on JetBrains keymap (#20199)
JetBrains IDE's use `ctrl-w` and `ctrl-shift-w` on Win/Linux and
`cmd-up` and `cmd-down` on mac to extend/shrink selections.

https://www.jetbrains.com/guide/java/tips/extend-selection/

Release Notes:

- Fixed extend/shrink selection on JetBrains keymap
2024-11-04 16:35:52 -05:00
Kirill Bulatov
24dba07a9b Do not alter soft wrap based on .editorconfig contents (#20198)
Closes https://github.com/zed-industries/zed/issues/20194

Release Notes:

- Fixed Zed incorrectly changing soft wrap based on .editorconfig
contents ([#20194](https://github.com/zed-industries/zed/issues/20194))
2024-11-04 23:31:40 +02:00
Marshall Bowers
16e9b4ceeb typescript: Improve installation checks for vtsls (#20197)
This PR improves the installation checks for `vtsls`.

Previously we were checking the installed version of TypeScript against
the latest available version to determine whether we needed to installed
the `vtsls` language server or not.

However, these are two independent concerns, so we should be checking
individually whether `typescript` or `@vtsls/language-server` need to be
installed/updated.

Closes https://github.com/zed-industries/zed/issues/18349.

Release Notes:

- typescript: Fixed an issue where `@vtsls/language-server` may not have
been updated to the latest version.
2024-11-04 16:00:51 -05:00
Richard Feldman
bc4bd2e168 Don't conservatively include Suggest Edits token in token count (#20180)
Before: (note the 1.3k in the upper right corner instead of 3 in the
second screenshot)

<img width="459" alt="Screenshot 2024-11-04 at 11 37 58 AM"
src="https://github.com/user-attachments/assets/64c06aff-f7d2-42a4-a767-0d7a4ba0f486">

Now:
<img width="631" alt="Screenshot 2024-11-04 at 11 38 11 AM"
src="https://github.com/user-attachments/assets/22af974d-915a-41e1-9ee0-f0622901e242">

This was intended to be a conservative estimate in case you pressed
Suggest Edits (and therefore might have an unpleasant surprise if you
were under the context limit but Suggest Edits failed with a "too much
context" error message anyway, after the Suggest Edits context got added
for you behind the scenes).

However, in retrospect this design created more [confusion in the common
case](https://github.com/zed-industries/zed/pull/19900#issuecomment-2453456569)
because it made it look like more context had been actually consumed
than what was actually consumed.

This does raise a potential design question for the future: the Suggest
Edits button adds more context at the last minute without ever
communicating that it's going to do that.

In the meantime it seems best to go back to the less-confusing way of
reporting the token counts, especially since only users of the
experimental flag could possibly press Suggest Edits anyway.

Release Notes:

- Fixed issue where initial token count was over-reported as 1.3k
instead of 3 (for the context string "You").
2024-11-04 15:40:10 -05:00
Max Brunsfeld
4d3a18cbdc Fix two auto-indent issues with Markdown and YAML (#20193)
Closes #13376
Closes #13338

Release Notes:

- Fixed unhelpful auto-indent suggestions in markdown.
- Added `auto_indent_on_paste` setting, which can be used on a
per-language basis, to configure whether indentation should be adjusted
when pasting. This setting is enabled by default for languages other
than YAML and Markdown.
2024-11-04 12:29:38 -08:00
Conrad Irwin
cfcbfc1d82 Fix saving files as *.sql on macOS Sequoia (#20195)
Closes #16969

Release Notes:

- Fixed a bug in macOS Sequoia where you can't save a new file as
`*.sql`, it would rename to `.sql.s`. As a side effect you can no longer
save a new file as `*sql.s`. We hope to remove this workaround when the
operating system fixes its bug; in the meantime you can either set
`"use_system_path_prompts": false` in your settings file to skip the
macOS dialogues, or create new files by right clicking in the project
panel.
2024-11-04 13:23:59 -07:00
Avinash Thakur
c9ec235b12 Sort completions by relevance for strong matches (#20145)
Further enhancement:
On exploring VSCode's sorting logic, there are two major distinctions:
* A config option exists to adjust sort priority of snippets. They can
be placed inline (default), top or at bottom of completitions.
* The sorting order sorts by (in order): sort_text (lower case),
sort_text, kind

ref:
6f2d4781e8/src/vs/editor/contrib/suggest/browser/suggest.ts (L338-L383)

Closes #19786

Release Notes:

- Improved sort order in completions to show relevant matches first
([#19786](https://github.com/zed-industries/zed/issues/19786))
2024-11-04 22:23:36 +02:00
Marshall Bowers
8196db6022 settings: Include null in the type for optional settings (#20192)
This PR updates all settings that are defined as `Option`s to include
`null` in their type.

This prevents warnings from being displayed when `null` is used a
default value.

Closes https://github.com/zed-industries/zed/issues/18006.

Release Notes:

- Updated the settings schema to allow `null` as a value for optional
settings instead of showing a warning.
2024-11-04 14:25:44 -05:00
Piotr Osiewicz
dc5fad52a3 diagnostics: Improve performance with large # of diagnostics (#20189)
Related to: https://github.com/zed-industries/zed/issues/19022

Release Notes:

- Improve editor performance with large # of diagnostics.

---------

Co-authored-by: Conrad <conrad@zed.dev>
Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2024-11-04 12:16:02 -07:00
Marshall Bowers
77de20c23a settings: Allow null as a value for font fallback fields (#20186)
This PR updates the `buffer_font_fallbacks` and `ui_font_fallbacks`
settings to allow `null` as a value instead of showing a warning.

Related to https://github.com/zed-industries/zed/issues/18006.

Release Notes:

- Updated the settings schema to allow `null` as a value for
`buffer_font_fallbacks` and `ui_font_fallbacks` instead of showing a
warning.
2024-11-04 12:58:10 -05:00
Nate Butler
e1cb8a66f0 Add pages to theme_preview (#20185)
Added some simple logic + an example of adding pages to the theme
preview. Will be used for organizing theme preview sections.

Release Notes:

- N/A
2024-11-04 12:57:36 -05:00
Marshall Bowers
7025d3f29d Extract outline rendering to outline crate (#20179)
This PR extracts the `render_item` implementation for outlines to the
`outline` crate to help reduce `language`'s dependence on `theme`.

Release Notes:

- N/A
2024-11-04 11:54:37 -05:00
Bennet Bo Fenner
4bbde40267 recent projects: Fix inconsistent keybinding with which window is replaced or not (#20176)
This removes the inconsistency between these two workflows:
- Clicking on the project name in the upper left shows:
<img width="333" alt="image"
src="https://github.com/user-attachments/assets/f54c34b4-67ab-4cbc-85ee-845c41aa4a8c">

- Using `projects: Open recent` shows:
<img width="337" alt="image"
src="https://github.com/user-attachments/assets/b1eb244f-ce28-4e6c-8404-b6cd88caef1e">

---

We now use `enter` to re-use the window and `cmd-enter` to open a new
window in both cases

Closes #16361

Release Notes:

- Fixed an inconsistency in the recent project picker, where different
keybindings would determine whether to reuse the window or not
2024-11-04 17:41:25 +01:00
Bennet Bo Fenner
15e7b67559 markdown preview: Refresh preview when file is changed outside of the editor (#20175)
Closes #20091

Release Notes:

- Fixed an issue where the markdown preview would not update when a
markdown file was changed outside of the editor
2024-11-04 17:23:32 +01:00
Mike Lloyd
f0aeab7d00 Add surround aliases (#20104)
Closes #19417

Release Notes:

- vim : Added `r` and `a` as aliases for `[` and `<` text objects
(copying vim-surround).
- vim: (breaking change) rebound the function argument text object to
`g`.
- vim: Fixed surrounds to allow `b`/`B`/`r`/`a` anywhere you could use
`(`, `{`, `[`, `<`.

---


- vim: Added `b`, `B`, `r`, `s`, `a` as aliases for `()`, `{}`, `[]`,
`<>` in vim surround mode.
- Adds a new `surround_alias` function where aliases are defined.
- This function is used in `find_surround_pairs` to substitute the
chosen text with the alias
- The keymap is also modified to add support for Square and Angle
brackets when changing surrounds. These two were added to follow the
example of Tim Pope's ubiquitous `vim-surround` plugin.
- I had to overwrite the `vim::Argument` keybind in order to do this. I
moved it to use the `g` modifier. I realize this is a breaking change
and will happily move the `vim::AngleBracket` keymap to a different
letter if you'd like to avoid this. I'm just trying to keep with
convention. Ideally, Users would be able to define surround aliases
themselves in the config file but that's a much bigger task than I'm
able to do right now.
- I also added tests for the new aliases.

Thanks for making such a clean and organized codebase. I was able to
find the relevant section of code rather quickly thanks to this.
2024-11-04 09:03:27 -07:00
bangbangsheshotmedown
4bbddcad31 extension: Add support for labelDetails for LSP completions (#20144)
Closes #14278


be7336e92a/src/completion.rs (L419-L429)


be7336e92a/src/completion.rs (L555-L572)


Release Notes:

- N/A

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-11-04 11:02:19 -05:00
Pablo Portas López
1e944a51ff Delete a.py (#20173)
Release Notes:

- N/A
2024-11-04 10:41:44 -05:00
Kirill Bulatov
d90770c673 Actually reuse previous search entries (#20171)
Release Notes:

- Improved outline panel performance during large project searches
2024-11-04 17:21:09 +02:00
Peter Tripp
6316151a83 docs: Add redirect for /conversations (#20170)
Release Notes:

- N/A
2024-11-04 09:56:07 -05:00
Piotr Osiewicz
49a0a11c4f chore: Remove toolchain section from language settings (#20168)
Closes #ISSUE

Release Notes:

- N/A
2024-11-04 14:13:20 +01:00
Danilo Leal
376a45528d assistant: Improve role button loading state (#20125)
We've received feedback that it wasn't clear how to cancel/interrupt the
LLM while it's generating a response. Additionally, I also had folks
telling me that the loading state was hard to notice—the pulsating
animation is too subtle on its own. This PR attempts to improve both of
these things. The end result is:


![llm](https://github.com/user-attachments/assets/57a94f8a-c254-4011-adc0-7c63ed13daa1)

Release Notes:

- N/A
2024-11-04 10:12:27 -03:00
Jason Lee
20eeb78251 chore: Update BranchListDelegate to use WeakView<Workspace> (#20157) 2024-11-04 12:17:11 +02:00
Jonathan Toledo
67be6ec3b5 copilot: Add support for new models (#19968)
Closes #19963

This PR implements integration with the newly announced GitHub Copilot
LLM models, including:
- Claude 3.5 Sonnet
- o1-mini
- o1-preview

Release Notes:

- N/A

---------

Co-authored-by: Bennet Bo Fenner <bennet@zed.dev>
2024-11-04 10:55:20 +01:00
Lilith Iris
070e5914c9 markdown renderer: Add copy icon button for code block (#19312)
Closes #19061

I don't know should i add tooltip or not

Before

![image](https://github.com/user-attachments/assets/0729806b-9b0d-442a-8f71-92c0443f34ef)

After

![image](https://github.com/user-attachments/assets/08eb5178-4139-44e7-a23f-50133233911d)

Release Notes:

- Markdown Preview: Added button to copy code blocks.

---------

Co-authored-by: Bennet Bo Fenner <bennet@zed.dev>
2024-11-04 10:40:46 +01:00
Kirill Bulatov
c41a8e33a0 Fix file reloading not populating the history (#20156)
Closes https://github.com/zed-industries/zed/issues/20111
Closes https://github.com/zed-industries/zed/issues/20153

cc @mikayla-maki and @ConradIrwin 

Release Notes:

- Fixed undo stack corruption on external file changes
([#20111](https://github.com/zed-industries/zed/issues/20111))
([#20153](https://github.com/zed-industries/zed/issues/20153))

Co-authored-by: Antonio Scandurra <antonio@zed.dev>
2024-11-04 11:30:15 +02:00
Bennet Bo Fenner
25443a91ca image viewer: Show path in breadcrumbs (#20155)
Closes #10057

<img width="354" alt="image"
src="https://github.com/user-attachments/assets/47afe8fd-c8ac-45af-be9a-9ca8c5e066f6">

Release Notes:

- Show path in breadcrumbs/toolbar when opening an image
2024-11-04 10:18:21 +01:00
Roshan Padaki
8e00caf23b go: Add go-generate runnables and tasks (#19987)
I was missing the `go generate` runnable from other editors so I figured
I'd implement one here! Now, comments of the form `//go:generate` can
prompt for the `go generate <package>` task. Meanwhile, I've also added
a global `go generate ./...` task.

~When making the global task, I noticed that the existing `go test
./...` task runs tests in subdirectories of the CWD of the active
editor, whereas I would really expect it to run all tests across my
project. I have changed to use the latter behavior (run relative to
project root) for both `go generate ./...` and `go test ./...`. Please
let me know if the prior behavior was intended, and I can revert.~

Release Notes:

- Added runnable and tasks for `go generate` commands
2024-11-04 10:13:15 +01:00
Bennet Bo Fenner
9a869f0c5f project panel: Focus editor when single-clicking on file and preview tabs are disabled (#20154)
Closes #4324

Release Notes:

- Fixed an issue where single-clicking on a file in the project panel
would not focus the editor, when preview tabs are disabled
2024-11-04 10:02:55 +01:00
Marc
de2483e132 terminal: Do not show multibuffer hint when in centered pane (#20137)
Co-Authored-by: Bennet <bennet@zed.dev>


![image](https://github.com/user-attachments/assets/581f493e-aa9b-4767-8029-6ab83755336b)

Release Notes:

- Fixed an issue where the multibuffer hint was shown when terminal was
in centered mode

Co-authored-by: Bennet <bennet@zed.dev>
2024-11-04 09:19:48 +01:00
Michael Sloan
95259bf9fe Add michael@zed.dev to .mailmap (#20119)
Release Notes:

- N/A
2024-11-04 09:17:53 +02:00
Henry Barreto
3b76ba6d5a docs: Align soft_wrap reference with naming pattern used (#20080)
Release Notes:

- N/A
2024-11-02 20:29:56 +02:00
Richard Feldman
773a3b335e Expand selections to Replace block boundaries (#20092)
Release Notes:

- N/A

---------

Co-authored-by: Antonio Scandurra <me@as-cii.com>
Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
Co-authored-by: Antonio <antonio@zed.dev>
Co-authored-by: Max <max@zed.dev>
2024-11-01 16:10:19 -04:00
Conrad Irwin
b5c38e9a09 Remoting: Fix connecting to servers with long hostnames (#20093)
Closes #20018

Release Notes:

- Remoting: Fixed connecting to hosts with long (>~50 character)
hostnames
2024-11-01 13:52:21 -06:00
Marshall Bowers
273173ec8a Revert "theme: Turn ThemeRegistry into a trait (#20076)" (#20094)
This PR reverts #20076 to turn the `ThemeRegistry` back into a regular
struct again.

It doesn't actually help us by having it behind a trait.

Release Notes:

- N/A
2024-11-01 15:34:20 -04:00
Conrad Irwin
4084ba36f9 Fix clang popovers (#20090)
Closes  #15498

Release Notes:

- Fixed info popups from clangd missing information
2024-11-01 13:28:34 -06:00
Marshall Bowers
770886880f Add new extension crate (#20089)
This PR adds a new `extension` crate, containing some contents extracted
from the `extension_host`.

Right now it contains just the `ExtensionManifest` and
`ExtensionBuilder`, although we may move more of the extension interface
into here.

The introduction of the `extension` crate allows us to depend on it in
the `extension_cli`, thereby eliminating the need for the `no-webrtc`
feature on a number of crates.

Release Notes:

- N/A
2024-11-01 13:20:30 -04:00
Marshall Bowers
ea44c510a3 Rename extension crate to extension_host (#20081)
This PR renames the `extension` crate to `extension_host`.

This is to free up the name so that we can create a smaller-scoped
`extension` crate.

Release Notes:

- N/A
2024-11-01 12:53:02 -04:00
Kirill Bulatov
c8f1969916 Fix the outline panel's focus tracking (#20083)
Closes https://github.com/zed-industries/zed/issues/20073

Release Notes:

- Fixed outline panel navigation
([https://github.com/zed-industries/zed/issues/20073](#20073))

Co-authored-by: Mikayla Maki <mikayla@zed.dev>
2024-11-01 18:01:10 +02:00
Marshall Bowers
a960344301 theme: Remove unused staff parameter for listing themes (#20077)
This PR removes the `staff` parameter for listing themes, as it was not
used.

Release Notes:

- N/A
2024-11-01 10:54:21 -04:00
Marshall Bowers
af9e7f1f96 theme: Turn ThemeRegistry into a trait (#20076)
This PR converts the `ThemeRegistry` type into a trait instead of a
concrete implementation.

This allows for the extension store to depend on an abstraction rather
than the concrete theme registry implementation.

We currently have two `ThemeRegistry` implementations:

- `RealThemeRegistry` — this was previously the `ThemeRegistry` and
contains the real implementation of the registry.
- `VoidThemeRegistry` — a null object that doesn't have any behavior.

Release Notes:

- N/A
2024-11-01 10:19:09 -04:00
renovate[bot]
c04c439d23 Update Rust crate async-compression to v0.4.17 (#19319)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[async-compression](https://redirect.github.com/Nullus157/async-compression)
| workspace.dependencies | patch | `0.4.13` -> `0.4.17` |

---

### Release Notes

<details>
<summary>Nullus157/async-compression (async-compression)</summary>

###
[`v0.4.17`](https://redirect.github.com/Nullus157/async-compression/blob/HEAD/CHANGELOG.md#0417---2024-10-20)

[Compare
Source](https://redirect.github.com/Nullus157/async-compression/compare/v0.4.16...v0.4.17)

##### Fixed

-   Fix occasional panics when consuming from pending buffers.

###
[`v0.4.16`](https://redirect.github.com/Nullus157/async-compression/blob/HEAD/CHANGELOG.md#0416---2024-10-16)

[Compare
Source](https://redirect.github.com/Nullus157/async-compression/compare/v0.4.15...v0.4.16)

##### Other

- Implement pass-through `AsyncBufRead` on write-based encoders &
decoders.

###
[`v0.4.15`](https://redirect.github.com/Nullus157/async-compression/blob/HEAD/CHANGELOG.md#0415---2024-10-13)

[Compare
Source](https://redirect.github.com/Nullus157/async-compression/compare/v0.4.14...v0.4.15)

##### Feature

- Implement pass-through `AsyncRead` or `AsyncWrite` where appropriate.
- Relax `AsyncRead`/`AsyncWrite` bounds on `*::{get_ref, get_mut,
get_pin_mut, into_inner}()` methods.

###
[`v0.4.14`](https://redirect.github.com/Nullus157/async-compression/blob/HEAD/CHANGELOG.md#0414---2024-10-10)

[Compare
Source](https://redirect.github.com/Nullus157/async-compression/compare/v0.4.13...v0.4.14)

##### Fixed

- In Tokio-based decoders, attempt to decode from internal state even if
nothing was read.

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "after 3pm on Wednesday" in timezone
America/New_York, Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

Release Notes:

- N/A

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOC4xMjAuMSIsInVwZGF0ZWRJblZlciI6IjM4LjEyMC4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-01 16:11:16 +02:00
renovate[bot]
d3cd8f8f14 Update Rust crate proc-macro2 to v1.0.89 (#19326)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [proc-macro2](https://redirect.github.com/dtolnay/proc-macro2) |
dependencies | patch | `1.0.87` -> `1.0.89` |

---

### Release Notes

<details>
<summary>dtolnay/proc-macro2 (proc-macro2)</summary>

###
[`v1.0.89`](https://redirect.github.com/dtolnay/proc-macro2/releases/tag/1.0.89)

[Compare
Source](https://redirect.github.com/dtolnay/proc-macro2/compare/1.0.88...1.0.89)

- Ensure OUT_DIR is left with deterministic contents after build script
execution
([#&#8203;474](https://redirect.github.com/dtolnay/proc-macro2/issues/474))

###
[`v1.0.88`](https://redirect.github.com/dtolnay/proc-macro2/releases/tag/1.0.88)

[Compare
Source](https://redirect.github.com/dtolnay/proc-macro2/compare/1.0.87...1.0.88)

- Return accurate line and column from `Span::start` and `Span::end`
inside proc macros on nightly
([#&#8203;472](https://redirect.github.com/dtolnay/proc-macro2/issues/472))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "after 3pm on Wednesday" in timezone
America/New_York, Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

Release Notes:

- N/A

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOC4xMjAuMSIsInVwZGF0ZWRJblZlciI6IjM4LjEyMC4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-01 16:10:31 +02:00
renovate[bot]
cd8d776fe1 Update Rust crate profiling to v1.0.16 (#19334)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [profiling](https://redirect.github.com/aclysma/profiling) |
workspace.dependencies | patch | `1.0.15` -> `1.0.16` |

---

### Release Notes

<details>
<summary>aclysma/profiling (profiling)</summary>

###
[`v1.0.16`](https://redirect.github.com/aclysma/profiling/blob/HEAD/CHANGELOG.md#1016)

[Compare
Source](https://redirect.github.com/aclysma/profiling/compare/v1.0.15...v1.0.16)

-   Address warnings from upstream rustc changes
-   Update puffin to 0.19.1
-   Update tracing-tracy to 0.11.3 and tracing-subscriber to 0.3
-   Implement finish_frame! for tracing
-   Add fuction_scope!() as an alternative to the function proc macro
- Avoid local variable names that don't start with an underscore
introduced into a function's namespace

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "after 3pm on Wednesday" in timezone
America/New_York, Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

Release Notes:

- N/A

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOC4xMjAuMSIsInVwZGF0ZWRJblZlciI6IjM4LjEyMC4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-01 16:10:21 +02:00
renovate[bot]
6de2330253 Update Rust crate pathdiff to v0.2.2 (#19325)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [pathdiff](https://redirect.github.com/Manishearth/pathdiff) |
workspace.dependencies | patch | `0.2.1` -> `0.2.2` |

---

### Configuration

📅 **Schedule**: Branch creation - "after 3pm on Wednesday" in timezone
America/New_York, Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

Release Notes:

- N/A

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOC4xMjAuMSIsInVwZGF0ZWRJblZlciI6IjM4LjEyMC4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-01 16:10:09 +02:00
renovate[bot]
0e264b5a68 Update cloudflare/wrangler-action digest to b2a0191 (#19645)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[cloudflare/wrangler-action](https://redirect.github.com/cloudflare/wrangler-action)
| action | digest | `9681c29` -> `b2a0191` |

---

### Configuration

📅 **Schedule**: Branch creation - "after 3pm on Wednesday" in timezone
America/New_York, Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

Release Notes:

- N/A

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOC4xMjAuMSIsInVwZGF0ZWRJblZlciI6IjM4LjEyMC4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-01 16:07:36 +02:00
renovate[bot]
1af5304074 Update Rust crate flume to v0.11.1 (#19641)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [flume](https://redirect.github.com/zesterer/flume) | dependencies |
patch | `0.11.0` -> `0.11.1` |

---

### Release Notes

<details>
<summary>zesterer/flume (flume)</summary>

###
[`v0.11.1`](https://redirect.github.com/zesterer/flume/blob/HEAD/CHANGELOG.md#0111---2024-10-19)

##### Added

-   `SendSink::sender`
- `SendFut`, `SendSink`, `RecvFut`, `RecvStream`, `WeakSender`, `Iter`,
`TryIter`, and `IntoIter` now implement `Debug`
-   Docs now show required features

##### Removed

##### Changed

-   `WeakSender` is now `Clone`
- `spin` feature no longer uses `std::thread::sleep` for locking except
on Unix-like operating systems and Windows
- Flume is now in [casual maintenance
mode](https://casuallymaintained.tech/).

##### Fixed

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "after 3pm on Wednesday" in timezone
America/New_York, Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

Release Notes:

- N/A

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOC4xMjAuMSIsInVwZGF0ZWRJblZlciI6IjM4LjEyMC4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-01 16:06:24 +02:00
renovate[bot]
dde692eb88 Update Rust crate libc to v0.2.161 (#19650)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [libc](https://redirect.github.com/rust-lang/libc) |
workspace.dependencies | patch | `0.2.159` -> `0.2.161` |

---

### Release Notes

<details>
<summary>rust-lang/libc (libc)</summary>

###
[`v0.2.161`](https://redirect.github.com/rust-lang/libc/releases/tag/0.2.161)

[Compare
Source](https://redirect.github.com/rust-lang/libc/compare/0.2.160...0.2.161)

##### Fixed

- OpenBSD: fix `FNM_PATHNAME` and `FNM_NOESCAPE` values
[#&#8203;3983](https://redirect.github.com/rust-lang/libc/pull/3983)

###
[`v0.2.160`](https://redirect.github.com/rust-lang/libc/releases/tag/0.2.160)

[Compare
Source](https://redirect.github.com/rust-lang/libc/compare/0.2.159...0.2.160)

##### Added

- Android: add `PR_GET_NAME` and `PR_SET_NAME`
[#&#8203;3941](https://redirect.github.com/rust-lang/libc/pull/3941)
- Apple: add `F_TRANSFEREXTENTS`
[#&#8203;3925](https://redirect.github.com/rust-lang/libc/pull/3925)
- Apple: add `mach_error_string`
[#&#8203;3913](https://redirect.github.com/rust-lang/libc/pull/3913)
- Apple: add additional `pthread` APIs
[#&#8203;3846](https://redirect.github.com/rust-lang/libc/pull/3846)
- Apple: add the `LOCAL_PEERTOKEN` socket option
[#&#8203;3929](https://redirect.github.com/rust-lang/libc/pull/3929)
- BSD: add `RTF_*`, `RTA_*`, `RTAX_*`, and `RTM_*` definitions
[#&#8203;3714](https://redirect.github.com/rust-lang/libc/pull/3714)
- Emscripten: add `AT_EACCESS`
[#&#8203;3911](https://redirect.github.com/rust-lang/libc/pull/3911)
- Emscripten: add `getgrgid`, `getgrnam`, `getgrnam_r` and `getgrgid_r`
[#&#8203;3912](https://redirect.github.com/rust-lang/libc/pull/3912)
- Emscripten: add `getpwnam_r` and `getpwuid_r`
[#&#8203;3906](https://redirect.github.com/rust-lang/libc/pull/3906)
- FreeBSD: add `POLLRDHUP`
[#&#8203;3936](https://redirect.github.com/rust-lang/libc/pull/3936)
- Haiku: add `arc4random`
[#&#8203;3945](https://redirect.github.com/rust-lang/libc/pull/3945)
- Illumos: add `ptsname_r`
[#&#8203;3867](https://redirect.github.com/rust-lang/libc/pull/3867)
- Linux: add `fanotify` interfaces
[#&#8203;3695](https://redirect.github.com/rust-lang/libc/pull/3695)
- Linux: add `tcp_info`
[#&#8203;3480](https://redirect.github.com/rust-lang/libc/pull/3480)
- Linux: add additional AF_PACKET options
[#&#8203;3540](https://redirect.github.com/rust-lang/libc/pull/3540)
- Linux: make Elf constants always available
[#&#8203;3938](https://redirect.github.com/rust-lang/libc/pull/3938)
- Musl x86: add `iopl` and `ioperm`
[#&#8203;3720](https://redirect.github.com/rust-lang/libc/pull/3720)
- Musl: add `posix_spawn` chdir functions
[#&#8203;3949](https://redirect.github.com/rust-lang/libc/pull/3949)
- Musl: add `utmpx.h` constants
[#&#8203;3908](https://redirect.github.com/rust-lang/libc/pull/3908)
- NetBSD: add `sysctlnametomib`, `CLOCK_THREAD_CPUTIME_ID` and
`CLOCK_PROCESS_CPUTIME_ID`
[#&#8203;3927](https://redirect.github.com/rust-lang/libc/pull/3927)
- Nuttx: initial support
[#&#8203;3909](https://redirect.github.com/rust-lang/libc/pull/3909)
- RTEMS: add `getentropy`
[#&#8203;3973](https://redirect.github.com/rust-lang/libc/pull/3973)
- RTEMS: initial support
[#&#8203;3866](https://redirect.github.com/rust-lang/libc/pull/3866)
- Solarish: add `POLLRDHUP`, `POSIX_FADV_*`, `O_RSYNC`, and
`posix_fallocate`
[#&#8203;3936](https://redirect.github.com/rust-lang/libc/pull/3936)
- Unix: add `fnmatch.h`
[#&#8203;3937](https://redirect.github.com/rust-lang/libc/pull/3937)
- VxWorks: add riscv64 support
[#&#8203;3935](https://redirect.github.com/rust-lang/libc/pull/3935)
- VxWorks: update constants related to the scheduler
[#&#8203;3963](https://redirect.github.com/rust-lang/libc/pull/3963)

##### Changed

- Redox: change `ino_t` to be `c_ulonglong`
[#&#8203;3919](https://redirect.github.com/rust-lang/libc/pull/3919)

##### Fixed

- ESP-IDF: fix mismatched constants and structs
[#&#8203;3920](https://redirect.github.com/rust-lang/libc/pull/3920)
- FreeBSD: fix `struct stat` on FreeBSD 12+
[#&#8203;3946](https://redirect.github.com/rust-lang/libc/pull/3946)

##### Other

- CI: Fix CI for FreeBSD 15
[#&#8203;3950](https://redirect.github.com/rust-lang/libc/pull/3950)
- Docs: link to `windows-sys`
[#&#8203;3915](https://redirect.github.com/rust-lang/libc/pull/3915)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "after 3pm on Wednesday" in timezone
America/New_York, Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

Release Notes:

- N/A

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOC4xMjAuMSIsInVwZGF0ZWRJblZlciI6IjM4LjEyMC4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-01 16:06:16 +02:00
renovate[bot]
cec72b837e Update Rust crate linkme to v0.3.29 (#19657)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [linkme](https://redirect.github.com/dtolnay/linkme) | dependencies |
patch | `0.3.28` -> `0.3.29` |

---

### Release Notes

<details>
<summary>dtolnay/linkme (linkme)</summary>

###
[`v0.3.29`](https://redirect.github.com/dtolnay/linkme/releases/tag/0.3.29)

[Compare
Source](https://redirect.github.com/dtolnay/linkme/compare/0.3.28...0.3.29)

- Add UEFI target support
([#&#8203;100](https://redirect.github.com/dtolnay/linkme/issues/100),
thanks [@&#8203;Javagedes](https://redirect.github.com/Javagedes))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "after 3pm on Wednesday" in timezone
America/New_York, Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

Release Notes:

- N/A

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOC4xMjAuMSIsInVwZGF0ZWRJblZlciI6IjM4LjEyMC4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-01 16:06:04 +02:00
Piotr Osiewicz
95842c7987 gpui: Add scroll anchors (#19894)
## Problem statement
I want to add keyboard navigation support to SSH modal. Doing so is
possible in current landscape, but not particularly ergonomic;
`gpui::ScrollHandle` has `scroll_to_item` API that takes an index of the
item you want to scroll to. The problem is, however, that it only works
with it's immediate children - thus in order to support scrolling via
keyboard you have to bend your UI to have a particular layout. Even when
your list of items is perfectly flat, having decorations inbetween items
is problematic as they are also children of the list, which means that
you either have to maintain the mapping to devise a correct index of an
item that you want to scroll to, or you have to make the decoration a
part of the list item itself, which might render the scrolling imprecise
(you might e.g. not want to scroll to a header, but to a button beneath
it).

## The solution
This PR adds `ScrollAnchor`, a new kind of handle to the gpui. It has a
similar role to that of a ScrollHandle, but instead of tracking how far
along an item has been scrolled, it tracks position of an element
relative to the parent to which a given scroll handle belongs. In short,
it allows us to persist the position of an element in a list of items
and scroll to it even if it's not an immediate children of a container
whose scroll position is tracked via an associated scroll handle.
Additionally this PR adds a new kind of the container to the UI crate
that serves as a convenience wrapper for using ScrollAnchors. This
container provides handlers for `menu::SelectNext` and
`menu::SelectPrev` and figures out which item should be focused next.

Release Notes:

- Improve keyboard navigation in ssh modal
2024-11-01 14:47:46 +01:00
Kirill Bulatov
183e3664cc Mention spectre-mitigated libs component in the Windows docs (#20069)
Closes https://github.com/zed-industries/zed/issues/20066

Release Notes:

- N/A
2024-11-01 13:43:35 +02:00
Yury Zhuravlev
08b124c8d4 Add possibility to build without musl (#19813)
Closes #19803 

Ad



Release Notes:

- N/A

---------

Co-authored-by: Kirill Bulatov <kirill@zed.dev>
2024-11-01 09:25:45 +02:00
Conrad Irwin
ea08026cd0 vim: Make window shortcuts work in other contexts (#20058)
Closes #18552

Release Notes:

- vim: Extended `ctrl-w` to work in non-editor contexts (like markdown
preview, or screen shares)
2024-10-31 23:49:43 -06:00
Conrad Irwin
daa9939c03 vim: o should scroll (#20054)
Closes: #19684

Release Notes:

- vim: Fixed `o` not scrolling new head into view
2024-10-31 23:26:04 -06:00
Conrad Irwin
f757e5a6c3 vim: Add :noh[lsearch] (#20056)
Closes: #18590

Release Notes:

- vim: Add :noh[lsearch]
2024-10-31 23:25:59 -06:00
Conrad Irwin
ecb874db62 vim: Fix gU$ (#20057)
Closes: #19380

Release Notes:

- vim: Fixed `gu$` missing last character of the line
2024-10-31 23:25:54 -06:00
Conrad Irwin
75f1862268 vim: Add (half of) ctrl-v/ctrl-q (#19585)
Release Notes:

- vim: Add `ctrl-v`/`ctrl-q` to type any unicode code point. For example
`ctrl-v escape` inserts an escape character(U+001B), or `ctrl-v u 1 0 E
2` types ტ (U+10E2). As in vim `ctrl-v ctrl-j` inserts U+0000 not
U+000A. Zed does not yet implement insertion of the vim-specific
representation of the typed keystroke for other keystrokes.
- vim: Add `ctrl-shift-v` as an alias for paste on Linux
2024-10-31 23:25:42 -06:00
Conrad Irwin
f8ab86f930 Simplify line normalization (#19712)
Release Notes:

- Added \u2028 and \u2029 to invisible characters. Previously these were
treated as \n.
2024-10-31 22:24:24 -06:00
Conrad Irwin
155854d9a9 Fix trigger release? (#20053)
Release Notes:

- N/A
2024-10-31 20:45:11 -06:00
Kyle Kelley
5b6578247f Upgrade nbformat and runtimelib (#20050)
Fixes an issue on load of notebooks that have `text/*` output in
`Vec<String>` rather than `String`. This ensures that Markdown output
will render correctly.

<img width="1306" alt="image"
src="https://github.com/user-attachments/assets/0bcc7dc8-527f-4067-a916-3ae569ea197d">


Release Notes:

- N/A
2024-10-31 17:58:36 -07:00
Boris Cherny
b87c4a1e13 assistant: Add health telemetry (#19928)
This PR adds a bit of telemetry for Anthropic models, in order to
understand model health. With this logging, we can monitor and diagnose
dips in performance, for example due to model rollouts.

Release Notes:

- N/A

---------

Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
2024-10-31 16:21:26 -07:00
Conrad Irwin
a0988508f0 SSHHELL escaping.... (#20046)
Closes #20027 
Closes #19976 (again)

Release Notes:

- Remoting: Fixed remotes with non-sh/bash/zsh default shells
- Remoting: Fixed remotes running busybox's version of gunzip
2024-10-31 16:10:03 -06:00
Nate Butler
a347c4def7 Add theme preview (#20039)
This PR adds a theme preview tab to help get an at a glance overview of
the styles in a theme.

![CleanShot 2024-10-31 at 11 27
18@2x](https://github.com/user-attachments/assets/798e97cf-9f80-4994-b2fd-ac1dcd58e4d9)

You can open it using `debug: open theme preview`.

The next major theme preview PR will move this into it's own crate, as
it will grow substantially as we add content.

Next for theme preview:

- Update layout to two columns, with controls on the right for selecting
theme, layer/elevation-index, etc.
- Cover more UI elements in preview
- Display theme colors in a more helpful way
- Add syntax & markdown previews


Release Notes:

- Added a way to preview the current theme's styles with the `debug:
open theme preview` command.
2024-10-31 11:40:38 -04:00
renovate[bot]
9c77bcc827 Update actions/setup-node digest to 39370e3 (#19979)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [actions/setup-node](https://redirect.github.com/actions/setup-node) |
action | digest | `0a44ba7` -> `39370e3` |

---

### Configuration

📅 **Schedule**: Branch creation - "after 3pm on Wednesday" in timezone
America/New_York, Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

Release Notes:

- N/A

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOC4xMzUuMiIsInVwZGF0ZWRJblZlciI6IjM4LjEzNS4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-31 11:28:37 -04:00
Marshall Bowers
8d1f377bf0 assistant: Add example streaming slash command (#20034)
This PR adds a `/streaming-example` slash command for the purposes of
showcasing streaming during development.

This slash command is only available to staff and isn't intended to be
shipped to the general public.

Release Notes:

- N/A
2024-10-31 10:53:47 -04:00
Kirill Bulatov
f766f6ceae Do less work when revealing entries in the outline panel (#20031)
Before this change, we were trying to determine current element before
debouncing, causing a lot of extra work on caret movement. Now, we only
do this for the task that managed to wait the entire debounce period.

Closes https://github.com/zed-industries/zed/issues/19817
Closes https://github.com/zed-industries/zed/issues/14235

Release Notes:

- Fixed outline panel-related performance issues when selections change
in the large document
([#19817](https://github.com/zed-industries/zed/issues/19817)),
([#14235](https://github.com/zed-industries/zed/issues/14235))
2024-10-31 16:29:19 +02:00
Kyle Kelley
9dad897d49 Clean up notebook item creation in project (#20030)
* Implement `clone_on_split` to allow splitting a notebook into another
pane

* Switched to `tab_content` in `impl Item for NotebookEditor` to show
both the notebook name and an icon

* Added placeholder methods and TODOs for future work, such as saving,
reloading, and search functionality within the notebook editor.

* Started moving more core `Model` bits into `NotebookItem`, including
pulling the language of the notebook (which affects every code cell)

* Loaded notebook asynchronously using `fs`

Release Notes:

- N/A

---------

Co-authored-by: Mikayla <mikayla@zed.dev>
2024-10-31 07:01:46 -07:00
Thorsten Ball
5b6401519b activity indicator: Reset formatting failure on click (#20029)
Release Notes:

- N/A
2024-10-31 14:33:36 +01:00
Thorsten Ball
293e080f03 tasks: Add editor: Spawn Nearest Task action (#19901)
This spawns the runnable task that that's closest to the cursor.

One thing missing right now is that it doesn't find tasks that are
attached to non-outline symbols, such as subtests in Go.

Release Notes:

- Added a new reveal option for tasks: `"no_focus"`. If used, the tasks
terminal panel will be opened and shown, but not focused.
- Added a new `editor: spawn nearest task` action that spawns the task
with a run indicator icon nearest to the cursor. It can be configured to
also use a `reveal` strategy. Example:
```json
{
  "context": "EmptyPane || SharedScreen || vim_mode == normal",
  "bindings": {
    ", r t": ["editor::SpawnNearestTask", { "reveal": "no_focus" }],
  }
}
```


Demo:



https://github.com/user-attachments/assets/0d1818f0-7ae4-4200-8c3e-0ed47550c298

---------

Co-authored-by: Bennet <bennet@zed.dev>
2024-10-31 14:25:57 +01:00
Auf keinen Fall Jens
633b665379 Option to insert comment character(s) at the beginning of the line(s) (#19746)
Closes #19459


This PR adds the optional setting to insert comment character(s) at the
beginning of the line(s) instead of after the indentation. It can be
enabled via keybindings:

```
"ctrl-/": ["editor::ToggleComments", { "ignore_indent": true }]
```

As suggested by @notpeter in #19459, this is implemented in
`toggle_comments` (editor.rs) taking the existing `advance_downwards`
option as example.

There's also a test case for the setting, which mimics the test case for
the regular comment toggling behavior.

---

I am not entirely happy with the name `ignore_indent`. The default would
be a double negative now `ignore_indent=false`. A positive wording would
probably easier to understand, but I could not think of anything
concise. `insert_at_line_start` or just `at_line_start` might work, but
didn't convince me either. That said, I am happy to change the name if
there are better ideas.

---

Release Notes:

- Added optional setting to insert comment character(s) at the beginning
of the line(s) instead of after the indentation. It can be used by
changing the default mapping to toggle comments like this: `"ctrl-/":
["editor::ToggleComments", { "ignore_indent": true }]`
2024-10-31 09:39:57 +01:00
Thorsten Ball
7fd334fddb proto: Remove unused UpdateUserSettings message (#20005)
Release Notes:

- N/A
2024-10-31 09:36:18 +01:00
Thorsten Ball
10226a3992 docs: Document inline blame options (#20006)
Release Notes:

- N/A
2024-10-31 09:36:05 +01:00
Peter Tripp
383e868af0 docs: SSH no longer requires Zed Preview (#20003) 2024-10-30 23:28:13 -04:00
Conrad Irwin
40802d91d4 SSH installation refactor (#19991)
This also cleans up logic for deciding how to do things.

Release Notes:

- Remoting: If downloading the binary on the remote fails, fall back to
uploading it.

---------

Co-authored-by: Mikayala <mikayla@zed.dev>
2024-10-30 16:20:11 -07:00
Danilo Leal
6d5784daa6 Adjust design of the slash command picker (#19973)
This PR removes the quote selection icon button from the footer and adds
it in the picker, and adds an icon field to each command entry. Final
result looks like:


https://github.com/user-attachments/assets/d177f1c1-b6f6-4652-9434-f6291b279e34

Release Notes:

- N/A
2024-10-30 19:42:42 -03:00
Conrad Irwin
f80eb264fb Robustify download on remote (#19983)
Closes #19976
Closes #19972

We now prefer curl to wget (as it supports socks5:// proxies) and pass
-f to
curl so it fails; and use sh instead of bash, which should have more
consistent
behaviour across systems

Release Notes:

- SSH Remoting: make downloading binary on remote more reliable.

---------

Co-authored-by: Will <will@zed.dev>
2024-10-30 15:17:50 -07:00
Conrad Irwin
3d956ca68b Fail download if download fails (#19990)
Co-Authored-By: Mikayla <mikayla@zed.dev>

Release Notes:

- Remoting: Fixes a bug where we could cache an HTML error page as a
binary

Co-authored-by: Mikayla <mikayla@zed.dev>
2024-10-30 14:50:41 -07:00
Kyle Kelley
7ce131aaf8 Trim whitespace from base64 encoded image data before decoding it (#19977)
Closes #17956
Closes #16330

This fix is for both REPL (released) and notebook (unreleased)

<img width="1210" alt="image"
src="https://github.com/user-attachments/assets/bd046f0f-3ad1-4c25-b3cb-114e008c2a69">

Release Notes:

- Fixed image support in REPL for certain versions of matplotlib that
included preceding and/or trailing whitespace in the base64 image data
2024-10-30 12:32:17 -07:00
Jen Stehlik
60be47d115 Update Gleam icon (#19978)
Improves upon: https://github.com/zed-industries/zed/pull/19887

Implements the feedback by @PixelJanitor to make the icon follow the
design guidelines.

Release Notes:

- Improved Gleam icon
2024-10-30 15:29:32 -04:00
Conrad Irwin
bd187883da Migration to remove dev servers (#19639)
Depends on #19638

Release Notes:

- None
2024-10-30 11:55:55 -06:00
Conrad Irwin
4f9217bca0 Support zed://ssh (#19970)
Closes: #15070

Release Notes:

- Added support for `zed://ssh/<connnection>/<path>`
2024-10-30 11:28:25 -06:00
Conrad Irwin
ce5222f1df Add KeyContextView (#19872)
Release Notes:

- Added `cmd-shift-p debug: Open Key Context View` to help debug custom
key bindings



https://github.com/user-attachments/assets/de273c97-5b27-45aa-9ff1-f943b0ed7dfe
2024-10-30 11:26:54 -06:00
Kirill Bulatov
cf7b0c8971 Add scrollbars to outline panel (#19969)
Part of https://github.com/zed-industries/zed/issues/15324


![image](https://github.com/user-attachments/assets/4f32d585-9bd2-46be-8234-3658a71906ee)

Repeats the approach used in the project panel.

Release Notes:

- Added scrollbars to outline panel

---------

Co-authored-by: Nate Butler <nate@zed.dev>
2024-10-30 19:09:14 +02:00
renovate[bot]
7bc4cb9868 Update Rust crate hyper to v0.14.31 (#19323)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [hyper](https://hyper.rs)
([source](https://redirect.github.com/hyperium/hyper)) |
workspace.dependencies | patch | `0.14.30` -> `0.14.31` |

---

### Release Notes

<details>
<summary>hyperium/hyper (hyper)</summary>

###
[`v0.14.31`](https://redirect.github.com/hyperium/hyper/releases/tag/v0.14.31)

[Compare
Source](https://redirect.github.com/hyperium/hyper/compare/v0.14.30...v0.14.31)

#### Bug Fixes

- **http1:** improve performance of parsing sequentially partial
messages
([97b595e](97b595e589))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "after 3pm on Wednesday" in timezone
America/New_York, Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

Release Notes:

- N/A

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOC4xMjAuMSIsInVwZGF0ZWRJblZlciI6IjM4LjEyMC4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-30 12:11:07 -04:00
Gherman
f84f3ffeb7 docs: Add linkedProjects section to Rust docs (#19954)
Related to #19897

Adds a section about multi-project workspaces and how to configure
rust-analyzer to diagnose them even if the cargo workspace does not list
them

Release Notes:

- N/A

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-10-30 11:43:44 -04:00
Richard Feldman
c564a4a26c Require /file or /tab when using Suggest Edits (#19960)
Now if you try to do Suggest Edits without a file context, you see this
(and it doesn't run the query).

<img width="635" alt="Screenshot 2024-10-30 at 10 51 24 AM"
src="https://github.com/user-attachments/assets/a3997ba6-98a9-4bfa-81b6-1d8579c26fd7">


Release Notes:

- N/A

---------

Co-authored-by: Antonio <antonio@zed.dev>
2024-10-30 11:38:43 -04:00
Marshall Bowers
515fd7b75f git_hosting_providers: Fix support for GitLab remotes containing subgroups (#19962)
This PR fixes the support for GitLab remote URLs containing subgroups.

Reported in
https://github.com/zed-industries/zed/issues/18012#issuecomment-2446206256.

Release Notes:

- N/A
2024-10-30 11:16:44 -04:00
Peter Tripp
662a4440cc v0.161.x dev 2024-10-30 11:06:39 -04:00
Marshall Bowers
5dee43b05c dart: Extract to zed-extensions/dart repository (#19959)
This PR extracts the Dart extension to the
[zed-extensions/dart](https://github.com/zed-extensions/dart)
repository.

Release Notes:

- N/A
2024-10-30 11:00:06 -04:00
283 changed files with 9839 additions and 5173 deletions

View File

@@ -15,6 +15,13 @@ body:
description: A clear and concise description of what you want to happen.
validations:
required: true
- type: textarea
id: environment
attributes:
label: Environment
description: Run the `copy system specs into clipboard` command palette action and paste the output in the field below.
validations:
required: true
- type: textarea
attributes:
label: |

View File

@@ -2,7 +2,7 @@ name: Bug Report
description: |
Use this template for **non-crash-related** bug reports.
Tip: open this issue template from within Zed with the `file bug report` command palette action.
labels: ["admin read", "triage", "defect"]
labels: ["admin read", "triage", "bug"]
body:
- type: checkboxes
attributes:
@@ -38,9 +38,12 @@ body:
Linux: `~/.local/share/zed/logs/Zed.log` or $XDG_DATA_HOME
If you only need the most recent lines, you can run the `zed: open log` command palette action to see the last 1000.
value: |
<details><summary>Zed.log</summary><pre>
<!-- Click below this line and paste or drag-and-drop your log-->
<details><summary>Zed.log</summary>
<!-- Click above this line and paste or drag-and-drop your log--></pre></details>
<!-- Click below this line and paste or drag-and-drop your log-->
```
```
<!-- Click above this line and paste or drag-and-drop your log--></details>
validations:
required: false

View File

@@ -1,7 +1,7 @@
name: Crash Report
description: |
Use this template for crash reports.
labels: ["admin read", "triage", "defect", "panic / crash"]
labels: ["admin read", "triage", "bug", "panic / crash"]
body:
- type: checkboxes
attributes:
@@ -31,9 +31,12 @@ body:
Linux: `~/.local/share/zed/logs/Zed.log` or $XDG_DATA_HOME
If you only need the most recent lines, you can run the `zed: open log` command palette action to see the last 1000.
value: |
<details><summary>Zed.log</summary><pre>
<!-- Click below this line and paste or drag-and-drop your log-->
<details><summary>Zed.log</summary>
<!-- Click above this line and paste or drag-and-drop your log--></pre></details>
<!-- Click below this line and paste or drag-and-drop your log-->
```
```
<!-- Click above this line and paste or drag-and-drop your log--></details>
validations:
required: false

View File

@@ -10,7 +10,7 @@ runs:
cargo install cargo-nextest
- name: Install Node
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
with:
node-version: "18"

View File

@@ -43,6 +43,8 @@ jobs:
esac
which cargo-set-version > /dev/null || cargo install cargo-edit
output=$(cargo set-version -p zed --bump patch 2>&1 | sed 's/.* //')
export GIT_COMMITTER_NAME="Zed Bot"
export GIT_COMMITTER_EMAIL="hi@zed.dev"
git commit -am "Bump to $output for @$GITHUB_ACTOR" --author "Zed Bot <hi@zed.dev>"
git tag v${output}${tag_suffix}
git push origin HEAD v${output}${tag_suffix}

View File

@@ -13,7 +13,7 @@ on:
branches:
- "**"
paths-ignore:
- "docs/**"
- "docs/**/*"
- ".github/workflows/community_*"
concurrency:
@@ -232,7 +232,7 @@ jobs:
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
steps:
- name: Install Node
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
with:
node-version: "18"
@@ -260,7 +260,7 @@ jobs:
run: |
mkdir -p target/
# Ignore any errors that occur while drafting release notes to not fail the build.
script/draft-release-notes "$version" "$channel" > target/release-notes.md || true
script/draft-release-notes "$RELEASE_VERSION" "$RELEASE_CHANNEL" > target/release-notes.md || true
- name: Generate license file
run: script/generate-licenses

View File

@@ -24,7 +24,7 @@ jobs:
# issues, preventing 365 days from working until then.
days-before-stale: 180
days-before-close: 7
any-of-issue-labels: "defect,panic / crash"
any-of-issue-labels: "bug,panic / crash"
operations-per-run: 1000
ascending: true
enable-statistics: true

View File

@@ -21,7 +21,7 @@ jobs:
version: 9
- name: Setup Node
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
with:
node-version: "20"
cache: "pnpm"

View File

@@ -37,28 +37,28 @@ jobs:
mdbook build ./docs --dest-dir=../target/deploy/docs/
- name: Deploy Docs
uses: cloudflare/wrangler-action@9681c2997648301493e78cacbfb790a9f19c833f # v3
uses: cloudflare/wrangler-action@b2a0191ce60d21388e1a8dcc968b4e9966f938e1 # v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy target/deploy --project-name=docs
- name: Deploy Install
uses: cloudflare/wrangler-action@9681c2997648301493e78cacbfb790a9f19c833f # v3
uses: cloudflare/wrangler-action@b2a0191ce60d21388e1a8dcc968b4e9966f938e1 # v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: r2 object put -f script/install.sh zed-open-source-website-assets/install.sh
- name: Deploy Docs Workers
uses: cloudflare/wrangler-action@9681c2997648301493e78cacbfb790a9f19c833f # v3
uses: cloudflare/wrangler-action@b2a0191ce60d21388e1a8dcc968b4e9966f938e1 # v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: deploy .cloudflare/docs-proxy/src/worker.js
- name: Deploy Install Workers
uses: cloudflare/wrangler-action@9681c2997648301493e78cacbfb790a9f19c833f # v3
uses: cloudflare/wrangler-action@b2a0191ce60d21388e1a8dcc968b4e9966f938e1 # v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}

View File

@@ -22,7 +22,7 @@ jobs:
- buildjet-16vcpu-ubuntu-2204
steps:
- name: Install Node
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
with:
node-version: "18"

View File

@@ -70,7 +70,7 @@ jobs:
ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: ${{ secrets.ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON }}
steps:
- name: Install Node
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
with:
node-version: "18"

View File

@@ -60,6 +60,8 @@ Max Brunsfeld <maxbrunsfeld@gmail.com>
Max Brunsfeld <maxbrunsfeld@gmail.com> <max@zed.dev>
Max Linke <maxlinke88@gmail.com>
Max Linke <maxlinke88@gmail.com> <kain88-de@users.noreply.github.com>
Michael Sloan <michael@zed.dev>
Michael Sloan <michael@zed.dev> <mgsloan@google.com>
Mikayla Maki <mikayla@zed.dev>
Mikayla Maki <mikayla@zed.dev> <mikayla.c.maki@gmail.com>
Mikayla Maki <mikayla@zed.dev> <mikayla.c.maki@icloud.com>

314
Cargo.lock generated
View File

@@ -9,7 +9,7 @@ dependencies = [
"anyhow",
"auto_update",
"editor",
"extension",
"extension_host",
"futures 0.3.30",
"gpui",
"language",
@@ -544,9 +544,9 @@ dependencies = [
[[package]]
name = "async-compression"
version = "0.4.13"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e614738943d3f68c628ae3dbce7c3daffb196665f82f8c8ea6b65de73c79429"
checksum = "0cb8f1d480b0ea3783ab015936d2a55c87e219676f0c0b7dec61494043f21857"
dependencies = [
"deflate64",
"flate2",
@@ -854,7 +854,7 @@ dependencies = [
"chrono",
"futures-util",
"http-types",
"hyper 0.14.30",
"hyper 0.14.31",
"hyper-rustls 0.24.2",
"serde",
"serde_json",
@@ -1350,7 +1350,7 @@ dependencies = [
"http-body 0.4.6",
"http-body 1.0.1",
"httparse",
"hyper 0.14.30",
"hyper 0.14.31",
"hyper-rustls 0.24.2",
"once_cell",
"pin-project-lite",
@@ -1441,7 +1441,7 @@ dependencies = [
"headers",
"http 0.2.12",
"http-body 0.4.6",
"hyper 0.14.30",
"hyper 0.14.31",
"itoa",
"matchit",
"memchr",
@@ -1587,7 +1587,7 @@ dependencies = [
"bitflags 2.6.0",
"cexpr",
"clang-sys",
"itertools 0.12.1",
"itertools 0.10.5",
"lazy_static",
"lazycell",
"proc-macro2",
@@ -1636,6 +1636,15 @@ dependencies = [
"bit-vec 0.7.0",
]
[[package]]
name = "bit-set"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
dependencies = [
"bit-vec 0.8.0",
]
[[package]]
name = "bit-vec"
version = "0.6.3"
@@ -1648,6 +1657,12 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2c54ff287cfc0a34f38a6b832ea1bd8e448a330b3e40a50859e6488bee07f22"
[[package]]
name = "bit-vec"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
[[package]]
name = "bit_field"
version = "0.10.2"
@@ -2366,7 +2381,7 @@ dependencies = [
"clickhouse-derive",
"clickhouse-rs-cityhash-sys",
"futures 0.3.30",
"hyper 0.14.30",
"hyper 0.14.31",
"hyper-tls",
"lz4",
"sealed",
@@ -2432,7 +2447,6 @@ dependencies = [
"smol",
"sysinfo",
"telemetry_events",
"tempfile",
"text",
"thiserror",
"time",
@@ -2569,7 +2583,7 @@ dependencies = [
"gpui",
"hex",
"http_client",
"hyper 0.14.30",
"hyper 0.14.31",
"indoc",
"jsonwebtoken",
"language",
@@ -3052,18 +3066,18 @@ dependencies = [
[[package]]
name = "cranelift-bforest"
version = "0.111.1"
version = "0.111.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32d69b774780246008783a75edfb943eccc2487b6a43808503a07cd563f2ffde"
checksum = "f823c6662ea77699089ec8b6b4b8a23c1e1a9c6526a6420ede7ac957274a7ab4"
dependencies = [
"cranelift-entity",
]
[[package]]
name = "cranelift-bitset"
version = "0.111.1"
version = "0.111.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7d8d71c6b32c1a7cff254c5e5d7359872c1e5e610fbe963472afcddbd9cf303"
checksum = "2fcbb4187005097204458a8e4309bb9e737933477e47b4609f81b07a5b4cdd25"
dependencies = [
"serde",
"serde_derive",
@@ -3071,9 +3085,9 @@ dependencies = [
[[package]]
name = "cranelift-codegen"
version = "0.111.1"
version = "0.111.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ad3a906f2a3f3590ad9798d59a46959a8593258eb985af722f634723c063a2c"
checksum = "8cd1aaf8e88339f4f95afffd60d22033546ec7da4d79e805b85260a16668f78f"
dependencies = [
"bumpalo",
"cranelift-bforest",
@@ -3094,33 +3108,33 @@ dependencies = [
[[package]]
name = "cranelift-codegen-meta"
version = "0.111.1"
version = "0.111.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd5e4ee12262a135efbef3ced4ab2153adafe4adc55f36af94f9d73be0f7505d"
checksum = "8e541b0418bbba3ce82040a445bd9a83bf3e0da604a95178d9e949dc8a7840af"
dependencies = [
"cranelift-codegen-shared",
]
[[package]]
name = "cranelift-codegen-shared"
version = "0.111.1"
version = "0.111.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b9374a2a5f060f72e3080fe1c87c9ff4bef2cbe798faae60daf276fb1a13968"
checksum = "91fc96a709a30be39d53ecf89dbfe4edcc5adba528d4b65f7e58dc867ba70fab"
[[package]]
name = "cranelift-control"
version = "0.111.1"
version = "0.111.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fba3ca2f344bb22d265a928e7c3f5f46e1a2eb41f1393bd53538d07b6ffb5293"
checksum = "4c3bfcb035e0a501323896bb7ea3d7a5dd1fac3e92dda458ccd23960fde12c88"
dependencies = [
"arbitrary",
]
[[package]]
name = "cranelift-entity"
version = "0.111.1"
version = "0.111.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6aef77dfb018eed09d92d4244abe3c1c060cbbd900c24f75ddde7d75d0e781e"
checksum = "b2f00b4eba51d73a8c343c45cfdeeffa1f74f423bba0e6b8e290e646777c2b81"
dependencies = [
"cranelift-bitset",
"serde",
@@ -3129,9 +3143,9 @@ dependencies = [
[[package]]
name = "cranelift-frontend"
version = "0.111.1"
version = "0.111.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b1d6954f03d63df1cb95d66153c97df0201862220861349bbd5f583754b1917"
checksum = "52d5e18bf04660bb716dacf45809e2d4c85e7111701e27dbdb75b4634504ad8f"
dependencies = [
"cranelift-codegen",
"log",
@@ -3141,15 +3155,15 @@ dependencies = [
[[package]]
name = "cranelift-isle"
version = "0.111.1"
version = "0.111.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8b9b7e088b784796ea8aa5947c1cc12034c1b076a077ec2a5a287da717fa746"
checksum = "31f9901807b6d0fde1205f0e4db9d96dcf7ddfc1894c69eb2ff93c47ebf2439f"
[[package]]
name = "cranelift-native"
version = "0.111.1"
version = "0.111.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cab7424083d070669ff3fdeea7c5b4b5013a055aa1ee0532703f17a5f62af64"
checksum = "967d65a4077726a9afc3f4694e037f34b992cbe2b6c48ce519b714a0b0558f97"
dependencies = [
"cranelift-codegen",
"libc",
@@ -3158,9 +3172,9 @@ dependencies = [
[[package]]
name = "cranelift-wasm"
version = "0.111.1"
version = "0.111.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81a9f6d0495984eef1d753ec8748de0b216b37ade16d219f1c0f27d8188d7f77"
checksum = "4899fd1ef6b1fe1df30f26ef864bd6e45040b8cf9f3cb3905d3e973c25698579"
dependencies = [
"cranelift-codegen",
"cranelift-entity",
@@ -3491,7 +3505,6 @@ dependencies = [
"ctor",
"editor",
"env_logger 0.11.5",
"futures 0.3.30",
"gpui",
"language",
"log",
@@ -3718,6 +3731,7 @@ dependencies = [
"tree-sitter-rust",
"tree-sitter-typescript",
"ui",
"unicode-script",
"unicode-segmentation",
"unindent",
"url",
@@ -4092,6 +4106,51 @@ dependencies = [
[[package]]
name = "extension"
version = "0.1.0"
dependencies = [
"anyhow",
"async-compression",
"async-tar",
"collections",
"fs",
"futures 0.3.30",
"http_client",
"language",
"log",
"lsp",
"semantic_version",
"serde",
"serde_json",
"toml 0.8.19",
"wasm-encoder 0.215.0",
"wasmparser 0.215.0",
"wit-component",
]
[[package]]
name = "extension_cli"
version = "0.1.0"
dependencies = [
"anyhow",
"clap",
"env_logger 0.11.5",
"extension",
"fs",
"language",
"log",
"reqwest_client",
"rpc",
"serde",
"serde_json",
"theme",
"tokio",
"toml 0.8.19",
"tree-sitter",
"wasmtime",
]
[[package]]
name = "extension_host"
version = "0.1.0"
dependencies = [
"anyhow",
"assistant_slash_command",
@@ -4102,6 +4161,7 @@ dependencies = [
"collections",
"ctor",
"env_logger 0.11.5",
"extension",
"fs",
"futures 0.3.30",
"gpui",
@@ -4129,36 +4189,11 @@ dependencies = [
"ui",
"url",
"util",
"wasm-encoder 0.215.0",
"wasmparser 0.215.0",
"wasmtime",
"wasmtime-wasi",
"wit-component",
"workspace",
]
[[package]]
name = "extension_cli"
version = "0.1.0"
dependencies = [
"anyhow",
"clap",
"env_logger 0.11.5",
"extension",
"fs",
"language",
"log",
"reqwest_client",
"rpc",
"serde",
"serde_json",
"theme",
"tokio",
"toml 0.8.19",
"tree-sitter",
"wasmtime",
]
[[package]]
name = "extensions_ui"
version = "0.1.0"
@@ -4168,7 +4203,7 @@ dependencies = [
"collections",
"db",
"editor",
"extension",
"extension_host",
"fs",
"fuzzy",
"gpui",
@@ -4205,6 +4240,17 @@ dependencies = [
"regex",
]
[[package]]
name = "fancy-regex"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e24cb5a94bcae1e5408b0effca5cd7172ea3c5755049c5f3af4cd283a165298"
dependencies = [
"bit-set 0.8.0",
"regex-automata 0.4.7",
"regex-syntax 0.8.4",
]
[[package]]
name = "fast-srgb8"
version = "1.0.0"
@@ -4388,9 +4434,9 @@ checksum = "8ce81f49ae8a0482e4c55ea62ebbd7e5a686af544c00b9d090bba3ff9be97b3d"
[[package]]
name = "flume"
version = "0.11.0"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181"
checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095"
dependencies = [
"futures-core",
"futures-sink",
@@ -5570,9 +5616,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "hyper"
version = "0.14.30"
version = "0.14.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9"
checksum = "8c08302e8fa335b151b788c775ff56e7a03ae64ff85c548ee820fecb70356e85"
dependencies = [
"bytes 1.7.2",
"futures-channel",
@@ -5620,7 +5666,7 @@ checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590"
dependencies = [
"futures-util",
"http 0.2.12",
"hyper 0.14.30",
"hyper 0.14.31",
"log",
"rustls 0.21.12",
"rustls-native-certs 0.6.3",
@@ -5653,7 +5699,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
dependencies = [
"bytes 1.7.2",
"hyper 0.14.30",
"hyper 0.14.31",
"native-tls",
"tokio",
"tokio-native-tls",
@@ -5776,6 +5822,7 @@ dependencies = [
"gpui",
"project",
"settings",
"theme",
"ui",
"workspace",
]
@@ -6157,9 +6204,9 @@ dependencies = [
[[package]]
name = "jupyter-serde"
version = "0.2.0"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a444fb3f87ee6885eb316028cc998c7d84811663ef95d78c419419423d5a054"
checksum = "77b96de099fc23d5c21e05de32cc087c8326983895b7f6c242562af01f7d4c81"
dependencies = [
"anyhow",
"chrono",
@@ -6309,6 +6356,7 @@ dependencies = [
"settings",
"smol",
"strum 0.25.0",
"telemetry_events",
"text",
"theme",
"thiserror",
@@ -6346,6 +6394,7 @@ dependencies = [
"env_logger 0.11.5",
"futures 0.3.30",
"gpui",
"itertools 0.13.0",
"language",
"lsp",
"project",
@@ -6357,6 +6406,7 @@ dependencies = [
"ui",
"util",
"workspace",
"zed_actions",
]
[[package]]
@@ -6368,7 +6418,6 @@ dependencies = [
"async-tar",
"async-trait",
"collections",
"feature_flags",
"futures 0.3.30",
"gpui",
"http_client",
@@ -6380,6 +6429,7 @@ dependencies = [
"pet",
"pet-conda",
"pet-core",
"pet-fs",
"pet-poetry",
"pet-reporter",
"project",
@@ -6445,9 +6495,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
[[package]]
name = "libc"
version = "0.2.159"
version = "0.2.161"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5"
checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1"
[[package]]
name = "libdbus-sys"
@@ -6489,7 +6539,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
dependencies = [
"cfg-if",
"windows-targets 0.52.6",
"windows-targets 0.48.5",
]
[[package]]
@@ -6553,18 +6603,18 @@ dependencies = [
[[package]]
name = "linkme"
version = "0.3.28"
version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c943daedff228392b791b33bba32e75737756e80a613e32e246c6ce9cbab20a"
checksum = "70fe496a7af8c406f877635cbf3cd6a9fac9d6f443f58691cd8afe6ce0971af4"
dependencies = [
"linkme-impl",
]
[[package]]
name = "linkme-impl"
version = "0.3.28"
version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb26336e6dc7cc76e7927d2c9e7e3bb376d7af65a6f56a0b16c47d18a9b1abc5"
checksum = "b01f197a15988fb5b2ec0a5a9800c97e70771499c456ad757d63b3c5e9b96e75"
dependencies = [
"proc-macro2",
"quote",
@@ -7154,9 +7204,9 @@ dependencies = [
[[package]]
name = "nbformat"
version = "0.3.1"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "146074ad45cab20f5d98ccded164826158471f21d04f96e40b9872529e10979d"
checksum = "84f8a9ab08b34237c2c1d0504b794c2ff01c08dfc46a060d160f004a7f479c31"
dependencies = [
"anyhow",
"chrono",
@@ -7771,6 +7821,7 @@ dependencies = [
"project",
"rope",
"serde_json",
"settings",
"smol",
"theme",
"tree-sitter-rust",
@@ -7795,6 +7846,7 @@ dependencies = [
"language",
"log",
"menu",
"outline",
"pretty_assertions",
"project",
"schemars",
@@ -7929,9 +7981,9 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "pathdiff"
version = "0.2.1"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
checksum = "d61c5ce1153ab5b689d0c074c4e7fc613e942dfb7dd9eea5ab202d2ad91fe361"
[[package]]
name = "pathfinder_geometry"
@@ -8806,27 +8858,27 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.87"
version = "1.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a"
checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
dependencies = [
"unicode-ident",
]
[[package]]
name = "profiling"
version = "1.0.15"
version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43d84d1d7a6ac92673717f9f6d1518374ef257669c24ebc5ac25d5033828be58"
checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d"
dependencies = [
"profiling-procmacros",
]
[[package]]
name = "profiling-procmacros"
version = "1.0.15"
version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd"
checksum = "a65f2e60fbf1063868558d69c6beacf412dc755f9fc020f514b7955fc914fe30"
dependencies = [
"quote",
"syn 2.0.76",
@@ -8843,6 +8895,7 @@ dependencies = [
"clock",
"collections",
"env_logger 0.11.5",
"fancy-regex 0.14.0",
"fs",
"futures 0.3.30",
"fuzzy",
@@ -9533,9 +9586,12 @@ dependencies = [
"fs",
"futures 0.3.30",
"gpui",
"itertools 0.13.0",
"log",
"parking_lot",
"paths",
"prost",
"release_channel",
"rpc",
"serde",
"serde_json",
@@ -9585,6 +9641,7 @@ dependencies = [
"settings",
"shellexpand 2.1.2",
"smol",
"sysinfo",
"telemetry_events",
"toml 0.8.19",
"util",
@@ -9660,7 +9717,7 @@ dependencies = [
"h2 0.3.26",
"http 0.2.12",
"http-body 0.4.6",
"hyper 0.14.30",
"hyper 0.14.31",
"hyper-tls",
"ipnet",
"js-sys",
@@ -9965,9 +10022,9 @@ dependencies = [
[[package]]
name = "runtimelib"
version = "0.16.0"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "263588fe9593333c4bfde258c9021fc64e766ea434e070c6b67c7100536d6499"
checksum = "bc7fe3c17675445fe89de68d130be00b7115104924fbcf53a9b0a84b0283fc81"
dependencies = [
"anyhow",
"async-dispatcher",
@@ -12090,6 +12147,7 @@ dependencies = [
"serde_json_lenient",
"serde_repr",
"settings",
"strum 0.25.0",
"util",
"uuid",
]
@@ -12120,7 +12178,6 @@ name = "theme_selector"
version = "0.1.0"
dependencies = [
"client",
"feature_flags",
"fs",
"fuzzy",
"gpui",
@@ -12184,7 +12241,7 @@ dependencies = [
"anyhow",
"base64 0.21.7",
"bstr",
"fancy-regex",
"fancy-regex 0.12.0",
"lazy_static",
"parking_lot",
"rustc-hash 1.1.0",
@@ -13102,9 +13159,9 @@ checksum = "52ea75f83c0137a9b98608359a5f1af8144876eb67bcb1ce837368e906a9f524"
[[package]]
name = "unicode-script"
version = "0.5.6"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad8d71f5726e5f285a935e9fe8edfd53f0491eb6e9a5774097fdabee7cd8c9cd"
checksum = "9fb421b350c9aff471779e262955939f565ec18b86c15364e6bdf0d662ca7c1f"
[[package]]
name = "unicode-segmentation"
@@ -13452,7 +13509,7 @@ dependencies = [
"futures-util",
"headers",
"http 0.2.12",
"hyper 0.14.30",
"hyper 0.14.31",
"log",
"mime",
"mime_guess",
@@ -13639,9 +13696,9 @@ dependencies = [
[[package]]
name = "wasmtime"
version = "24.0.1"
version = "24.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e4a5b05e9f1797e557e79f0cf04348eaa7a232596939ef4762838ddf7a6127a"
checksum = "e763074ccd6b251c78095fcd27707253b69cef961ea0a2ff76a8d246ddfadd1b"
dependencies = [
"anyhow",
"async-trait",
@@ -13685,9 +13742,9 @@ dependencies = [
[[package]]
name = "wasmtime-asm-macros"
version = "24.0.1"
version = "24.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64414227e19556d4372f9688458c5673606de83473eb66cd0514d36ea8808cab"
checksum = "f45004b6fa5d12dd95b427474e69bde05a6d31d33b39bd56054f9cd68e824283"
dependencies = [
"cfg-if",
]
@@ -13718,9 +13775,9 @@ dependencies = [
[[package]]
name = "wasmtime-component-macro"
version = "24.0.1"
version = "24.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3ead31b73689602225742920adbcd881f5656702c1a3b4830862c0c66731727"
checksum = "74b72572d389586e429a9830ab68a5b3e2a567962b8a82f4249652ccc68ddab2"
dependencies = [
"anyhow",
"proc-macro2",
@@ -13733,15 +13790,15 @@ dependencies = [
[[package]]
name = "wasmtime-component-util"
version = "24.0.1"
version = "24.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab2c778661800e1dcd8ba3e15ff042299709e0a4c512525d9cbb604a04c0421b"
checksum = "eb3081af782040e8016373e603ee854496c82cdc0f32b13a6bc9700e15f582db"
[[package]]
name = "wasmtime-cranelift"
version = "24.0.1"
version = "24.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7ee1f436bcf7d213ef7c2e9d44caffcd57e540ccf997d013384c2ae9b82db7"
checksum = "42c18ca178eee0947cd53b27d3a101dd2f79afec86fc3ce657545519c6bf011a"
dependencies = [
"anyhow",
"cfg-if",
@@ -13763,9 +13820,9 @@ dependencies = [
[[package]]
name = "wasmtime-environ"
version = "24.0.1"
version = "24.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa8c33adfb3b9f8d6ef716bc55aea5e6b2275cd5a6721ec8c837d1cb0c471516"
checksum = "e80da0784d4dd0788479ce390cd4a54a893d24f2937d4046145704777aa7a131"
dependencies = [
"anyhow",
"cpp_demangle",
@@ -13790,9 +13847,9 @@ dependencies = [
[[package]]
name = "wasmtime-fiber"
version = "24.0.1"
version = "24.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f3227ed807c2dda9dd770c241023fcd6e48e6722c1c26ff79fc3604d412e884"
checksum = "57c3d366194ff87b8aeeb7348bb789d5dd9a9aca18b340b19dcf4ab96966e663"
dependencies = [
"anyhow",
"cc",
@@ -13805,9 +13862,9 @@ dependencies = [
[[package]]
name = "wasmtime-jit-icache-coherence"
version = "24.0.1"
version = "24.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa89fc440f0edca882ba6d1890608898e6f0193afdc504c0a64478ec53622bd6"
checksum = "c543f7ee7b1ec8f2215f88197a40f9fa3452dc98c5902c5c700d8ec9e9ea7021"
dependencies = [
"anyhow",
"cfg-if",
@@ -13817,15 +13874,15 @@ dependencies = [
[[package]]
name = "wasmtime-slab"
version = "24.0.1"
version = "24.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "682b7a5b6772c4e4de8c696fc619ec97930b5e89098db9bee22c1136e002438b"
checksum = "bcf7ded4156c76cc1cb348e5728096087e2c432714d1b285044c6da6a1e3d01a"
[[package]]
name = "wasmtime-types"
version = "24.0.1"
version = "24.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a95ea5572f8c3ffe777af21aa00a92097ded291a342fecad9f2c6a972ecea99"
checksum = "c92a6f3c2a8704a60ae0278ea2635c986539539ce1b80080b0fe8ea7bc83da81"
dependencies = [
"anyhow",
"cranelift-entity",
@@ -13837,9 +13894,9 @@ dependencies = [
[[package]]
name = "wasmtime-versioned-export-macros"
version = "24.0.1"
version = "24.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3621bfccd4e4336ae141d62b96e96316c0f23c47d64e9700594ebe3c4d9a10"
checksum = "7a6e2f847c118d5b26f0cc01d12a6d72fa450e32c42a4a3ce5d33afb4729ed6a"
dependencies = [
"proc-macro2",
"quote",
@@ -13879,9 +13936,9 @@ dependencies = [
[[package]]
name = "wasmtime-winch"
version = "24.0.1"
version = "24.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1d3e99f6bba37864487c9356398667699935b9cfa3655ed2b153b9428b3dd21"
checksum = "ee3640cd34c67f505e88cef0da11368806204a24c68c35d671a48a59bb37f908"
dependencies = [
"anyhow",
"cranelift-codegen",
@@ -13896,9 +13953,9 @@ dependencies = [
[[package]]
name = "wasmtime-wit-bindgen"
version = "24.0.1"
version = "24.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee0f4524da226d2cb503d794c8928de6bc24878758cebd4e383c946e9fdb8b3a"
checksum = "c58b085b2d330e5057dddd31f3ca527569b90fcdd35f6d373420c304927a5190"
dependencies = [
"anyhow",
"heck 0.4.1",
@@ -14165,7 +14222,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
"windows-sys 0.59.0",
"windows-sys 0.48.0",
]
[[package]]
@@ -14176,9 +14233,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "winch-codegen"
version = "0.22.1"
version = "0.22.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c139fb9298d9651b6869afd544e567ca2448cd5f5ddcb24e4bb86a1ee187c8b3"
checksum = "46d7fecc199486f048bb2d649dce68bf28712ae1183dd54fd4a0534989517b24"
dependencies = [
"anyhow",
"cranelift-codegen",
@@ -14740,7 +14797,6 @@ dependencies = [
"parking_lot",
"postage",
"project",
"release_channel",
"remote",
"schemars",
"serde",
@@ -14749,6 +14805,7 @@ dependencies = [
"settings",
"smallvec",
"sqlez",
"strum 0.25.0",
"task",
"tempfile",
"theme",
@@ -15033,7 +15090,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.160.0"
version = "0.162.0"
dependencies = [
"activity_indicator",
"anyhow",
@@ -15060,7 +15117,7 @@ dependencies = [
"diagnostics",
"editor",
"env_logger 0.11.5",
"extension",
"extension_host",
"extensions_ui",
"feature_flags",
"feedback",
@@ -15173,13 +15230,6 @@ dependencies = [
"zed_extension_api 0.1.0",
]
[[package]]
name = "zed_dart"
version = "0.1.2"
dependencies = [
"zed_extension_api 0.1.0",
]
[[package]]
name = "zed_deno"
version = "0.0.2"

View File

@@ -30,6 +30,7 @@ members = [
"crates/extension",
"crates/extension_api",
"crates/extension_cli",
"crates/extension_host",
"crates/extensions_ui",
"crates/feature_flags",
"crates/feedback",
@@ -138,7 +139,6 @@ members = [
"extensions/astro",
"extensions/clojure",
"extensions/csharp",
"extensions/dart",
"extensions/deno",
"extensions/elixir",
"extensions/elm",
@@ -203,6 +203,7 @@ db = { path = "crates/db" }
diagnostics = { path = "crates/diagnostics" }
editor = { path = "crates/editor" }
extension = { path = "crates/extension" }
extension_host = { path = "crates/extension_host" }
extensions_ui = { path = "crates/extensions_ui" }
feature_flags = { path = "crates/feature_flags" }
feedback = { path = "crates/feedback" }
@@ -349,6 +350,7 @@ ec4rs = "1.1"
emojis = "0.6.1"
env_logger = "0.11"
exec = "0.3.1"
fancy-regex = "0.14.0"
fork = "0.2.0"
futures = "0.3"
futures-batch = "0.6.1"
@@ -371,7 +373,7 @@ linkify = "0.10.0"
log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] }
markup5ever_rcdom = "0.3.0"
nanoid = "0.4"
nbformat = "0.3.1"
nbformat = "0.3.2"
nix = "0.29"
num-format = "0.4.4"
once_cell = "1.19.0"
@@ -380,6 +382,7 @@ palette = { version = "0.7.5", default-features = false, features = ["std"] }
parking_lot = "0.12.1"
pathdiff = "0.2"
pet = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
pet-fs = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
pet-conda = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
pet-core = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
pet-poetry = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "ffcbf3f28c46633abd5448a52b1f396c322e0d6c" }
@@ -404,7 +407,7 @@ reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "fd110f
"stream",
] }
rsa = "0.9.6"
runtimelib = { version = "0.16.0", default-features = false, features = [
runtimelib = { version = "0.16.1", default-features = false, features = [
"async-dispatcher-runtime",
] }
rustc-demangle = "0.1.23"
@@ -474,6 +477,7 @@ tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml",
unicase = "2.6"
unindent = "0.1.7"
unicode-segmentation = "1.10"
unicode-script = "0.5.7"
url = "2.2"
uuid = { version = "1.1.2", features = ["v4", "v5", "serde"] }
wasmparser = "0.215"

View File

@@ -1,6 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
<path fill-rule="evenodd" fill="black" d="M 3.828125 14.601562 C 3.894531 15.726562 5.183594 16.375 6.132812 15.785156 L 6.136719 15.785156 L 8.988281 13.824219 C 8.996094 13.816406 9.007812 13.8125 9.015625 13.804688 C 9.203125 13.675781 9.4375 13.636719 9.65625 13.691406 L 12.988281 14.550781 C 14.105469 14.839844 15.140625 13.769531 14.8125 12.667969 L 13.832031 9.386719 C 13.769531 9.167969 13.800781 8.9375 13.921875 8.75 C 13.921875 8.746094 13.925781 8.746094 13.925781 8.746094 L 15.777344 5.863281 L 15.777344 5.859375 C 15.78125 5.851562 15.785156 5.84375 15.789062 5.835938 L 15.792969 5.835938 C 16.382812 4.871094 15.6875 3.582031 14.542969 3.554688 L 11.109375 3.472656 C 10.878906 3.464844 10.664062 3.359375 10.519531 3.183594 L 8.339844 0.542969 C 8.019531 0.152344 7.550781 -0.015625 7.105469 0.0078125 L 7.101562 0.0078125 C 7.039062 0.0117188 6.976562 0.0195312 6.914062 0.0273438 C 6.414062 0.117188 5.945312 0.453125 5.75 1 L 4.609375 4.222656 C 4.535156 4.4375 4.367188 4.613281 4.152344 4.695312 L 0.957031 5.945312 C -0.121094 6.363281 -0.328125 7.835938 0.589844 8.535156 L 3.316406 10.609375 C 3.5 10.75 3.609375 10.960938 3.625 11.191406 Z M 7.515625 1.847656 C 7.421875 1.730469 7.296875 1.695312 7.183594 1.714844 C 7.066406 1.734375 6.960938 1.8125 6.914062 1.953125 L 5.867188 4.902344 C 5.699219 5.382812 5.328125 5.765625 4.851562 5.949219 L 1.925781 7.09375 C 1.785156 7.148438 1.710938 7.253906 1.695312 7.371094 C 1.679688 7.484375 1.71875 7.605469 1.839844 7.695312 L 4.335938 9.597656 C 4.742188 9.90625 4.992188 10.375 5.023438 10.882812 L 5.207031 14.003906 C 5.214844 14.152344 5.296875 14.253906 5.398438 14.304688 C 5.503906 14.355469 5.632812 14.355469 5.757812 14.269531 L 8.347656 12.492188 C 8.765625 12.207031 9.292969 12.113281 9.785156 12.242188 L 12.824219 13.027344 C 12.972656 13.066406 13.09375 13.023438 13.175781 12.9375 C 13.257812 12.855469 13.296875 12.734375 13.253906 12.589844 L 12.355469 9.589844 C 12.210938 9.105469 12.285156 8.578125 12.558594 8.148438 L 14.253906 5.511719 C 14.335938 5.386719 14.332031 5.257812 14.277344 5.15625 C 14.222656 5.054688 14.117188 4.980469 13.964844 4.976562 L 10.824219 4.902344 C 10.316406 4.886719 9.835938 4.65625 9.511719 4.261719 Z M 7.515625 1.847656 "/>
<path fill="black" d="M 5.71875 7.257812 C 5.671875 7.25 5.628906 7.246094 5.582031 7.246094 C 5.09375 7.246094 4.695312 7.644531 4.695312 8.128906 C 4.695312 8.613281 5.09375 9.011719 5.582031 9.011719 C 6.070312 9.011719 6.46875 8.613281 6.46875 8.128906 C 6.46875 7.6875 6.140625 7.320312 5.71875 7.257812 Z M 5.71875 7.257812 "/>
<path fill="black" d="M 11.019531 7.953125 C 10.976562 7.957031 10.929688 7.960938 10.886719 7.960938 C 10.398438 7.960938 10 7.5625 10 7.078125 C 10 6.59375 10.398438 6.195312 10.886719 6.195312 C 11.371094 6.195312 11.773438 6.59375 11.773438 7.078125 C 11.773438 7.519531 11.445312 7.886719 11.019531 7.953125 Z M 11.019531 7.953125 "/>
<path fill="black" d="M 7.269531 9.089844 C 7.53125 8.988281 7.828125 9.113281 7.933594 9.375 C 8.125 9.859375 8.503906 9.996094 8.796875 9.949219 C 9.082031 9.898438 9.378906 9.664062 9.378906 9.136719 C 9.378906 8.855469 9.605469 8.628906 9.886719 8.628906 C 10.167969 8.628906 10.398438 8.855469 10.398438 9.136719 C 10.398438 10.140625 9.757812 10.816406 8.96875 10.949219 C 8.1875 11.078125 7.351562 10.664062 6.988281 9.75 C 6.882812 9.488281 7.011719 9.195312 7.269531 9.089844 Z M 7.269531 9.089844 "/>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.3848 9.30444C7.3848 9.30444 7.53254 10.2646 8.53248 10.0882C9.53242 9.91193 9.36378 8.95549 9.36378 8.95549" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5.54155 5.54157C6.12355 4.90104 6.01688 2.62541 7.22875 2.3985C8.44063 2.17158 9.19097 4.33148 9.91982 4.6814C10.6487 5.03133 12.8517 4.3028 13.4381 5.38734C14.0244 6.47188 12.1395 7.95973 12.026 8.64088C11.9126 9.32203 13.3614 11.2416 12.4675 12.1701C11.5736 13.0986 9.73005 11.7545 8.90486 11.8834C8.07966 12.0123 6.79244 13.9095 5.67367 13.3502C4.55491 12.7909 5.16702 10.5455 4.82437 9.87612C4.48171 9.20673 2.34028 8.54978 2.4525 7.35049C2.56471 6.15121 4.95956 6.1821 5.54155 5.54157Z" stroke="#FF7676" stroke-opacity="0.52" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5.54155 5.54157C6.12355 4.90104 6.01688 2.62541 7.22875 2.3985C8.44063 2.17158 9.19097 4.33148 9.91982 4.6814C10.6487 5.03133 12.8517 4.3028 13.4381 5.38734C14.0244 6.47188 12.1395 7.95973 12.026 8.64088C11.9126 9.32203 13.3614 11.2416 12.4675 12.1701C11.5736 13.0986 9.73005 11.7545 8.90486 11.8834C8.07966 12.0123 6.79244 13.9095 5.67367 13.3502C4.55491 12.7909 5.16702 10.5455 4.82437 9.87612C4.48171 9.20673 2.34028 8.54978 2.4525 7.35049C2.56471 6.15121 4.95956 6.1821 5.54155 5.54157Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="6.25098" cy="7.75" r="0.75" fill="black"/>
<circle cx="10.1035" cy="7.25" r="0.75" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7 21V12M7 12H3M7 12H11" stroke="black" stroke-width="2" stroke-linecap="round"/>
<path d="M21 19L16 19L16 14" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7.99987 5.07027L7.49915 4.20467L7.99987 5.07027ZM6.04652 5.25026C5.63245 5.61573 5.59305 6.24766 5.95851 6.66173C6.32398 7.0758 6.95592 7.1152 7.36999 6.74974L6.04652 5.25026ZM11.9999 5C15.8659 5 18.9999 8.13401 18.9999 12H20.9999C20.9999 7.02944 16.9705 3 11.9999 3V5ZM18.9999 12C18.9999 14.2101 17.9768 16.1806 16.3744 17.4651L17.6254 19.0256C19.6809 17.3779 20.9999 14.8426 20.9999 12H18.9999ZM8.5006 5.93588C9.5292 5.34086 10.7232 5 11.9999 5V3C10.3623 3 8.82395 3.4383 7.49915 4.20467L8.5006 5.93588ZM7.36999 6.74974C7.71803 6.44255 8.09667 6.16954 8.5006 5.93588L7.49915 4.20467C6.9797 4.50515 6.49329 4.85593 6.04652 5.25026L7.36999 6.74974Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 979 B

1
assets/icons/wand.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-wand"><path d="M15 4V2"/><path d="M15 16v-2"/><path d="M8 9h2"/><path d="M20 9h2"/><path d="M17.8 11.8 19 13"/><path d="M15 9h.01"/><path d="M17.8 6.2 19 5"/><path d="m3 21 9-9"/><path d="M12.2 6.2 11 5"/></svg>

After

Width:  |  Height:  |  Size: 414 B

View File

@@ -25,8 +25,8 @@
"alt-j": ["editor::SelectNext", { "replace_newest": false }],
"alt-shift-j": ["editor::SelectPrevious", { "replace_newest": false }],
"ctrl-/": ["editor::ToggleComments", { "advance_downwards": true }],
"alt-up": "editor::SelectLargerSyntaxNode",
"alt-down": "editor::SelectSmallerSyntaxNode",
"ctrl-w": "editor::SelectLargerSyntaxNode",
"ctrl-shift-w": "editor::SelectSmallerSyntaxNode",
"shift-alt-up": "editor::MoveLineUp",
"shift-alt-down": "editor::MoveLineDown",
"ctrl-alt-l": "editor::Format",

View File

@@ -24,8 +24,8 @@
"ctrl-g": ["editor::SelectNext", { "replace_newest": false }],
"ctrl-cmd-g": ["editor::SelectPrevious", { "replace_newest": false }],
"cmd-/": ["editor::ToggleComments", { "advance_downwards": true }],
"alt-up": "editor::SelectLargerSyntaxNode",
"alt-down": "editor::SelectSmallerSyntaxNode",
"cmd-up": "editor::SelectLargerSyntaxNode",
"cmd-down": "editor::SelectSmallerSyntaxNode",
"shift-alt-up": "editor::MoveLineUp",
"shift-alt-down": "editor::MoveLineDown",
"cmd-alt-l": "editor::Format",
@@ -58,6 +58,12 @@
"alt-enter": "editor::ToggleCodeActions"
}
},
{
"context": "BufferSearchBar > Editor",
"bindings": {
"shift-enter": "search::SelectPrevMatch"
}
},
{
"context": "Workspace",
"bindings": {

View File

@@ -127,6 +127,9 @@
"shift-h": "vim::WindowTop",
"shift-m": "vim::WindowMiddle",
"shift-l": "vim::WindowBottom",
"q": "vim::ToggleRecord",
"shift-q": "vim::ReplayLastRecording",
"@": ["vim::PushOperator", "ReplayRegister"],
// z commands
"z enter": ["workspace::SendKeystrokes", "z t ^"],
"z -": ["workspace::SendKeystrokes", "z b ^"],
@@ -137,14 +140,14 @@
"z .": ["workspace::SendKeystrokes", "z z ^"],
"z b": "editor::ScrollCursorBottom",
"z a": "editor::ToggleFold",
"z A": "editor::ToggleFoldRecursive",
"z shift-a": "editor::ToggleFoldRecursive",
"z c": "editor::Fold",
"z C": "editor::FoldRecursive",
"z shift-c": "editor::FoldRecursive",
"z o": "editor::UnfoldLines",
"z O": "editor::UnfoldRecursive",
"z shift-o": "editor::UnfoldRecursive",
"z f": "editor::FoldSelectedRanges",
"z M": "editor::FoldAll",
"z R": "editor::UnfoldAll",
"z shift-m": "editor::FoldAll",
"z shift-r": "editor::UnfoldAll",
"shift-z shift-q": ["pane::CloseActiveItem", { "saveIntent": "skip" }],
"shift-z shift-z": ["pane::CloseActiveItem", { "saveIntent": "saveAll" }],
// Count support
@@ -157,51 +160,6 @@
"7": ["vim::Number", 7],
"8": ["vim::Number", 8],
"9": ["vim::Number", 9],
// window related commands (ctrl-w X)
"ctrl-w": null,
"ctrl-w left": ["workspace::ActivatePaneInDirection", "Left"],
"ctrl-w right": ["workspace::ActivatePaneInDirection", "Right"],
"ctrl-w up": ["workspace::ActivatePaneInDirection", "Up"],
"ctrl-w down": ["workspace::ActivatePaneInDirection", "Down"],
"ctrl-w h": ["workspace::ActivatePaneInDirection", "Left"],
"ctrl-w l": ["workspace::ActivatePaneInDirection", "Right"],
"ctrl-w k": ["workspace::ActivatePaneInDirection", "Up"],
"ctrl-w j": ["workspace::ActivatePaneInDirection", "Down"],
"ctrl-w ctrl-h": ["workspace::ActivatePaneInDirection", "Left"],
"ctrl-w ctrl-l": ["workspace::ActivatePaneInDirection", "Right"],
"ctrl-w ctrl-k": ["workspace::ActivatePaneInDirection", "Up"],
"ctrl-w ctrl-j": ["workspace::ActivatePaneInDirection", "Down"],
"ctrl-w shift-left": ["workspace::SwapPaneInDirection", "Left"],
"ctrl-w shift-right": ["workspace::SwapPaneInDirection", "Right"],
"ctrl-w shift-up": ["workspace::SwapPaneInDirection", "Up"],
"ctrl-w shift-down": ["workspace::SwapPaneInDirection", "Down"],
"ctrl-w shift-h": ["workspace::SwapPaneInDirection", "Left"],
"ctrl-w shift-l": ["workspace::SwapPaneInDirection", "Right"],
"ctrl-w shift-k": ["workspace::SwapPaneInDirection", "Up"],
"ctrl-w shift-j": ["workspace::SwapPaneInDirection", "Down"],
"ctrl-w g t": "pane::ActivateNextItem",
"ctrl-w ctrl-g t": "pane::ActivateNextItem",
"ctrl-w g shift-t": "pane::ActivatePrevItem",
"ctrl-w ctrl-g shift-t": "pane::ActivatePrevItem",
"ctrl-w w": "workspace::ActivateNextPane",
"ctrl-w ctrl-w": "workspace::ActivateNextPane",
"ctrl-w p": "workspace::ActivatePreviousPane",
"ctrl-w ctrl-p": "workspace::ActivatePreviousPane",
"ctrl-w shift-w": "workspace::ActivatePreviousPane",
"ctrl-w ctrl-shift-w": "workspace::ActivatePreviousPane",
"ctrl-w v": "pane::SplitVertical",
"ctrl-w ctrl-v": "pane::SplitVertical",
"ctrl-w s": "pane::SplitHorizontal",
"ctrl-w shift-s": "pane::SplitHorizontal",
"ctrl-w ctrl-s": "pane::SplitHorizontal",
"ctrl-w c": "pane::CloseAllItems",
"ctrl-w ctrl-c": "pane::CloseAllItems",
"ctrl-w q": "pane::CloseAllItems",
"ctrl-w ctrl-q": "pane::CloseAllItems",
"ctrl-w o": "workspace::CloseInactiveTabsAndPanes",
"ctrl-w ctrl-o": "workspace::CloseInactiveTabsAndPanes",
"ctrl-w n": "workspace::NewFileSplitHorizontal",
"ctrl-w ctrl-n": "workspace::NewFileSplitHorizontal",
"ctrl-w d": "editor::GoToDefinitionSplit",
"ctrl-w g d": "editor::GoToDefinitionSplit",
"ctrl-w shift-d": "editor::GoToTypeDefinitionSplit",
@@ -251,9 +209,6 @@
"\"": ["vim::PushOperator", "Register"],
"g q": ["vim::PushOperator", "Rewrap"],
"g w": ["vim::PushOperator", "Rewrap"],
"q": "vim::ToggleRecord",
"shift-q": "vim::ReplayLastRecording",
"@": ["vim::PushOperator", "ReplayRegister"],
"ctrl-pagedown": "pane::ActivateNextItem",
"ctrl-pageup": "pane::ActivatePrevItem",
"insert": "vim::InsertBefore",
@@ -279,7 +234,7 @@
"bindings": {
":": "vim::VisualCommand",
"u": "vim::ConvertToLowerCase",
"U": "vim::ConvertToUpperCase",
"shift-u": "vim::ConvertToUpperCase",
"o": "vim::OtherEnd",
"shift-o": "vim::OtherEnd",
"d": "vim::VisualDelete",
@@ -303,8 +258,8 @@
"g ctrl-x": ["vim::Decrement", { "step": true }],
"shift-i": "vim::InsertBefore",
"shift-a": "vim::InsertAfter",
"g I": "vim::VisualInsertFirstNonWhiteSpace",
"g A": "vim::VisualInsertEndOfLine",
"g shift-i": "vim::VisualInsertFirstNonWhiteSpace",
"g shift-a": "vim::VisualInsertEndOfLine",
"shift-j": "vim::JoinLines",
"r": ["vim::PushOperator", "Replace"],
"ctrl-c": ["vim::SwitchMode", "Normal"],
@@ -339,6 +294,10 @@
"ctrl-t": "vim::Indent",
"ctrl-d": "vim::Outdent",
"ctrl-k": ["vim::PushOperator", { "Digraph": {} }],
"ctrl-v": ["vim::PushOperator", { "Literal": {} }],
"ctrl-shift-v": "editor::Paste", // note: this is *very* similar to ctrl-v in vim, but ctrl-shift-v on linux is the typical shortcut for paste when ctrl-v is already in use.
"ctrl-q": ["vim::PushOperator", { "Literal": {} }],
"ctrl-shift-q": ["vim::PushOperator", { "Literal": {} }],
"ctrl-r": ["vim::PushOperator", "Register"],
"insert": "vim::ToggleReplace"
}
@@ -357,6 +316,10 @@
"ctrl-c": "vim::NormalBefore",
"ctrl-[": "vim::NormalBefore",
"ctrl-k": ["vim::PushOperator", { "Digraph": {} }],
"ctrl-v": ["vim::PushOperator", { "Literal": {} }],
"ctrl-shift-v": "editor::Paste", // note: this is *very* similar to ctrl-v in vim, but ctrl-shift-v on linux is the typical shortcut for paste when ctrl-v is already in use.
"ctrl-q": ["vim::PushOperator", { "Literal": {} }],
"ctrl-shift-q": ["vim::PushOperator", { "Literal": {} }],
"backspace": "vim::UndoReplace",
"tab": "vim::Tab",
"enter": "vim::Enter",
@@ -371,7 +334,9 @@
"escape": "vim::ClearOperators",
"ctrl-c": "vim::ClearOperators",
"ctrl-[": "vim::ClearOperators",
"ctrl-k": ["vim::PushOperator", { "Digraph": {} }]
"ctrl-k": ["vim::PushOperator", { "Digraph": {} }],
"ctrl-v": ["vim::PushOperator", { "Literal": {} }],
"ctrl-q": ["vim::PushOperator", { "Literal": {} }]
}
},
{
@@ -399,12 +364,14 @@
"b": "vim::Parentheses",
"[": "vim::SquareBrackets",
"]": "vim::SquareBrackets",
"r": "vim::SquareBrackets",
"{": "vim::CurlyBrackets",
"}": "vim::CurlyBrackets",
"shift-b": "vim::CurlyBrackets",
"<": "vim::AngleBrackets",
">": "vim::AngleBrackets",
"a": "vim::Argument"
"a": "vim::AngleBrackets",
"g": "vim::Argument"
}
},
{
@@ -485,6 +452,49 @@
"c": "vim::CurrentLine"
}
},
{
"context": "vim_mode == literal",
"bindings": {
"ctrl-@": ["vim::Literal", ["ctrl-@", "\u0000"]],
"ctrl-a": ["vim::Literal", ["ctrl-a", "\u0001"]],
"ctrl-b": ["vim::Literal", ["ctrl-b", "\u0002"]],
"ctrl-c": ["vim::Literal", ["ctrl-c", "\u0003"]],
"ctrl-d": ["vim::Literal", ["ctrl-d", "\u0004"]],
"ctrl-e": ["vim::Literal", ["ctrl-e", "\u0005"]],
"ctrl-f": ["vim::Literal", ["ctrl-f", "\u0006"]],
"ctrl-g": ["vim::Literal", ["ctrl-g", "\u0007"]],
"ctrl-h": ["vim::Literal", ["ctrl-h", "\u0008"]],
"ctrl-i": ["vim::Literal", ["ctrl-i", "\u0009"]],
"ctrl-j": ["vim::Literal", ["ctrl-j", "\u000A"]],
"ctrl-k": ["vim::Literal", ["ctrl-k", "\u000B"]],
"ctrl-l": ["vim::Literal", ["ctrl-l", "\u000C"]],
"ctrl-m": ["vim::Literal", ["ctrl-m", "\u000D"]],
"ctrl-n": ["vim::Literal", ["ctrl-n", "\u000E"]],
"ctrl-o": ["vim::Literal", ["ctrl-o", "\u000F"]],
"ctrl-p": ["vim::Literal", ["ctrl-p", "\u0010"]],
"ctrl-q": ["vim::Literal", ["ctrl-q", "\u0011"]],
"ctrl-r": ["vim::Literal", ["ctrl-r", "\u0012"]],
"ctrl-s": ["vim::Literal", ["ctrl-s", "\u0013"]],
"ctrl-t": ["vim::Literal", ["ctrl-t", "\u0014"]],
"ctrl-u": ["vim::Literal", ["ctrl-u", "\u0015"]],
"ctrl-v": ["vim::Literal", ["ctrl-v", "\u0016"]],
"ctrl-w": ["vim::Literal", ["ctrl-w", "\u0017"]],
"ctrl-x": ["vim::Literal", ["ctrl-x", "\u0018"]],
"ctrl-y": ["vim::Literal", ["ctrl-y", "\u0019"]],
"ctrl-z": ["vim::Literal", ["ctrl-z", "\u001A"]],
"ctrl-[": ["vim::Literal", ["ctrl-[", "\u001B"]],
"ctrl-\\": ["vim::Literal", ["ctrl-\\", "\u001C"]],
"ctrl-]": ["vim::Literal", ["ctrl-]", "\u001D"]],
"ctrl-^": ["vim::Literal", ["ctrl-^", "\u001E"]],
"ctrl-_": ["vim::Literal", ["ctrl-_", "\u001F"]],
"escape": ["vim::Literal", ["escape", "\u001B"]],
"enter": ["vim::Literal", ["enter", "\u000D"]],
"tab": ["vim::Literal", ["tab", "\u0009"]],
// zed extensions:
"backspace": ["vim::Literal", ["backspace", "\u0008"]],
"delete": ["vim::Literal", ["delete", "\u007F"]]
}
},
{
"context": "BufferSearchBar && !in_replace",
"bindings": {
@@ -493,7 +503,57 @@
}
},
{
"context": "EmptyPane || SharedScreen",
"context": "ProjectPanel || CollabPanel || OutlinePanel || ChatPanel || VimControl || EmptyPane || SharedScreen || MarkdownPreview || KeyContextView",
"bindings": {
// window related commands (ctrl-w X)
"ctrl-w": null,
"ctrl-w left": ["workspace::ActivatePaneInDirection", "Left"],
"ctrl-w right": ["workspace::ActivatePaneInDirection", "Right"],
"ctrl-w up": ["workspace::ActivatePaneInDirection", "Up"],
"ctrl-w down": ["workspace::ActivatePaneInDirection", "Down"],
"ctrl-w h": ["workspace::ActivatePaneInDirection", "Left"],
"ctrl-w l": ["workspace::ActivatePaneInDirection", "Right"],
"ctrl-w k": ["workspace::ActivatePaneInDirection", "Up"],
"ctrl-w j": ["workspace::ActivatePaneInDirection", "Down"],
"ctrl-w ctrl-h": ["workspace::ActivatePaneInDirection", "Left"],
"ctrl-w ctrl-l": ["workspace::ActivatePaneInDirection", "Right"],
"ctrl-w ctrl-k": ["workspace::ActivatePaneInDirection", "Up"],
"ctrl-w ctrl-j": ["workspace::ActivatePaneInDirection", "Down"],
"ctrl-w shift-left": ["workspace::SwapPaneInDirection", "Left"],
"ctrl-w shift-right": ["workspace::SwapPaneInDirection", "Right"],
"ctrl-w shift-up": ["workspace::SwapPaneInDirection", "Up"],
"ctrl-w shift-down": ["workspace::SwapPaneInDirection", "Down"],
"ctrl-w shift-h": ["workspace::SwapPaneInDirection", "Left"],
"ctrl-w shift-l": ["workspace::SwapPaneInDirection", "Right"],
"ctrl-w shift-k": ["workspace::SwapPaneInDirection", "Up"],
"ctrl-w shift-j": ["workspace::SwapPaneInDirection", "Down"],
"ctrl-w g t": "pane::ActivateNextItem",
"ctrl-w ctrl-g t": "pane::ActivateNextItem",
"ctrl-w g shift-t": "pane::ActivatePrevItem",
"ctrl-w ctrl-g shift-t": "pane::ActivatePrevItem",
"ctrl-w w": "workspace::ActivateNextPane",
"ctrl-w ctrl-w": "workspace::ActivateNextPane",
"ctrl-w p": "workspace::ActivatePreviousPane",
"ctrl-w ctrl-p": "workspace::ActivatePreviousPane",
"ctrl-w shift-w": "workspace::ActivatePreviousPane",
"ctrl-w ctrl-shift-w": "workspace::ActivatePreviousPane",
"ctrl-w v": "pane::SplitVertical",
"ctrl-w ctrl-v": "pane::SplitVertical",
"ctrl-w s": "pane::SplitHorizontal",
"ctrl-w shift-s": "pane::SplitHorizontal",
"ctrl-w ctrl-s": "pane::SplitHorizontal",
"ctrl-w c": "pane::CloseAllItems",
"ctrl-w ctrl-c": "pane::CloseAllItems",
"ctrl-w q": "pane::CloseAllItems",
"ctrl-w ctrl-q": "pane::CloseAllItems",
"ctrl-w o": "workspace::CloseInactiveTabsAndPanes",
"ctrl-w ctrl-o": "workspace::CloseInactiveTabsAndPanes",
"ctrl-w n": "workspace::NewFileSplitHorizontal",
"ctrl-w ctrl-n": "workspace::NewFileSplitHorizontal"
}
},
{
"context": "EmptyPane || SharedScreen || MarkdownPreview || KeyContextView",
"bindings": {
":": "command_palette::Toggle",
"g /": "pane::DeploySearch"

View File

@@ -68,9 +68,17 @@
"ui_font_size": 16,
// How much to fade out unused code.
"unnecessary_code_fade": 0.3,
// The factor to grow the active pane by. Defaults to 1.0
// which gives the same size as all other panes.
"active_pane_magnification": 1.0,
// Active pane styling settings.
"active_pane_modifiers": {
// The factor to grow the active pane by. Defaults to 1.0
// which gives the same size as all other panes.
"magnification": 1.0,
// Inset border size of the active pane, in pixels.
"border_size": 0.0,
// Opacity of the inactive panes. 0 means transparent, 1 means opaque.
// Values are clamped to the [0.0, 1.0] range.
"inactive_opacity": 1.0
},
// The direction that you want to split panes horizontally. Defaults to "up"
"pane_split_direction_horizontal": "up",
// The direction that you want to split panes horizontally. Defaults to "left"
@@ -152,7 +160,7 @@
"show_signature_help_after_edits": true,
// 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
// if 'soft_wrap' is set to 'preferred_line_length', and will show any
// additional guides as specified by the 'wrap_guides' setting.
"show_wrap_guides": true,
// Character counts at which to show wrap guides in the editor.
@@ -174,6 +182,8 @@
// bracket, brace, single or double quote characters.
// For example, when you select text and type (, Zed will surround the text with ().
"use_auto_surround": true,
// Whether indentation of pasted content should be adjusted based on the context.
"auto_indent_on_paste": true,
// Controls how the editor handles the autoclosed characters.
// When set to `false`(default), skipping over and auto-removing of the closing characters
// happen only for auto-inserted characters.
@@ -414,6 +424,23 @@
// 2. Never show indent guides:
// "never"
"show": "always"
},
/// Scrollbar-related settings
"scrollbar": {
/// When to show the scrollbar in the project panel.
/// This setting can take four values:
///
/// 1. null (default): Inherit editor settings
/// 2. Show the scrollbar if there's important information or
/// follow the system's configured behavior (default):
/// "auto"
/// 3. Match the system's configured behavior:
/// "system"
/// 4. Always show the scrollbar:
/// "always"
/// 5. Never show the scrollbar:
/// "never"
"show": null
}
},
"collaboration_panel": {
@@ -458,7 +485,7 @@
"default_width": 640,
// Default height when the assistant is docked to the bottom.
"default_height": 320,
// The default model to use when creating new contexts.
// The default model to use when creating new chats.
"default_model": {
// The provider to use.
"provider": "zed.dev",
@@ -635,6 +662,12 @@
// Sets a delay after which the inline blame information is shown.
// Delay is restarted with every cursor movement.
// "delay_ms": 600
//
// Whether or not do display the git commit summary on the same line.
// "show_commit_summary": false
//
// The minimum column number to show the inline blame information at
// "min_column": 0
}
},
// Configuration for how direnv configuration should be loaded. May take 2 values:
@@ -797,7 +830,6 @@
"tasks": {
"variables": {}
},
"toolchain": { "name": "default", "path": "default" },
// An object whose keys are language names, and whose values
// are arrays of filenames or extensions of files that should
// use those languages.

View File

@@ -16,6 +16,7 @@
"allow_concurrent_runs": false,
// What to do with the terminal pane and tab, after the command was started:
// * `always` — always show the terminal pane, add and focus the corresponding task's tab in it (default)
// * `no_focus` — always show the terminal pane, add/reuse the task's tab there, but don't focus it
// * `never` — avoid changing current terminal pane focus, but still add/reuse the task's tab there
"reveal": "always",
// What to do with the terminal pane and tab, after the command had finished:

View File

@@ -16,7 +16,7 @@ doctest = false
anyhow.workspace = true
auto_update.workspace = true
editor.workspace = true
extension.workspace = true
extension_host.workspace = true
futures.workspace = true
gpui.workspace = true
language.workspace = true

View File

@@ -1,6 +1,6 @@
use auto_update::{AutoUpdateStatus, AutoUpdater, DismissErrorMessage};
use editor::Editor;
use extension::ExtensionStore;
use extension_host::ExtensionStore;
use futures::StreamExt;
use gpui::{
actions, percentage, Animation, AnimationExt as _, AppContext, CursorStyle, EventEmitter,
@@ -352,7 +352,10 @@ impl ActivityIndicator {
.into_any_element(),
),
message: format!("Formatting failed: {}. Click to see logs.", failure),
on_click: Some(Arc::new(|_, cx| {
on_click: Some(Arc::new(|indicator, cx| {
indicator.project.update(cx, |project, cx| {
project.reset_last_formatting_failure(cx);
});
cx.dispatch_action(Box::new(workspace::OpenLog));
})),
});

View File

@@ -41,16 +41,19 @@ use prompts::PromptLoadingParams;
use semantic_index::{CloudEmbeddingProvider, SemanticDb};
use serde::{Deserialize, Serialize};
use settings::{update_settings_file, Settings, SettingsStore};
use slash_command::search_command::SearchSlashCommandFeatureFlag;
use slash_command::{
auto_command, cargo_workspace_command, context_server_command, default_command, delta_command,
diagnostics_command, docs_command, fetch_command, file_command, now_command, project_command,
prompt_command, search_command, symbols_command, tab_command, terminal_command,
prompt_command, search_command, selection_command, symbols_command, tab_command,
terminal_command,
};
use std::path::PathBuf;
use std::sync::Arc;
pub(crate) use streaming_diff::*;
use util::ResultExt;
use crate::slash_command::streaming_example_command;
use crate::slash_command_settings::SlashCommandSettings;
actions!(
@@ -210,21 +213,23 @@ pub fn init(
});
}
cx.spawn(|mut cx| {
let client = client.clone();
async move {
let embedding_provider = CloudEmbeddingProvider::new(client.clone());
let semantic_index = SemanticDb::new(
paths::embeddings_dir().join("semantic-index-db.0.mdb"),
Arc::new(embedding_provider),
&mut cx,
)
.await?;
if cx.has_flag::<SearchSlashCommandFeatureFlag>() {
cx.spawn(|mut cx| {
let client = client.clone();
async move {
let embedding_provider = CloudEmbeddingProvider::new(client.clone());
let semantic_index = SemanticDb::new(
paths::embeddings_dir().join("semantic-index-db.0.mdb"),
Arc::new(embedding_provider),
&mut cx,
)
.await?;
cx.update(|cx| cx.set_global(semantic_index))
}
})
.detach();
cx.update(|cx| cx.set_global(semantic_index))
}
})
.detach();
}
context_store::init(&client.clone().into());
prompt_library::init(cx);
@@ -435,6 +440,7 @@ fn register_slash_commands(prompt_builder: Option<Arc<PromptBuilder>>, cx: &mut
slash_command_registry
.register_command(cargo_workspace_command::CargoWorkspaceSlashCommand, true);
slash_command_registry.register_command(prompt_command::PromptSlashCommand, true);
slash_command_registry.register_command(selection_command::SelectionCommand, true);
slash_command_registry.register_command(default_command::DefaultSlashCommand, false);
slash_command_registry.register_command(terminal_command::TerminalSlashCommand, true);
slash_command_registry.register_command(now_command::NowSlashCommand, false);
@@ -468,6 +474,19 @@ fn register_slash_commands(prompt_builder: Option<Arc<PromptBuilder>>, cx: &mut
})
.detach();
cx.observe_flag::<streaming_example_command::StreamingExampleSlashCommandFeatureFlag, _>({
let slash_command_registry = slash_command_registry.clone();
move |is_enabled, _cx| {
if is_enabled {
slash_command_registry.register_command(
streaming_example_command::StreamingExampleSlashCommand,
false,
);
}
}
})
.detach();
update_slash_commands_from_settings(cx);
cx.observe_global::<SettingsStore>(update_slash_commands_from_settings)
.detach();

View File

@@ -64,6 +64,7 @@ use serde::{Deserialize, Serialize};
use settings::{update_settings_file, Settings};
use smol::stream::StreamExt;
use std::{
any::TypeId,
borrow::Cow,
cmp,
ops::{ControlFlow, Range},
@@ -73,12 +74,11 @@ use std::{
};
use terminal_view::{terminal_panel::TerminalPanel, TerminalView};
use text::SelectionGoal;
use ui::TintColor;
use ui::{
prelude::*,
utils::{format_distance_from_now, DateTimeType},
Avatar, ButtonLike, ContextMenu, Disclosure, ElevationIndex, KeyBinding, ListItem,
ListItemSpacing, PopoverMenu, PopoverMenuHandle, Tooltip,
ListItemSpacing, PopoverMenu, PopoverMenuHandle, TintColor, Tooltip,
};
use util::{maybe, ResultExt};
use workspace::{
@@ -443,27 +443,24 @@ impl AssistantPanel {
);
let _pane = cx.view().clone();
let right_children = h_flex()
.gap(Spacing::Small.rems(cx))
.gap(Spacing::XSmall.rems(cx))
.child(
IconButton::new("new-context", IconName::Plus)
IconButton::new("new-chat", IconName::Plus)
.on_click(
cx.listener(|_, _, cx| {
cx.dispatch_action(NewContext.boxed_clone())
}),
)
.tooltip(move |cx| {
Tooltip::for_action_in(
"New Context",
&NewContext,
&focus_handle,
cx,
)
Tooltip::for_action_in("New Chat", &NewContext, &focus_handle, cx)
}),
)
.child(
PopoverMenu::new("assistant-panel-popover-menu")
.trigger(
IconButton::new("menu", IconName::Menu).icon_size(IconSize::Small),
IconButton::new("menu", IconName::EllipsisVertical)
.icon_size(IconSize::Small)
.tooltip(|cx| Tooltip::text("Toggle Assistant Menu", cx)),
)
.menu(move |cx| {
let zoom_label = if _pane.read(cx).is_zoomed() {
@@ -474,7 +471,7 @@ impl AssistantPanel {
let focus_handle = _pane.focus_handle(cx);
Some(ContextMenu::build(cx, move |menu, _| {
menu.context(focus_handle.clone())
.action("New Context", Box::new(NewContext))
.action("New Chat", Box::new(NewContext))
.action("History", Box::new(DeployHistory))
.action("Prompt Library", Box::new(DeployPromptLibrary))
.action("Configure", Box::new(ShowConfiguration))
@@ -1462,6 +1459,7 @@ type MessageHeader = MessageMetadata;
#[derive(Clone)]
enum AssistError {
FileRequired,
PaymentRequired,
MaxMonthlySpendReached,
Message(SharedString),
@@ -1497,7 +1495,7 @@ pub struct ContextEditor {
dragged_file_worktrees: Vec<Model<Worktree>>,
}
const DEFAULT_TAB_TITLE: &str = "New Context";
const DEFAULT_TAB_TITLE: &str = "New Chat";
const MAX_TAB_TITLE_LEN: usize = 16;
impl ContextEditor {
@@ -1628,7 +1626,10 @@ impl ContextEditor {
self.last_error = None;
if let Some(user_message) = self
if request_type == RequestType::SuggestEdits && !self.context.read(cx).contains_files(cx) {
self.last_error = Some(AssistError::FileRequired);
cx.notify();
} else if let Some(user_message) = self
.context
.update(cx, |context, cx| context.assist(request_type, cx))
{
@@ -2595,57 +2596,108 @@ impl ContextEditor {
let context = self.context.clone();
move |cx| {
let message_id = MessageId(message.timestamp);
let show_spinner = message.role == Role::Assistant
let llm_loading = message.role == Role::Assistant
&& message.status == MessageStatus::Pending;
let label = match message.role {
Role::User => {
Label::new("You").color(Color::Default).into_any_element()
}
let (label, spinner, note) = match message.role {
Role::User => (
Label::new("You").color(Color::Default).into_any_element(),
None,
None,
),
Role::Assistant => {
let label = Label::new("Assistant").color(Color::Info);
if show_spinner {
label
let base_label = Label::new("Assistant").color(Color::Info);
let mut spinner = None;
let mut note = None;
let animated_label = if llm_loading {
base_label
.with_animation(
"pulsating-label",
Animation::new(Duration::from_secs(2))
.repeat()
.with_easing(pulsating_between(0.4, 0.8)),
.with_easing(pulsating_between(0.3, 0.9)),
|label, delta| label.alpha(delta),
)
.into_any_element()
} else {
label.into_any_element()
base_label.into_any_element()
};
if llm_loading {
spinner = Some(
Icon::new(IconName::ArrowCircle)
.size(IconSize::XSmall)
.color(Color::Muted)
.with_animation(
"arrow-circle",
Animation::new(Duration::from_secs(2)).repeat(),
|icon, delta| {
icon.transform(Transformation::rotate(
percentage(delta),
))
},
)
.into_any_element(),
);
note = Some(
div()
.font(
theme::ThemeSettings::get_global(cx)
.buffer_font
.clone(),
)
.child(
Label::new("Press 'esc' to cancel")
.color(Color::Muted)
.size(LabelSize::XSmall),
)
.into_any_element(),
);
}
(animated_label, spinner, note)
}
Role::System => Label::new("System")
.color(Color::Warning)
.into_any_element(),
Role::System => (
Label::new("System")
.color(Color::Warning)
.into_any_element(),
None,
None,
),
};
let sender = ButtonLike::new("role")
.style(ButtonStyle::Filled)
.child(label)
.tooltip(|cx| {
Tooltip::with_meta(
"Toggle message role",
None,
"Available roles: You (User), Assistant, System",
cx,
)
})
.on_click({
let context = context.clone();
move |_, cx| {
context.update(cx, |context, cx| {
context.cycle_message_roles(
HashSet::from_iter(Some(message_id)),
let sender = h_flex()
.items_center()
.gap_2()
.child(
ButtonLike::new("role")
.style(ButtonStyle::Filled)
.child(
h_flex()
.items_center()
.gap_1p5()
.child(label)
.children(spinner),
)
.tooltip(|cx| {
Tooltip::with_meta(
"Toggle message role",
None,
"Available roles: You (User), Assistant, System",
cx,
)
})
}
});
.on_click({
let context = context.clone();
move |_, cx| {
context.update(cx, |context, cx| {
context.cycle_message_roles(
HashSet::from_iter(Some(message_id)),
cx,
)
})
}
}),
)
.children(note);
h_flex()
.id(("message_header", message_id.as_u64()))
@@ -2970,97 +3022,11 @@ impl ContextEditor {
let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
return;
};
let Some(editor) = workspace
.active_item(cx)
.and_then(|item| item.act_as::<Editor>(cx))
else {
let Some(creases) = selections_creases(workspace, cx) else {
return;
};
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 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 line_comment_prefix = start_language
.and_then(|l| l.default_scope().line_comment_prefixes().first().cloned());
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));
}
});
if creases.is_empty() {
return;
}
@@ -3740,6 +3706,7 @@ impl ContextEditor {
.elevation_2(cx)
.occlude()
.child(match last_error {
AssistError::FileRequired => self.render_file_required_error(cx),
AssistError::PaymentRequired => self.render_payment_required_error(cx),
AssistError::MaxMonthlySpendReached => {
self.render_max_monthly_spend_reached_error(cx)
@@ -3752,6 +3719,41 @@ impl ContextEditor {
)
}
fn render_file_required_error(&self, cx: &mut ViewContext<Self>) -> AnyElement {
v_flex()
.gap_0p5()
.child(
h_flex()
.gap_1p5()
.items_center()
.child(Icon::new(IconName::Warning).color(Color::Warning))
.child(
Label::new("Suggest Edits needs a file to edit").weight(FontWeight::MEDIUM),
),
)
.child(
div()
.id("error-message")
.max_h_24()
.overflow_y_scroll()
.child(Label::new(
"To include files, type /file or /tab in your prompt.",
)),
)
.child(
h_flex()
.justify_end()
.mt_1()
.child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
|this, _, cx| {
this.last_error = None;
cx.notify();
},
))),
)
.into_any()
}
fn render_payment_required_error(&self, cx: &mut ViewContext<Self>) -> AnyElement {
const ERROR_MESSAGE: &str = "Free tier exceeded. Subscribe and add payment to continue using Zed LLMs. You'll be billed at cost for tokens used.";
@@ -3915,6 +3917,99 @@ fn find_surrounding_code_block(snapshot: &BufferSnapshot, offset: usize) -> Opti
None
}
pub fn selections_creases(
workspace: &mut workspace::Workspace,
cx: &mut ViewContext<Workspace>,
) -> Option<Vec<(String, String)>> {
let editor = workspace
.active_item(cx)
.and_then(|item| item.act_as::<Editor>(cx))?;
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 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 line_comment_prefix = start_language
.and_then(|l| l.default_scope().line_comment_prefixes().first().cloned());
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));
}
});
Some(creases)
}
fn render_fold_icon_button(
editor: WeakView<Editor>,
icon: IconName,
@@ -3966,13 +4061,7 @@ impl Render for ContextEditor {
} else {
None
};
let focus_handle = self
.workspace
.update(cx, |workspace, cx| {
Some(workspace.active_item_as::<Editor>(cx)?.focus_handle(cx))
})
.ok()
.flatten();
v_flex()
.key_context("ContextEditor")
.capture_action(cx.listener(ContextEditor::cancel))
@@ -4020,28 +4109,7 @@ impl Render for ContextEditor {
.child(
h_flex()
.gap_1()
.child(render_inject_context_menu(cx.view().downgrade(), cx))
.child(
IconButton::new("quote-button", IconName::Quote)
.icon_size(IconSize::Small)
.on_click(|_, cx| {
cx.dispatch_action(QuoteSelection.boxed_clone());
})
.tooltip(move |cx| {
cx.new_view(|cx| {
Tooltip::new("Insert Selection").key_binding(
focus_handle.as_ref().and_then(|handle| {
KeyBinding::for_action_in(
&QuoteSelection,
&handle,
cx,
)
}),
)
})
.into()
}),
),
.child(render_inject_context_menu(cx.view().downgrade(), cx)),
)
.child(
h_flex()
@@ -4115,6 +4183,21 @@ impl Item for ContextEditor {
fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
self.editor.update(cx, Item::deactivated)
}
fn act_as_type<'a>(
&'a self,
type_id: TypeId,
self_handle: &'a View<Self>,
_: &'a AppContext,
) -> Option<AnyView> {
if type_id == TypeId::of::<Self>() {
Some(self_handle.to_any())
} else if type_id == TypeId::of::<Editor>() {
Some(self.editor.to_any())
} else {
None
}
}
}
impl SearchableItem for ContextEditor {
@@ -4304,26 +4387,11 @@ impl FollowableItem for ContextEditor {
pub struct ContextEditorToolbarItem {
fs: Arc<dyn Fs>,
workspace: WeakView<Workspace>,
active_context_editor: Option<WeakView<ContextEditor>>,
model_summary_editor: View<Editor>,
model_selector_menu_handle: PopoverMenuHandle<Picker<ModelPickerDelegate>>,
}
fn active_editor_focus_handle(
workspace: &WeakView<Workspace>,
cx: &WindowContext<'_>,
) -> Option<FocusHandle> {
workspace.upgrade().and_then(|workspace| {
Some(
workspace
.read(cx)
.active_item_as::<Editor>(cx)?
.focus_handle(cx),
)
})
}
fn render_inject_context_menu(
active_context_editor: WeakView<ContextEditor>,
cx: &mut WindowContext<'_>,
@@ -4336,6 +4404,7 @@ fn render_inject_context_menu(
Button::new("trigger", "Add Context")
.icon(IconName::Plus)
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
.icon_position(IconPosition::Start)
.tooltip(|cx| Tooltip::text("Type / to insert via keyboard", cx)),
)
@@ -4349,7 +4418,6 @@ impl ContextEditorToolbarItem {
) -> Self {
Self {
fs: workspace.app_state().fs.clone(),
workspace: workspace.weak_handle(),
active_context_editor: None,
model_summary_editor,
model_selector_menu_handle,
@@ -4402,16 +4470,30 @@ impl ContextEditorToolbarItem {
impl Render for ContextEditorToolbarItem {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let left_side = h_flex()
.pl_1()
.gap_2()
.flex_1()
.min_w(rems(DEFAULT_TAB_TITLE.len() as f32))
.when(self.active_context_editor.is_some(), |left_side| {
left_side.child(self.model_summary_editor.clone())
});
.group("chat-title-group")
.pl_0p5()
.gap_1()
.items_center()
.flex_grow()
.child(
div()
.w_full()
.when(self.active_context_editor.is_some(), |left_side| {
left_side.child(self.model_summary_editor.clone())
}),
)
.child(
div().visible_on_hover("chat-title-group").child(
IconButton::new("regenerate-context", IconName::RefreshTitle)
.shape(ui::IconButtonShape::Square)
.tooltip(|cx| Tooltip::text("Regenerate Title", cx))
.on_click(cx.listener(move |_, _, cx| {
cx.emit(ContextEditorToolbarItemEvent::RegenerateSummary)
})),
),
);
let active_provider = LanguageModelRegistry::read_global(cx).active_provider();
let active_model = LanguageModelRegistry::read_global(cx).active_model();
let weak_self = cx.view().downgrade();
let right_side = h_flex()
.gap_2()
// TODO display this in a nicer way, once we have a design for it.
@@ -4424,7 +4506,6 @@ impl Render for ContextEditorToolbarItem {
// let scan_items_remaining = cx.update_global(|db: &mut SemanticDb, cx| {
// project.and_then(|project| db.remaining_summaries(&project, cx))
// });
// scan_items_remaining
// .map(|remaining_items| format!("Files to scan: {}", remaining_items))
// })
@@ -4446,9 +4527,13 @@ impl Render for ContextEditorToolbarItem {
(Some(provider), Some(model)) => h_flex()
.gap_1()
.child(
Icon::new(model.icon().unwrap_or_else(|| provider.icon()))
.color(Color::Muted)
.size(IconSize::XSmall),
Icon::new(
model
.icon()
.unwrap_or_else(|| provider.icon()),
)
.color(Color::Muted)
.size(IconSize::XSmall),
)
.child(
Label::new(model.name().0)
@@ -4474,71 +4559,7 @@ impl Render for ContextEditorToolbarItem {
)
.with_handle(self.model_selector_menu_handle.clone()),
)
.children(self.render_remaining_tokens(cx))
.child(
PopoverMenu::new("context-editor-popover")
.trigger(
IconButton::new("context-editor-trigger", IconName::EllipsisVertical)
.icon_size(IconSize::Small)
.tooltip(|cx| Tooltip::text("Open Context Options", cx)),
)
.menu({
let weak_self = weak_self.clone();
move |cx| {
let weak_self = weak_self.clone();
Some(ContextMenu::build(cx, move |menu, cx| {
let context = weak_self
.update(cx, |this, cx| {
active_editor_focus_handle(&this.workspace, cx)
})
.ok()
.flatten();
menu.when_some(context, |menu, context| menu.context(context))
.entry("Regenerate Context Title", None, {
let weak_self = weak_self.clone();
move |cx| {
weak_self
.update(cx, |_, cx| {
cx.emit(ContextEditorToolbarItemEvent::RegenerateSummary)
})
.ok();
}
})
.custom_entry(
|_| {
h_flex()
.w_full()
.justify_between()
.gap_2()
.child(Label::new("Insert Context"))
.child(Label::new("/ command").color(Color::Muted))
.into_any()
},
{
let weak_self = weak_self.clone();
move |cx| {
weak_self
.update(cx, |this, cx| {
if let Some(editor) =
&this.active_context_editor
{
editor
.update(cx, |this, cx| {
this.slash_menu_handle
.toggle(cx);
})
.ok();
}
})
.ok();
}
},
)
.action("Insert Selection", QuoteSelection.boxed_clone())
}))
}
}),
);
.children(self.render_remaining_tokens(cx));
h_flex()
.size_full()
@@ -4753,7 +4774,7 @@ impl ConfigurationView {
h_flex().justify_end().child(
Button::new(
SharedString::from(format!("new-context-{provider_id}")),
"Open new context",
"Open New Chat",
)
.icon_position(IconPosition::Start)
.icon(IconName::Plus)

View File

@@ -410,7 +410,7 @@ pub struct AssistantSettingsContentV2 {
///
/// Default: 320
default_height: Option<f32>,
/// The default model to use when creating new contexts.
/// The default model to use when creating new chats.
default_model: Option<LanguageModelSelection>,
/// Additional models with which to generate alternatives when performing inline assists.
inline_alternatives: Option<Vec<LanguageModelSelection>>,
@@ -498,11 +498,11 @@ pub struct LegacyAssistantSettingsContent {
///
/// Default: 320
pub default_height: Option<f32>,
/// The default OpenAI model to use when creating new contexts.
/// The default OpenAI model to use when creating new chats.
///
/// Default: gpt-4-1106-preview
pub default_open_ai_model: Option<OpenAiModel>,
/// OpenAI API base URL to use when creating new contexts.
/// OpenAI API base URL to use when creating new chats.
///
/// Default: https://api.openai.com/v1
pub openai_api_url: Option<String>,

View File

@@ -2,8 +2,9 @@
mod context_tests;
use crate::{
prompts::PromptBuilder, slash_command::SlashCommandLine, AssistantEdit, AssistantPatch,
AssistantPatchStatus, MessageId, MessageStatus,
prompts::PromptBuilder,
slash_command::{file_command::FileCommandMetadata, SlashCommandLine},
AssistantEdit, AssistantPatch, AssistantPatchStatus, MessageId, MessageStatus,
};
use anyhow::{anyhow, Context as _, Result};
use assistant_slash_command::{
@@ -23,6 +24,7 @@ use gpui::{
use language::{AnchorRangeExt, Bias, Buffer, LanguageRegistry, OffsetRangeExt, Point, ToOffset};
use language_model::{
logging::report_assistant_event,
provider::cloud::{MaxMonthlySpendReachedError, PaymentRequiredError},
LanguageModel, LanguageModelCacheConfiguration, LanguageModelCompletionEvent,
LanguageModelImage, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage,
@@ -66,7 +68,7 @@ impl ContextId {
}
}
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RequestType {
/// Request a normal chat response from the model.
Chat,
@@ -989,6 +991,20 @@ impl Context {
&self.slash_command_output_sections
}
pub fn contains_files(&self, cx: &AppContext) -> bool {
let buffer = self.buffer.read(cx);
self.slash_command_output_sections.iter().any(|section| {
section.is_valid(buffer)
&& section
.metadata
.as_ref()
.and_then(|metadata| {
serde_json::from_value::<FileCommandMetadata>(metadata.clone()).ok()
})
.is_some()
})
}
pub fn pending_tool_uses(&self) -> Vec<&PendingToolUse> {
self.pending_tool_uses_by_id.values().collect()
}
@@ -1036,7 +1052,9 @@ impl Context {
}
pub(crate) fn count_remaining_tokens(&mut self, cx: &mut ModelContext<Self>) {
let request = self.to_completion_request(RequestType::SuggestEdits, cx); // Conservatively assume SuggestEdits, since it takes more tokens.
// Assume it will be a Chat request, even though that takes fewer tokens (and risks going over the limit),
// because otherwise you see in the UI that your empty message has a bunch of tokens already used.
let request = self.to_completion_request(RequestType::Chat, cx);
let Some(model) = LanguageModelRegistry::read_global(cx).active_model() else {
return;
};
@@ -1940,6 +1958,7 @@ impl Context {
});
match event {
LanguageModelCompletionEvent::StartMessage { .. } => {}
LanguageModelCompletionEvent::Stop(reason) => {
stop_reason = reason;
}
@@ -2045,23 +2064,28 @@ impl Context {
None
};
if let Some(telemetry) = this.telemetry.as_ref() {
let language_name = this
.buffer
.read(cx)
.language()
.map(|language| language.name());
telemetry.report_assistant_event(AssistantEvent {
let language_name = this
.buffer
.read(cx)
.language()
.map(|language| language.name());
report_assistant_event(
AssistantEvent {
conversation_id: Some(this.id.0.clone()),
kind: AssistantKind::Panel,
phase: AssistantPhase::Response,
message_id: None,
model: model.telemetry_id(),
model_provider: model.provider_id().to_string(),
response_latency,
error_message,
language_name: language_name.map(|name| name.to_proto()),
});
}
},
this.telemetry.clone(),
cx.http_client(),
model.api_key(cx),
cx.background_executor(),
);
if let Ok(stop_reason) = result {
match stop_reason {
@@ -2180,7 +2204,7 @@ impl Context {
}
if let RequestType::SuggestEdits = request_type {
if let Ok(preamble) = self.prompt_builder.generate_workflow_prompt() {
if let Ok(preamble) = self.prompt_builder.generate_suggest_edits_prompt() {
let last_elem_index = completion_request.messages.len();
completion_request
@@ -2528,7 +2552,7 @@ impl Context {
let mut messages = stream.await?;
let mut replaced = !replace_old;
while let Some(message) = messages.next().await {
while let Some(message) = messages.stream.next().await {
let text = message?;
let mut lines = text.lines();
this.update(&mut cx, |this, cx| {

View File

@@ -21,9 +21,7 @@ use fs::Fs;
use futures::{
channel::mpsc,
future::{BoxFuture, LocalBoxFuture},
join,
stream::{self, BoxStream},
SinkExt, Stream, StreamExt,
join, SinkExt, Stream, StreamExt,
};
use gpui::{
anchored, deferred, point, AnyElement, AppContext, ClickEvent, EventEmitter, FocusHandle,
@@ -32,7 +30,8 @@ use gpui::{
};
use language::{Buffer, IndentKind, Point, Selection, TransactionId};
use language_model::{
LanguageModel, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role,
logging::report_assistant_event, LanguageModel, LanguageModelRegistry, LanguageModelRequest,
LanguageModelRequestMessage, LanguageModelTextStream, Role,
};
use multi_buffer::MultiBufferRow;
use parking_lot::Mutex;
@@ -241,12 +240,13 @@ impl InlineAssistant {
};
codegen_ranges.push(start..end);
if let Some(telemetry) = self.telemetry.as_ref() {
if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() {
if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() {
if let Some(telemetry) = self.telemetry.as_ref() {
telemetry.report_assistant_event(AssistantEvent {
conversation_id: None,
kind: AssistantKind::Inline,
phase: AssistantPhase::Invoked,
message_id: None,
model: model.telemetry_id(),
model_provider: model.provider_id().to_string(),
response_latency: None,
@@ -754,33 +754,6 @@ impl InlineAssistant {
pub fn finish_assist(&mut self, assist_id: InlineAssistId, undo: bool, cx: &mut WindowContext) {
if let Some(assist) = self.assists.get(&assist_id) {
if let Some(telemetry) = self.telemetry.as_ref() {
if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() {
let language_name = assist.editor.upgrade().and_then(|editor| {
let multibuffer = editor.read(cx).buffer().read(cx);
let ranges = multibuffer.range_to_buffer_ranges(assist.range.clone(), cx);
ranges
.first()
.and_then(|(buffer, _, _)| buffer.read(cx).language())
.map(|language| language.name())
});
telemetry.report_assistant_event(AssistantEvent {
conversation_id: None,
kind: AssistantKind::Inline,
phase: if undo {
AssistantPhase::Rejected
} else {
AssistantPhase::Accepted
},
model: model.telemetry_id(),
model_provider: model.provider_id().to_string(),
response_latency: None,
error_message: None,
language_name: language_name.map(|name| name.to_proto()),
});
}
}
let assist_group_id = assist.group_id;
if self.assist_groups[&assist_group_id].linked {
for assist_id in self.unlink_assist_group(assist_group_id, cx) {
@@ -815,12 +788,45 @@ impl InlineAssistant {
}
}
let active_alternative = assist.codegen.read(cx).active_alternative().clone();
let message_id = active_alternative.read(cx).message_id.clone();
if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() {
let language_name = assist.editor.upgrade().and_then(|editor| {
let multibuffer = editor.read(cx).buffer().read(cx);
let ranges = multibuffer.range_to_buffer_ranges(assist.range.clone(), cx);
ranges
.first()
.and_then(|(buffer, _, _)| buffer.read(cx).language())
.map(|language| language.name())
});
report_assistant_event(
AssistantEvent {
conversation_id: None,
kind: AssistantKind::Inline,
message_id,
phase: if undo {
AssistantPhase::Rejected
} else {
AssistantPhase::Accepted
},
model: model.telemetry_id(),
model_provider: model.provider_id().to_string(),
response_latency: None,
error_message: None,
language_name: language_name.map(|name| name.to_proto()),
},
self.telemetry.clone(),
cx.http_client(),
model.api_key(cx),
cx.background_executor(),
);
}
if undo {
assist.codegen.update(cx, |codegen, cx| codegen.undo(cx));
} else {
let confirmed_alternative = assist.codegen.read(cx).active_alternative().clone();
self.confirmed_assists
.insert(assist_id, confirmed_alternative);
self.confirmed_assists.insert(assist_id, active_alternative);
}
}
}
@@ -2497,6 +2503,7 @@ pub struct CodegenAlternative {
line_operations: Vec<LineOperation>,
request: Option<LanguageModelRequest>,
elapsed_time: Option<f64>,
message_id: Option<String>,
}
enum CodegenStatus {
@@ -2555,6 +2562,7 @@ impl CodegenAlternative {
buffer: buffer.clone(),
old_buffer,
edit_position: None,
message_id: None,
snapshot,
last_equal_ranges: Default::default(),
transformation_transaction_id: None,
@@ -2659,20 +2667,20 @@ impl CodegenAlternative {
self.edit_position = Some(self.range.start.bias_right(&self.snapshot));
let api_key = model.api_key(cx);
let telemetry_id = model.telemetry_id();
let provider_id = model.provider_id();
let chunks: LocalBoxFuture<Result<BoxStream<Result<String>>>> =
let stream: LocalBoxFuture<Result<LanguageModelTextStream>> =
if user_prompt.trim().to_lowercase() == "delete" {
async { Ok(stream::empty().boxed()) }.boxed_local()
async { Ok(LanguageModelTextStream::default()) }.boxed_local()
} else {
let request = self.build_request(user_prompt, assistant_panel_context, cx)?;
self.request = Some(request.clone());
let chunks = cx
.spawn(|_, cx| async move { model.stream_completion_text(request, &cx).await });
async move { Ok(chunks.await?.boxed()) }.boxed_local()
cx.spawn(|_, cx| async move { model.stream_completion_text(request, &cx).await })
.boxed_local()
};
self.handle_stream(telemetry_id, provider_id.to_string(), chunks, cx);
self.handle_stream(telemetry_id, provider_id.to_string(), api_key, stream, cx);
Ok(())
}
@@ -2737,7 +2745,8 @@ impl CodegenAlternative {
&mut self,
model_telemetry_id: String,
model_provider_id: String,
stream: impl 'static + Future<Output = Result<BoxStream<'static, Result<String>>>>,
model_api_key: Option<String>,
stream: impl 'static + Future<Output = Result<LanguageModelTextStream>>,
cx: &mut ModelContext<Self>,
) {
let start_time = Instant::now();
@@ -2767,6 +2776,7 @@ impl CodegenAlternative {
}
}
let http_client = cx.http_client().clone();
let telemetry = self.telemetry.clone();
let language_name = {
let multibuffer = self.buffer.read(cx);
@@ -2782,15 +2792,21 @@ impl CodegenAlternative {
let mut edit_start = self.range.start.to_offset(&snapshot);
self.generation = cx.spawn(|codegen, mut cx| {
async move {
let chunks = stream.await;
let stream = stream.await;
let message_id = stream
.as_ref()
.ok()
.and_then(|stream| stream.message_id.clone());
let generate = async {
let (mut diff_tx, mut diff_rx) = mpsc::channel(1);
let executor = cx.background_executor().clone();
let message_id = message_id.clone();
let line_based_stream_diff: Task<anyhow::Result<()>> =
cx.background_executor().spawn(async move {
let mut response_latency = None;
let request_start = Instant::now();
let diff = async {
let chunks = StripInvalidSpans::new(chunks?);
let chunks = StripInvalidSpans::new(stream?.stream);
futures::pin_mut!(chunks);
let mut diff = StreamingDiff::new(selected_text.to_string());
let mut line_diff = LineDiff::default();
@@ -2886,9 +2902,10 @@ impl CodegenAlternative {
let error_message =
result.as_ref().err().map(|error| error.to_string());
if let Some(telemetry) = telemetry {
telemetry.report_assistant_event(AssistantEvent {
report_assistant_event(
AssistantEvent {
conversation_id: None,
message_id,
kind: AssistantKind::Inline,
phase: AssistantPhase::Response,
model: model_telemetry_id,
@@ -2896,8 +2913,12 @@ impl CodegenAlternative {
response_latency,
error_message,
language_name: language_name.map(|name| name.to_proto()),
});
}
},
telemetry,
http_client,
model_api_key,
&executor,
);
result?;
Ok(())
@@ -2961,6 +2982,7 @@ impl CodegenAlternative {
codegen
.update(&mut cx, |this, cx| {
this.message_id = message_id;
this.last_equal_ranges.clear();
if let Err(error) = result {
this.status = CodegenStatus::Error(error);
@@ -3512,15 +3534,7 @@ mod tests {
)
});
let (chunks_tx, chunks_rx) = mpsc::unbounded();
codegen.update(cx, |codegen, cx| {
codegen.handle_stream(
String::new(),
String::new(),
future::ready(Ok(chunks_rx.map(Ok).boxed())),
cx,
)
});
let chunks_tx = simulate_response_stream(codegen.clone(), cx);
let mut new_text = concat!(
" let mut x = 0;\n",
@@ -3584,15 +3598,7 @@ mod tests {
)
});
let (chunks_tx, chunks_rx) = mpsc::unbounded();
codegen.update(cx, |codegen, cx| {
codegen.handle_stream(
String::new(),
String::new(),
future::ready(Ok(chunks_rx.map(Ok).boxed())),
cx,
)
});
let chunks_tx = simulate_response_stream(codegen.clone(), cx);
cx.background_executor.run_until_parked();
@@ -3659,15 +3665,7 @@ mod tests {
)
});
let (chunks_tx, chunks_rx) = mpsc::unbounded();
codegen.update(cx, |codegen, cx| {
codegen.handle_stream(
String::new(),
String::new(),
future::ready(Ok(chunks_rx.map(Ok).boxed())),
cx,
)
});
let chunks_tx = simulate_response_stream(codegen.clone(), cx);
cx.background_executor.run_until_parked();
@@ -3733,16 +3731,7 @@ mod tests {
)
});
let (chunks_tx, chunks_rx) = mpsc::unbounded();
codegen.update(cx, |codegen, cx| {
codegen.handle_stream(
String::new(),
String::new(),
future::ready(Ok(chunks_rx.map(Ok).boxed())),
cx,
)
});
let chunks_tx = simulate_response_stream(codegen.clone(), cx);
let new_text = concat!(
"func main() {\n",
"\tx := 0\n",
@@ -3797,16 +3786,7 @@ mod tests {
)
});
let (chunks_tx, chunks_rx) = mpsc::unbounded();
codegen.update(cx, |codegen, cx| {
codegen.handle_stream(
String::new(),
String::new(),
future::ready(Ok(chunks_rx.map(Ok).boxed())),
cx,
)
});
let chunks_tx = simulate_response_stream(codegen.clone(), cx);
chunks_tx
.unbounded_send("let mut x = 0;\nx += 1;".to_string())
.unwrap();
@@ -3880,6 +3860,26 @@ mod tests {
}
}
fn simulate_response_stream(
codegen: Model<CodegenAlternative>,
cx: &mut TestAppContext,
) -> mpsc::UnboundedSender<String> {
let (chunks_tx, chunks_rx) = mpsc::unbounded();
codegen.update(cx, |codegen, cx| {
codegen.handle_stream(
String::new(),
String::new(),
None,
future::ready(Ok(LanguageModelTextStream {
message_id: None,
stream: chunks_rx.map(Ok).boxed(),
})),
cx,
);
});
chunks_tx
}
fn rust_lang() -> Language {
Language::new(
LanguageConfig {

View File

@@ -310,8 +310,8 @@ impl PromptBuilder {
.render("terminal_assistant_prompt", &context)
}
pub fn generate_workflow_prompt(&self) -> Result<String, RenderError> {
self.handlebars.lock().render("edit_workflow", &())
pub fn generate_suggest_edits_prompt(&self) -> Result<String, RenderError> {
self.handlebars.lock().render("suggest_edits", &())
}
pub fn generate_project_slash_command_prompt(

View File

@@ -31,6 +31,8 @@ pub mod now_command;
pub mod project_command;
pub mod prompt_command;
pub mod search_command;
pub mod selection_command;
pub mod streaming_example_command;
pub mod symbols_command;
pub mod tab_command;
pub mod terminal_command;

View File

@@ -14,7 +14,7 @@ use language_model::{
use semantic_index::{FileSummary, SemanticDb};
use smol::channel;
use std::sync::{atomic::AtomicBool, Arc};
use ui::{BorrowAppContext, WindowContext};
use ui::{prelude::*, BorrowAppContext, WindowContext};
use util::ResultExt;
use workspace::Workspace;
@@ -37,6 +37,10 @@ impl SlashCommand for AutoCommand {
"Automatically infer what context to add".into()
}
fn icon(&self) -> IconName {
IconName::Wand
}
fn menu_text(&self) -> String {
self.description()
}

View File

@@ -10,6 +10,7 @@ use gpui::{Task, WeakView, WindowContext};
use language::{BufferSnapshot, LspAdapterDelegate};
use std::sync::{atomic::AtomicBool, Arc};
use text::OffsetRangeExt;
use ui::prelude::*;
use workspace::Workspace;
pub(crate) struct DeltaSlashCommand;
@@ -27,6 +28,10 @@ impl SlashCommand for DeltaSlashCommand {
self.description()
}
fn icon(&self) -> IconName {
IconName::Diff
}
fn requires_argument(&self) -> bool {
false
}

View File

@@ -98,6 +98,10 @@ impl SlashCommand for DiagnosticsSlashCommand {
"Insert diagnostics".into()
}
fn icon(&self) -> IconName {
IconName::XCircle
}
fn menu_text(&self) -> String {
self.description()
}

View File

@@ -21,8 +21,6 @@ use ui::prelude::*;
use util::ResultExt;
use workspace::Workspace;
use crate::slash_command::diagnostics_command::collect_buffer_diagnostics;
pub(crate) struct FileSlashCommand;
impl FileSlashCommand {
@@ -117,7 +115,7 @@ impl SlashCommand for FileSlashCommand {
}
fn description(&self) -> String {
"Insert file".into()
"Insert file and/or directory".into()
}
fn menu_text(&self) -> String {
@@ -128,6 +126,10 @@ impl SlashCommand for FileSlashCommand {
true
}
fn icon(&self) -> IconName {
IconName::File
}
fn complete_argument(
self: Arc<Self>,
arguments: &[String],
@@ -539,8 +541,6 @@ pub fn append_buffer_to_output(
output.text.push('\n');
let section_ix = output.sections.len();
collect_buffer_diagnostics(output, buffer, false);
output.sections.insert(
section_ix,
build_entry_output_section(prev_len..output.text.len(), path, false, None),

View File

@@ -24,7 +24,8 @@ use std::{
ops::DerefMut,
sync::{atomic::AtomicBool, Arc},
};
use ui::{BorrowAppContext as _, IconName};
use ui::prelude::*;
use workspace::Workspace;
pub struct ProjectSlashCommand {
@@ -50,6 +51,10 @@ impl SlashCommand for ProjectSlashCommand {
"Generate a semantic search based on context".into()
}
fn icon(&self) -> IconName {
IconName::Folder
}
fn menu_text(&self) -> String {
self.description()
}

View File

@@ -21,6 +21,10 @@ impl SlashCommand for PromptSlashCommand {
"Insert prompt from library".into()
}
fn icon(&self) -> IconName {
IconName::Library
}
fn menu_text(&self) -> String {
self.description()
}

View File

@@ -21,6 +21,10 @@ pub(crate) struct SearchSlashCommandFeatureFlag;
impl FeatureFlag for SearchSlashCommandFeatureFlag {
const NAME: &'static str = "search-slash-command";
fn enabled_for_staff() -> bool {
false
}
}
pub(crate) struct SearchSlashCommand;
@@ -38,6 +42,10 @@ impl SlashCommand for SearchSlashCommand {
"Search your project semantically".into()
}
fn icon(&self) -> IconName {
IconName::SearchCode
}
fn menu_text(&self) -> String {
self.description()
}

View File

@@ -0,0 +1,98 @@
use crate::assistant_panel::selections_creases;
use anyhow::{anyhow, Result};
use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandContent, SlashCommandEvent,
SlashCommandOutputSection, SlashCommandResult,
};
use futures::StreamExt;
use gpui::{AppContext, Task, WeakView};
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate};
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use ui::{IconName, SharedString, WindowContext};
use workspace::Workspace;
pub(crate) struct SelectionCommand;
impl SlashCommand for SelectionCommand {
fn name(&self) -> String {
"selection".into()
}
fn label(&self, _cx: &AppContext) -> CodeLabel {
CodeLabel::plain(self.name(), None)
}
fn description(&self) -> String {
"Insert editor selection".into()
}
fn icon(&self) -> IconName {
IconName::Quote
}
fn menu_text(&self) -> String {
self.description()
}
fn requires_argument(&self) -> bool {
false
}
fn accepts_arguments(&self) -> bool {
true
}
fn complete_argument(
self: Arc<Self>,
_arguments: &[String],
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut WindowContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
Task::ready(Err(anyhow!("this command does not require argument")))
}
fn run(
self: Arc<Self>,
_arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
) -> Task<SlashCommandResult> {
let mut events = vec![];
let Some(creases) = workspace
.update(cx, selections_creases)
.unwrap_or_else(|e| {
events.push(Err(e));
None
})
else {
return Task::ready(Err(anyhow!("no active selection")));
};
for (text, title) in creases {
events.push(Ok(SlashCommandEvent::StartSection {
icon: IconName::TextSnippet,
label: SharedString::from(title),
metadata: None,
}));
events.push(Ok(SlashCommandEvent::Content(SlashCommandContent::Text {
text,
run_commands_in_text: false,
})));
events.push(Ok(SlashCommandEvent::EndSection { metadata: None }));
events.push(Ok(SlashCommandEvent::Content(SlashCommandContent::Text {
text: "\n".to_string(),
run_commands_in_text: false,
})));
}
let result = futures::stream::iter(events).boxed();
Task::ready(Ok(result))
}
}

View File

@@ -0,0 +1,136 @@
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use std::time::Duration;
use anyhow::Result;
use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandContent, SlashCommandEvent,
SlashCommandOutputSection, SlashCommandResult,
};
use feature_flags::FeatureFlag;
use futures::channel::mpsc;
use gpui::{Task, WeakView};
use language::{BufferSnapshot, LspAdapterDelegate};
use smol::stream::StreamExt;
use smol::Timer;
use ui::prelude::*;
use workspace::Workspace;
pub struct StreamingExampleSlashCommandFeatureFlag;
impl FeatureFlag for StreamingExampleSlashCommandFeatureFlag {
const NAME: &'static str = "streaming-example-slash-command";
}
pub(crate) struct StreamingExampleSlashCommand;
impl SlashCommand for StreamingExampleSlashCommand {
fn name(&self) -> String {
"streaming-example".into()
}
fn description(&self) -> String {
"An example slash command that showcases streaming.".into()
}
fn menu_text(&self) -> String {
self.description()
}
fn requires_argument(&self) -> bool {
false
}
fn complete_argument(
self: Arc<Self>,
_arguments: &[String],
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut WindowContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
Task::ready(Ok(Vec::new()))
}
fn run(
self: Arc<Self>,
_arguments: &[String],
_context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
_context_buffer: BufferSnapshot,
_workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
) -> Task<SlashCommandResult> {
let (events_tx, events_rx) = mpsc::unbounded();
cx.background_executor()
.spawn(async move {
events_tx.unbounded_send(Ok(SlashCommandEvent::StartSection {
icon: IconName::FileRust,
label: "Section 1".into(),
metadata: None,
}))?;
events_tx.unbounded_send(Ok(SlashCommandEvent::Content(
SlashCommandContent::Text {
text: "Hello".into(),
run_commands_in_text: false,
},
)))?;
events_tx.unbounded_send(Ok(SlashCommandEvent::EndSection { metadata: None }))?;
events_tx.unbounded_send(Ok(SlashCommandEvent::Content(
SlashCommandContent::Text {
text: "\n".into(),
run_commands_in_text: false,
},
)))?;
Timer::after(Duration::from_secs(1)).await;
events_tx.unbounded_send(Ok(SlashCommandEvent::StartSection {
icon: IconName::FileRust,
label: "Section 2".into(),
metadata: None,
}))?;
events_tx.unbounded_send(Ok(SlashCommandEvent::Content(
SlashCommandContent::Text {
text: "World".into(),
run_commands_in_text: false,
},
)))?;
events_tx.unbounded_send(Ok(SlashCommandEvent::EndSection { metadata: None }))?;
events_tx.unbounded_send(Ok(SlashCommandEvent::Content(
SlashCommandContent::Text {
text: "\n".into(),
run_commands_in_text: false,
},
)))?;
for n in 1..=10 {
Timer::after(Duration::from_secs(1)).await;
events_tx.unbounded_send(Ok(SlashCommandEvent::StartSection {
icon: IconName::StarFilled,
label: format!("Section {n}").into(),
metadata: None,
}))?;
events_tx.unbounded_send(Ok(SlashCommandEvent::Content(
SlashCommandContent::Text {
text: "lorem ipsum ".repeat(n).trim().into(),
run_commands_in_text: false,
},
)))?;
events_tx
.unbounded_send(Ok(SlashCommandEvent::EndSection { metadata: None }))?;
events_tx.unbounded_send(Ok(SlashCommandEvent::Content(
SlashCommandContent::Text {
text: "\n".into(),
run_commands_in_text: false,
},
)))?;
}
anyhow::Ok(())
})
.detach_and_log_err(cx);
Task::ready(Ok(events_rx.boxed()))
}
}

View File

@@ -22,6 +22,10 @@ impl SlashCommand for OutlineSlashCommand {
"Insert symbols for active tab".into()
}
fn icon(&self) -> IconName {
IconName::ListTree
}
fn menu_text(&self) -> String {
self.description()
}

View File

@@ -12,7 +12,7 @@ use std::{
path::PathBuf,
sync::{atomic::AtomicBool, Arc},
};
use ui::{ActiveTheme, WindowContext};
use ui::{prelude::*, ActiveTheme, WindowContext};
use util::ResultExt;
use workspace::Workspace;
@@ -31,6 +31,10 @@ impl SlashCommand for TabSlashCommand {
"Insert open tabs (active tab by default)".to_owned()
}
fn icon(&self) -> IconName {
IconName::FileTree
}
fn menu_text(&self) -> String {
self.description()
}

View File

@@ -33,6 +33,10 @@ impl SlashCommand for TerminalSlashCommand {
"Insert terminal output".into()
}
fn icon(&self) -> IconName {
IconName::Terminal
}
fn menu_text(&self) -> String {
self.description()
}

View File

@@ -1,17 +1,10 @@
use std::sync::Arc;
use assistant_slash_command::SlashCommandRegistry;
use gpui::AnyElement;
use gpui::DismissEvent;
use gpui::WeakView;
use picker::PickerEditorPosition;
use ui::ListItemSpacing;
use gpui::SharedString;
use gpui::Task;
use picker::{Picker, PickerDelegate};
use ui::{prelude::*, ListItem, PopoverMenu, PopoverTrigger};
use gpui::{AnyElement, DismissEvent, SharedString, Task, WeakView};
use picker::{Picker, PickerDelegate, PickerEditorPosition};
use ui::{prelude::*, ListItem, ListItemSpacing, PopoverMenu, PopoverTrigger};
use crate::assistant_panel::ContextEditor;
@@ -27,6 +20,7 @@ struct SlashCommandInfo {
name: SharedString,
description: SharedString,
args: Option<SharedString>,
icon: IconName,
}
#[derive(Clone)]
@@ -145,16 +139,20 @@ impl PickerDelegate for SlashCommandDelegate {
}
ret
}
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
if let Some(command) = self.filtered_commands.get(self.selected_index) {
if let SlashCommandEntry::Info(info) = command {
self.active_context_editor
.update(cx, |context_editor, cx| {
context_editor.insert_command(&info.name, cx)
})
.ok();
} else if let SlashCommandEntry::Advert { on_confirm, .. } = command {
on_confirm(cx);
match command {
SlashCommandEntry::Info(info) => {
self.active_context_editor
.update(cx, |context_editor, cx| {
context_editor.insert_command(&info.name, cx)
})
.ok();
}
SlashCommandEntry::Advert { on_confirm, .. } => {
on_confirm(cx);
}
}
cx.emit(DismissEvent);
}
@@ -181,43 +179,41 @@ impl PickerDelegate for SlashCommandDelegate {
.spacing(ListItemSpacing::Dense)
.selected(selected)
.child(
h_flex()
v_flex()
.group(format!("command-entry-label-{ix}"))
.w_full()
.min_w(px(250.))
.child(
v_flex()
.child(
h_flex()
.child(div().font_buffer(cx).child({
let mut label = format!("/{}", info.name);
if let Some(args) =
info.args.as_ref().filter(|_| selected)
{
label.push_str(&args);
}
Label::new(label).size(LabelSize::Small)
}))
.children(info.args.clone().filter(|_| !selected).map(
|args| {
div()
.font_buffer(cx)
.child(
Label::new(args)
.size(LabelSize::Small)
.color(Color::Muted),
)
.visible_on_hover(format!(
"command-entry-label-{ix}"
))
},
)),
)
.child(
Label::new(info.description.clone())
.size(LabelSize::Small)
.color(Color::Muted),
),
h_flex()
.gap_1p5()
.child(Icon::new(info.icon).size(IconSize::XSmall))
.child(div().font_buffer(cx).child({
let mut label = format!("{}", info.name);
if let Some(args) = info.args.as_ref().filter(|_| selected)
{
label.push_str(&args);
}
Label::new(label).size(LabelSize::Small)
}))
.children(info.args.clone().filter(|_| !selected).map(
|args| {
div()
.font_buffer(cx)
.child(
Label::new(args)
.size(LabelSize::Small)
.color(Color::Muted),
)
.visible_on_hover(format!(
"command-entry-label-{ix}"
))
},
)),
)
.child(
Label::new(info.description.clone())
.size(LabelSize::Small)
.color(Color::Muted),
),
),
),
@@ -251,24 +247,40 @@ impl<T: PopoverTrigger> RenderOnce for SlashCommandSelector<T> {
name: command_name.into(),
description: menu_text,
args,
icon: command.icon(),
}))
})
.chain([SlashCommandEntry::Advert {
name: "create-your-command".into(),
renderer: |cx| {
v_flex()
.w_full()
.child(
h_flex()
.w_full()
.font_buffer(cx)
.items_center()
.gap_1()
.child(div().font_buffer(cx).child(
Label::new("create-your-command").size(LabelSize::Small),
))
.child(Icon::new(IconName::ArrowUpRight).size(IconSize::XSmall)),
.justify_between()
.child(
h_flex()
.items_center()
.gap_1p5()
.child(Icon::new(IconName::Plus).size(IconSize::XSmall))
.child(
div().font_buffer(cx).child(
Label::new("create-your-command")
.size(LabelSize::Small),
),
),
)
.child(
Icon::new(IconName::ArrowUpRight)
.size(IconSize::XSmall)
.color(Color::Muted),
),
)
.child(
Label::new("Learn how to create a custom command")
Label::new("Create your custom command")
.size(LabelSize::Small)
.color(Color::Muted),
)

View File

@@ -28,13 +28,36 @@ impl Matrix {
self.cols = cols;
}
fn swap_columns(&mut self, col1: usize, col2: usize) {
if col1 == col2 {
return;
}
if col1 >= self.cols {
panic!("column out of bounds");
}
if col2 >= self.cols {
panic!("column out of bounds");
}
unsafe {
let ptr = self.cells.as_mut_ptr();
std::ptr::swap_nonoverlapping(
ptr.add(col1 * self.rows),
ptr.add(col2 * self.rows),
self.rows,
);
}
}
fn get(&self, row: usize, col: usize) -> f64 {
if row >= self.rows {
panic!("row out of bounds")
}
if col >= self.cols {
panic!("col out of bounds")
panic!("column out of bounds")
}
self.cells[col * self.rows + row]
}
@@ -45,7 +68,7 @@ impl Matrix {
}
if col >= self.cols {
panic!("col out of bounds")
panic!("column out of bounds")
}
self.cells[col * self.rows + row] = value;
@@ -106,26 +129,32 @@ impl StreamingDiff {
pub fn push_new(&mut self, text: &str) -> Vec<CharOperation> {
self.new.extend(text.chars());
self.scores.resize(self.old.len() + 1, self.new.len() + 1);
self.scores.swap_columns(0, self.scores.cols - 1);
self.scores
.resize(self.old.len() + 1, self.new.len() - self.new_text_ix + 1);
self.equal_runs.retain(|(_i, j), _| *j == self.new_text_ix);
for j in self.new_text_ix + 1..=self.new.len() {
self.scores.set(0, j, j as f64 * Self::INSERTION_SCORE);
let relative_j = j - self.new_text_ix;
self.scores
.set(0, relative_j, j as f64 * Self::INSERTION_SCORE);
for i in 1..=self.old.len() {
let insertion_score = self.scores.get(i, j - 1) + Self::INSERTION_SCORE;
let deletion_score = self.scores.get(i - 1, j) + Self::DELETION_SCORE;
let insertion_score = self.scores.get(i, relative_j - 1) + Self::INSERTION_SCORE;
let deletion_score = self.scores.get(i - 1, relative_j) + Self::DELETION_SCORE;
let equality_score = if self.old[i - 1] == self.new[j - 1] {
let mut equal_run = self.equal_runs.get(&(i - 1, j - 1)).copied().unwrap_or(0);
equal_run += 1;
self.equal_runs.insert((i, j), equal_run);
let exponent = cmp::min(equal_run as i32 / 4, Self::MAX_EQUALITY_EXPONENT);
self.scores.get(i - 1, j - 1) + Self::EQUALITY_BASE.powi(exponent)
self.scores.get(i - 1, relative_j - 1) + Self::EQUALITY_BASE.powi(exponent)
} else {
f64::NEG_INFINITY
};
let score = insertion_score.max(deletion_score).max(equality_score);
self.scores.set(i, j, score);
self.scores.set(i, relative_j, score);
}
}
@@ -133,7 +162,7 @@ impl StreamingDiff {
let mut next_old_text_ix = self.old_text_ix;
let next_new_text_ix = self.new.len();
for i in self.old_text_ix..=self.old.len() {
let score = self.scores.get(i, next_new_text_ix);
let score = self.scores.get(i, next_new_text_ix - self.new_text_ix);
if score > max_score {
max_score = score;
next_old_text_ix = i;
@@ -174,7 +203,9 @@ impl StreamingDiff {
let (prev_i, prev_j) = [insertion_score, deletion_score, equality_score]
.iter()
.max_by_key(|cell| cell.map(|(i, j)| OrderedFloat(self.scores.get(i, j))))
.max_by_key(|cell| {
cell.map(|(i, j)| OrderedFloat(self.scores.get(i, j - self.new_text_ix)))
})
.unwrap()
.unwrap();

View File

@@ -17,7 +17,8 @@ use gpui::{
};
use language::Buffer;
use language_model::{
LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role,
logging::report_assistant_event, LanguageModelRegistry, LanguageModelRequest,
LanguageModelRequestMessage, Role,
};
use settings::Settings;
use std::{
@@ -306,6 +307,33 @@ impl TerminalInlineAssistant {
this.focus_handle(cx).focus(cx);
})
.log_err();
if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() {
let codegen = assist.codegen.read(cx);
let executor = cx.background_executor().clone();
report_assistant_event(
AssistantEvent {
conversation_id: None,
kind: AssistantKind::InlineTerminal,
message_id: codegen.message_id.clone(),
phase: if undo {
AssistantPhase::Rejected
} else {
AssistantPhase::Accepted
},
model: model.telemetry_id(),
model_provider: model.provider_id().to_string(),
response_latency: None,
error_message: None,
language_name: None,
},
codegen.telemetry.clone(),
cx.http_client(),
model.api_key(cx),
&executor,
);
}
assist.codegen.update(cx, |codegen, cx| {
if undo {
codegen.undo(cx);
@@ -1016,6 +1044,7 @@ pub struct Codegen {
telemetry: Option<Arc<Telemetry>>,
terminal: Model<Terminal>,
generation: Task<()>,
message_id: Option<String>,
transaction: Option<TerminalTransaction>,
}
@@ -1026,6 +1055,7 @@ impl Codegen {
telemetry,
status: CodegenStatus::Idle,
generation: Task::ready(()),
message_id: None,
transaction: None,
}
}
@@ -1035,6 +1065,8 @@ impl Codegen {
return;
};
let model_api_key = model.api_key(cx);
let http_client = cx.http_client();
let telemetry = self.telemetry.clone();
self.status = CodegenStatus::Pending;
self.transaction = Some(TerminalTransaction::start(self.terminal.clone()));
@@ -1043,44 +1075,62 @@ impl Codegen {
let model_provider_id = model.provider_id();
let response = model.stream_completion_text(prompt, &cx).await;
let generate = async {
let message_id = response
.as_ref()
.ok()
.and_then(|response| response.message_id.clone());
let (mut hunks_tx, mut hunks_rx) = mpsc::channel(1);
let task = cx.background_executor().spawn(async move {
let mut response_latency = None;
let request_start = Instant::now();
let task = async {
let mut chunks = response?;
while let Some(chunk) = chunks.next().await {
if response_latency.is_none() {
response_latency = Some(request_start.elapsed());
let task = cx.background_executor().spawn({
let message_id = message_id.clone();
let executor = cx.background_executor().clone();
async move {
let mut response_latency = None;
let request_start = Instant::now();
let task = async {
let mut chunks = response?.stream;
while let Some(chunk) = chunks.next().await {
if response_latency.is_none() {
response_latency = Some(request_start.elapsed());
}
let chunk = chunk?;
hunks_tx.send(chunk).await?;
}
let chunk = chunk?;
hunks_tx.send(chunk).await?;
}
anyhow::Ok(())
};
let result = task.await;
let error_message = result.as_ref().err().map(|error| error.to_string());
report_assistant_event(
AssistantEvent {
conversation_id: None,
kind: AssistantKind::InlineTerminal,
message_id,
phase: AssistantPhase::Response,
model: model_telemetry_id,
model_provider: model_provider_id.to_string(),
response_latency,
error_message,
language_name: None,
},
telemetry,
http_client,
model_api_key,
&executor,
);
result?;
anyhow::Ok(())
};
let result = task.await;
let error_message = result.as_ref().err().map(|error| error.to_string());
if let Some(telemetry) = telemetry {
telemetry.report_assistant_event(AssistantEvent {
conversation_id: None,
kind: AssistantKind::Inline,
phase: AssistantPhase::Response,
model: model_telemetry_id,
model_provider: model_provider_id.to_string(),
response_latency,
error_message,
language_name: None,
});
}
result?;
anyhow::Ok(())
});
this.update(&mut cx, |this, _| {
this.message_id = message_id;
})?;
while let Some(hunk) = hunks_rx.next().await {
this.update(&mut cx, |this, cx| {
if let Some(transaction) = &mut this.transaction {

View File

@@ -62,6 +62,9 @@ pub type SlashCommandResult = Result<BoxStream<'static, Result<SlashCommandEvent
pub trait SlashCommand: 'static + Send + Sync {
fn name(&self) -> String;
fn icon(&self) -> IconName {
IconName::Slash
}
fn label(&self, _cx: &AppContext) -> CodeLabel {
CodeLabel::plain(self.name(), None)
}

View File

@@ -432,6 +432,9 @@ impl AutoUpdater {
cx.notify();
}
// If you are packaging Zed and need to override the place it downloads SSH remotes from,
// you can override this function. You should also update get_remote_server_release_url to return
// Ok(None).
pub async fn download_remote_server_release(
os: &str,
arch: &str,
@@ -482,7 +485,7 @@ impl AutoUpdater {
release_channel: ReleaseChannel,
version: Option<SemanticVersion>,
cx: &mut AsyncAppContext,
) -> Result<(JsonRelease, String)> {
) -> Result<Option<(String, String)>> {
let this = cx.update(|cx| {
cx.default_global::<GlobalAutoUpdate>()
.0
@@ -504,7 +507,7 @@ impl AutoUpdater {
let update_request_body = build_remote_server_update_request_body(cx)?;
let body = serde_json::to_string(&update_request_body)?;
Ok((release, body))
Ok(Some((release.url, body)))
}
async fn get_release(
@@ -686,6 +689,12 @@ async fn download_remote_server_binary(
let request_body = AsyncBody::from(serde_json::to_string(&update_request_body)?);
let mut response = client.get(&release.url, request_body, true).await?;
if !response.status().is_success() {
return Err(anyhow!(
"failed to download remote server release: {:?}",
response.status()
));
}
smol::io::copy(response.body_mut(), &mut temp_file).await?;
smol::fs::rename(&temp, &target_path).await?;

View File

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

View File

@@ -44,7 +44,6 @@ sha2.workspace = true
smol.workspace = true
sysinfo.workspace = true
telemetry_events.workspace = true
tempfile.workspace = true
text.workspace = true
thiserror.workspace = true
time.workspace = true

View File

@@ -13,6 +13,7 @@ use parking_lot::Mutex;
use release_channel::ReleaseChannel;
use settings::{Settings, SettingsStore};
use sha2::{Digest, Sha256};
use std::fs::File;
use std::io::Write;
use std::{env, mem, path::PathBuf, sync::Arc, time::Duration};
use sysinfo::{CpuRefreshKind, Pid, ProcessRefreshKind, RefreshKind, System};
@@ -21,10 +22,7 @@ use telemetry_events::{
EventRequestBody, EventWrapper, ExtensionEvent, InlineCompletionEvent, MemoryEvent, ReplEvent,
SettingEvent,
};
use tempfile::NamedTempFile;
#[cfg(not(debug_assertions))]
use util::ResultExt;
use util::TryFutureExt;
use util::{ResultExt, TryFutureExt};
use worktree::{UpdatedEntriesSet, WorktreeId};
use self::event_coalescer::EventCoalescer;
@@ -46,7 +44,7 @@ struct TelemetryState {
architecture: &'static str,
events_queue: Vec<EventWrapper>,
flush_events_task: Option<Task<()>>,
log_file: Option<NamedTempFile>,
log_file: Option<File>,
is_staff: Option<bool>,
first_event_date_time: Option<DateTime<Utc>>,
event_coalescer: EventCoalescer,
@@ -223,15 +221,13 @@ impl Telemetry {
os_name: os_name(),
app_version: release_channel::AppVersion::global(cx).to_string(),
}));
Self::log_file_path();
#[cfg(not(debug_assertions))]
cx.background_executor()
.spawn({
let state = state.clone();
async move {
if let Some(tempfile) =
NamedTempFile::new_in(paths::logs_dir().as_path()).log_err()
{
if let Some(tempfile) = File::create(Self::log_file_path()).log_err() {
state.lock().log_file = Some(tempfile);
}
}
@@ -280,8 +276,8 @@ impl Telemetry {
Task::ready(())
}
pub fn log_file_path(&self) -> Option<PathBuf> {
Some(self.state.lock().log_file.as_ref()?.path().to_path_buf())
pub fn log_file_path() -> PathBuf {
paths::logs_dir().join("telemetry.log")
}
pub fn start(
@@ -341,6 +337,13 @@ impl Telemetry {
.detach();
}
pub fn metrics_enabled(self: &Arc<Self>) -> bool {
let state = self.state.lock();
let enabled = state.settings.metrics;
drop(state);
return enabled;
}
pub fn set_authenticated_user_info(
self: &Arc<Self>,
metrics_id: Option<String>,
@@ -638,7 +641,6 @@ impl Telemetry {
let mut json_bytes = Vec::new();
if let Some(file) = &mut this.state.lock().log_file {
let file = file.as_file_mut();
for event in &mut events {
json_bytes.clear();
serde_json::to_writer(&mut json_bytes, event)?;

View File

@@ -52,9 +52,7 @@ CREATE TABLE "projects" (
"host_user_id" INTEGER REFERENCES users (id),
"host_connection_id" INTEGER,
"host_connection_server_id" INTEGER REFERENCES servers (id) ON DELETE CASCADE,
"unregistered" BOOLEAN NOT NULL DEFAULT FALSE,
"hosted_project_id" INTEGER REFERENCES hosted_projects (id),
"dev_server_project_id" INTEGER REFERENCES dev_server_projects(id)
"unregistered" BOOLEAN NOT NULL DEFAULT FALSE
);
CREATE INDEX "index_projects_on_host_connection_server_id" ON "projects" ("host_connection_server_id");
CREATE INDEX "index_projects_on_host_connection_id_and_host_connection_server_id" ON "projects" ("host_connection_id", "host_connection_server_id");
@@ -399,30 +397,6 @@ CREATE TABLE rate_buckets (
);
CREATE INDEX idx_user_id_rate_limit ON rate_buckets (user_id, rate_limit_name);
CREATE TABLE hosted_projects (
id INTEGER PRIMARY KEY AUTOINCREMENT,
channel_id INTEGER NOT NULL REFERENCES channels(id),
name TEXT NOT NULL,
visibility TEXT NOT NULL,
deleted_at TIMESTAMP NULL
);
CREATE INDEX idx_hosted_projects_on_channel_id ON hosted_projects (channel_id);
CREATE UNIQUE INDEX uix_hosted_projects_on_channel_id_and_name ON hosted_projects (channel_id, name) WHERE (deleted_at IS NULL);
CREATE TABLE dev_servers (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL REFERENCES users(id),
name TEXT NOT NULL,
ssh_connection_string TEXT,
hashed_token TEXT NOT NULL
);
CREATE TABLE dev_server_projects (
id INTEGER PRIMARY KEY AUTOINCREMENT,
dev_server_id INTEGER NOT NULL REFERENCES dev_servers(id),
paths TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS billing_preferences (
id INTEGER PRIMARY KEY AUTOINCREMENT,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,

View File

@@ -0,0 +1,6 @@
ALTER TABLE projects DROP COLUMN dev_server_project_id;
ALTER TABLE projects DROP COLUMN hosted_project_id;
DROP TABLE hosted_projects;
DROP TABLE dev_server_projects;
DROP TABLE dev_servers;

View File

@@ -750,49 +750,6 @@ impl Database {
Ok((project, replica_id as ReplicaId))
}
pub async fn leave_hosted_project(
&self,
project_id: ProjectId,
connection: ConnectionId,
) -> Result<LeftProject> {
self.transaction(|tx| async move {
let result = project_collaborator::Entity::delete_many()
.filter(
Condition::all()
.add(project_collaborator::Column::ProjectId.eq(project_id))
.add(project_collaborator::Column::ConnectionId.eq(connection.id as i32))
.add(
project_collaborator::Column::ConnectionServerId
.eq(connection.owner_id as i32),
),
)
.exec(&*tx)
.await?;
if result.rows_affected == 0 {
return Err(anyhow!("not in the project"))?;
}
let project = project::Entity::find_by_id(project_id)
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("no such project"))?;
let collaborators = project
.find_related(project_collaborator::Entity)
.all(&*tx)
.await?;
let connection_ids = collaborators
.into_iter()
.map(|collaborator| collaborator.connection())
.collect();
Ok(LeftProject {
id: project.id,
connection_ids,
should_unshare: false,
})
})
.await
}
/// Removes the given connection from the specified project.
pub async fn leave_project(
&self,

View File

@@ -6497,7 +6497,7 @@ async fn test_context_collaboration_with_reconnect(
.await
.unwrap();
// Client A creates a new context.
// Client A creates a new chats.
let context_a = context_store_a.update(cx_a, |store, cx| store.create(cx));
executor.run_until_parked();

View File

@@ -35,14 +35,30 @@ pub enum Model {
Gpt4,
#[serde(alias = "gpt-3.5-turbo", rename = "gpt-3.5-turbo")]
Gpt3_5Turbo,
#[serde(alias = "o1-preview", rename = "o1-preview-2024-09-12")]
O1Preview,
#[serde(alias = "o1-mini", rename = "o1-mini-2024-09-12")]
O1Mini,
#[serde(alias = "claude-3-5-sonnet", rename = "claude-3.5-sonnet")]
Claude3_5Sonnet,
}
impl Model {
pub fn uses_streaming(&self) -> bool {
match self {
Self::Gpt4o | Self::Gpt4 | Self::Gpt3_5Turbo | Self::Claude3_5Sonnet => true,
Self::O1Mini | Self::O1Preview => false,
}
}
pub fn from_id(id: &str) -> Result<Self> {
match id {
"gpt-4o" => Ok(Self::Gpt4o),
"gpt-4" => Ok(Self::Gpt4),
"gpt-3.5-turbo" => Ok(Self::Gpt3_5Turbo),
"o1-preview" => Ok(Self::O1Preview),
"o1-mini" => Ok(Self::O1Mini),
"claude-3-5-sonnet" => Ok(Self::Claude3_5Sonnet),
_ => Err(anyhow!("Invalid model id: {}", id)),
}
}
@@ -52,6 +68,9 @@ impl Model {
Self::Gpt3_5Turbo => "gpt-3.5-turbo",
Self::Gpt4 => "gpt-4",
Self::Gpt4o => "gpt-4o",
Self::O1Mini => "o1-mini",
Self::O1Preview => "o1-preview",
Self::Claude3_5Sonnet => "claude-3-5-sonnet",
}
}
@@ -60,6 +79,9 @@ impl Model {
Self::Gpt3_5Turbo => "GPT-3.5",
Self::Gpt4 => "GPT-4",
Self::Gpt4o => "GPT-4o",
Self::O1Mini => "o1-mini",
Self::O1Preview => "o1-preview",
Self::Claude3_5Sonnet => "Claude 3.5 Sonnet",
}
}
@@ -68,6 +90,9 @@ impl Model {
Self::Gpt4o => 128000,
Self::Gpt4 => 8192,
Self::Gpt3_5Turbo => 16385,
Self::O1Mini => 128000,
Self::O1Preview => 128000,
Self::Claude3_5Sonnet => 200_000,
}
}
}
@@ -87,7 +112,7 @@ impl Request {
Self {
intent: true,
n: 1,
stream: true,
stream: model.uses_streaming(),
temperature: 0.1,
model,
messages,
@@ -113,7 +138,8 @@ pub struct ResponseEvent {
pub struct ResponseChoice {
pub index: usize,
pub finish_reason: Option<String>,
pub delta: ResponseDelta,
pub delta: Option<ResponseDelta>,
pub message: Option<ResponseDelta>,
}
#[derive(Debug, Deserialize)]
@@ -333,9 +359,23 @@ async fn stream_completion(
if let Some(low_speed_timeout) = low_speed_timeout {
request_builder = request_builder.read_timeout(low_speed_timeout);
}
let is_streaming = request.stream;
let request = request_builder.body(AsyncBody::from(serde_json::to_string(&request)?))?;
let mut response = client.send(request).await?;
if response.status().is_success() {
if !response.status().is_success() {
let mut body = Vec::new();
response.body_mut().read_to_end(&mut body).await?;
let body_str = std::str::from_utf8(&body)?;
return Err(anyhow!(
"Failed to connect to API: {} {}",
response.status(),
body_str
));
}
if is_streaming {
let reader = BufReader::new(response.into_body());
Ok(reader
.lines()
@@ -367,19 +407,9 @@ async fn stream_completion(
} else {
let mut body = Vec::new();
response.body_mut().read_to_end(&mut body).await?;
let body_str = std::str::from_utf8(&body)?;
let response: ResponseEvent = serde_json::from_str(body_str)?;
match serde_json::from_str::<ResponseEvent>(body_str) {
Ok(_) => Err(anyhow!(
"Unexpected success response while expecting an error: {}",
body_str,
)),
Err(_) => Err(anyhow!(
"Failed to connect to API: {} {}",
response.status(),
body_str,
)),
}
Ok(futures::stream::once(async move { Ok(response) }).boxed())
}
}

View File

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

View File

@@ -14,10 +14,6 @@ use editor::{
scroll::Autoscroll,
Editor, EditorEvent, ExcerptId, ExcerptRange, MultiBuffer, ToOffset,
};
use futures::{
channel::mpsc::{self, UnboundedSender},
StreamExt as _,
};
use gpui::{
actions, div, svg, AnyElement, AnyView, AppContext, Context, EventEmitter, FocusHandle,
FocusableView, HighlightStyle, InteractiveElement, IntoElement, Model, ParentElement, Render,
@@ -62,11 +58,10 @@ struct ProjectDiagnosticsEditor {
summary: DiagnosticSummary,
excerpts: Model<MultiBuffer>,
path_states: Vec<PathState>,
paths_to_update: BTreeSet<(ProjectPath, LanguageServerId)>,
paths_to_update: BTreeSet<(ProjectPath, Option<LanguageServerId>)>,
include_warnings: bool,
context: u32,
update_paths_tx: UnboundedSender<(ProjectPath, Option<LanguageServerId>)>,
_update_excerpts_task: Task<Result<()>>,
update_excerpts_task: Option<Task<Result<()>>>,
_subscription: Subscription,
}
@@ -129,14 +124,14 @@ impl ProjectDiagnosticsEditor {
}
project::Event::DiskBasedDiagnosticsFinished { language_server_id } => {
log::debug!("disk based diagnostics finished for server {language_server_id}");
this.enqueue_update_stale_excerpts(Some(*language_server_id));
this.update_stale_excerpts(cx);
}
project::Event::DiagnosticsUpdated {
language_server_id,
path,
} => {
this.paths_to_update
.insert((path.clone(), *language_server_id));
.insert((path.clone(), Some(*language_server_id)));
this.summary = project.read(cx).diagnostic_summary(false, cx);
cx.emit(EditorEvent::TitleChanged);
@@ -144,7 +139,7 @@ impl ProjectDiagnosticsEditor {
log::debug!("diagnostics updated for server {language_server_id}, path {path:?}. recording change");
} else {
log::debug!("diagnostics updated for server {language_server_id}, path {path:?}. updating excerpts");
this.enqueue_update_stale_excerpts(Some(*language_server_id));
this.update_stale_excerpts(cx);
}
}
_ => {}
@@ -171,14 +166,12 @@ impl ProjectDiagnosticsEditor {
cx.focus(&this.focus_handle);
}
}
EditorEvent::Blurred => this.enqueue_update_stale_excerpts(None),
EditorEvent::Blurred => this.update_stale_excerpts(cx),
_ => {}
}
})
.detach();
let (update_excerpts_tx, mut update_excerpts_rx) = mpsc::unbounded();
let project = project_handle.read(cx);
let mut this = Self {
project: project_handle.clone(),
@@ -191,27 +184,45 @@ impl ProjectDiagnosticsEditor {
path_states: Default::default(),
paths_to_update: Default::default(),
include_warnings: ProjectDiagnosticsSettings::get_global(cx).include_warnings,
update_paths_tx: update_excerpts_tx,
_update_excerpts_task: cx.spawn(move |this, mut cx| async move {
while let Some((path, language_server_id)) = update_excerpts_rx.next().await {
if let Some(buffer) = project_handle
.update(&mut cx, |project, cx| project.open_buffer(path.clone(), cx))?
.await
.log_err()
{
this.update(&mut cx, |this, cx| {
this.update_excerpts(path, language_server_id, buffer, cx);
})?;
}
}
anyhow::Ok(())
}),
update_excerpts_task: None,
_subscription: project_event_subscription,
};
this.enqueue_update_all_excerpts(cx);
this.update_all_excerpts(cx);
this
}
fn update_stale_excerpts(&mut self, cx: &mut ViewContext<Self>) {
if self.update_excerpts_task.is_some() {
return;
}
let project_handle = self.project.clone();
self.update_excerpts_task = Some(cx.spawn(|this, mut cx| async move {
loop {
let Some((path, language_server_id)) = this.update(&mut cx, |this, _| {
let Some((path, language_server_id)) = this.paths_to_update.pop_first() else {
this.update_excerpts_task.take();
return None;
};
Some((path, language_server_id))
})?
else {
break;
};
if let Some(buffer) = project_handle
.update(&mut cx, |project, cx| project.open_buffer(path.clone(), cx))?
.await
.log_err()
{
this.update(&mut cx, |this, cx| {
this.update_excerpts(path, language_server_id, buffer, cx);
})?;
}
}
Ok(())
}));
}
fn new(
project_handle: Model<Project>,
workspace: WeakView<Workspace>,
@@ -239,7 +250,7 @@ impl ProjectDiagnosticsEditor {
fn toggle_warnings(&mut self, _: &ToggleWarnings, cx: &mut ViewContext<Self>) {
self.include_warnings = !self.include_warnings;
self.enqueue_update_all_excerpts(cx);
self.update_all_excerpts(cx);
cx.notify();
}
@@ -251,37 +262,28 @@ impl ProjectDiagnosticsEditor {
fn focus_out(&mut self, cx: &mut ViewContext<Self>) {
if !self.focus_handle.is_focused(cx) && !self.editor.focus_handle(cx).is_focused(cx) {
self.enqueue_update_stale_excerpts(None);
self.update_stale_excerpts(cx);
}
}
/// Enqueue an update of all excerpts. Updates all paths that either
/// currently have diagnostics or are currently present in this view.
fn enqueue_update_all_excerpts(&mut self, cx: &mut ViewContext<Self>) {
fn update_all_excerpts(&mut self, cx: &mut ViewContext<Self>) {
self.project.update(cx, |project, cx| {
let mut paths = project
.diagnostic_summaries(false, cx)
.map(|(path, _, _)| path)
.map(|(path, _, _)| (path, None))
.collect::<BTreeSet<_>>();
paths.extend(self.path_states.iter().map(|state| state.path.clone()));
for path in paths {
self.update_paths_tx.unbounded_send((path, None)).unwrap();
}
paths.extend(
self.path_states
.iter()
.map(|state| (state.path.clone(), None)),
);
let paths_to_update = std::mem::take(&mut self.paths_to_update);
paths.extend(paths_to_update.into_iter().map(|(path, _)| (path, None)));
self.paths_to_update = paths;
});
}
/// Enqueue an update of the excerpts for any path whose diagnostics are known
/// to have changed. If a language server id is passed, then only the excerpts for
/// that language server's diagnostics will be updated. Otherwise, all stale excerpts
/// will be refreshed.
fn enqueue_update_stale_excerpts(&mut self, language_server_id: Option<LanguageServerId>) {
for (path, server_id) in &self.paths_to_update {
if language_server_id.map_or(true, |id| id == *server_id) {
self.update_paths_tx
.unbounded_send((path.clone(), Some(*server_id)))
.unwrap();
}
}
self.update_stale_excerpts(cx);
}
fn update_excerpts(
@@ -291,11 +293,6 @@ impl ProjectDiagnosticsEditor {
buffer: Model<Buffer>,
cx: &mut ViewContext<Self>,
) {
self.paths_to_update.retain(|(path, server_id)| {
*path != path_to_update
|| server_to_update.map_or(false, |to_update| *server_id != to_update)
});
let was_empty = self.path_states.is_empty();
let snapshot = buffer.read(cx).snapshot();
let path_ix = match self

View File

@@ -800,7 +800,7 @@ async fn test_random_diagnostics(cx: &mut TestAppContext, mut rng: StdRng) {
}
log::info!("updating mutated diagnostics view");
mutated_view.update(cx, |view, _| view.enqueue_update_stale_excerpts(None));
mutated_view.update(cx, |view, cx| view.update_stale_excerpts(cx));
cx.run_until_parked();
log::info!("constructing reference diagnostics view");
@@ -986,6 +986,7 @@ fn editor_blocks(
em_width: px(0.),
max_width: px(0.),
block_id,
selected: false,
editor_style: &editor::EditorStyle::default(),
});
let element = element.downcast_mut::<Stateful<Div>>().unwrap();

View File

@@ -14,12 +14,12 @@ impl Render for ToolbarControls {
let mut has_stale_excerpts = false;
let mut is_updating = false;
if let Some(editor) = self.editor() {
let editor = editor.read(cx);
include_warnings = editor.include_warnings;
has_stale_excerpts = !editor.paths_to_update.is_empty();
is_updating = !editor.update_paths_tx.is_empty()
|| editor
if let Some(editor) = self.diagnostics() {
let diagnostics = editor.read(cx);
include_warnings = diagnostics.include_warnings;
has_stale_excerpts = !diagnostics.paths_to_update.is_empty();
is_updating = diagnostics.update_excerpts_task.is_some()
|| diagnostics
.project
.read(cx)
.language_servers_running_disk_based_diagnostics(cx)
@@ -49,9 +49,9 @@ impl Render for ToolbarControls {
.disabled(is_updating)
.tooltip(move |cx| Tooltip::text("Update excerpts", cx))
.on_click(cx.listener(|this, _, cx| {
if let Some(editor) = this.editor() {
editor.update(cx, |editor, _| {
editor.enqueue_update_stale_excerpts(None);
if let Some(diagnostics) = this.diagnostics() {
diagnostics.update(cx, |diagnostics, cx| {
diagnostics.update_all_excerpts(cx);
});
}
})),
@@ -63,7 +63,7 @@ impl Render for ToolbarControls {
.shape(IconButtonShape::Square)
.tooltip(move |cx| Tooltip::text(tooltip, cx))
.on_click(cx.listener(|this, _, cx| {
if let Some(editor) = this.editor() {
if let Some(editor) = this.diagnostics() {
editor.update(cx, |editor, cx| {
editor.toggle_warnings(&Default::default(), cx);
});
@@ -105,7 +105,7 @@ impl ToolbarControls {
ToolbarControls { editor: None }
}
fn editor(&self) -> Option<View<ProjectDiagnosticsEditor>> {
fn diagnostics(&self) -> Option<View<ProjectDiagnosticsEditor>> {
self.editor.as_ref()?.upgrade()
}
}

View File

@@ -77,6 +77,7 @@ tree-sitter-html = { workspace = true, optional = true }
tree-sitter-rust = { workspace = true, optional = true }
tree-sitter-typescript = { workspace = true, optional = true }
unicode-segmentation.workspace = true
unicode-script.workspace = true
unindent = { workspace = true, optional = true }
ui.workspace = true
url.workspace = true

View File

@@ -80,6 +80,8 @@ pub struct ConfirmCodeAction {
pub struct ToggleComments {
#[serde(default)]
pub advance_downwards: bool,
#[serde(default)]
pub ignore_indent: bool,
}
#[derive(PartialEq, Clone, Deserialize, Default)]
@@ -157,6 +159,13 @@ pub struct DeleteToPreviousWordStart {
pub struct FoldAtLevel {
pub level: u32,
}
#[derive(PartialEq, Clone, Deserialize, Default)]
pub struct SpawnNearestTask {
#[serde(default)]
pub reveal: task::RevealStrategy,
}
impl_actions!(
editor,
[
@@ -182,6 +191,7 @@ impl_actions!(
SelectToBeginningOfLine,
SelectToEndOfLine,
SelectUpByLines,
SpawnNearestTask,
ShowCompletions,
ToggleCodeActions,
ToggleComments,

View File

@@ -660,7 +660,7 @@ impl DisplaySnapshot {
new_start..new_end
}
fn point_to_display_point(&self, point: MultiBufferPoint, bias: Bias) -> DisplayPoint {
pub fn point_to_display_point(&self, point: MultiBufferPoint, bias: Bias) -> DisplayPoint {
let inlay_point = self.inlay_snapshot.to_inlay_point(point);
let fold_point = self.fold_snapshot.to_fold_point(inlay_point, bias);
let tab_point = self.tab_snapshot.to_tab_point(fold_point);
@@ -669,7 +669,7 @@ impl DisplaySnapshot {
DisplayPoint(block_point)
}
fn display_point_to_point(&self, point: DisplayPoint, bias: Bias) -> Point {
pub fn display_point_to_point(&self, point: DisplayPoint, bias: Bias) -> Point {
self.inlay_snapshot
.to_buffer_point(self.display_point_to_inlay_point(point, bias))
}
@@ -691,7 +691,7 @@ impl DisplaySnapshot {
fn display_point_to_inlay_point(&self, point: DisplayPoint, bias: Bias) -> InlayPoint {
let block_point = point.0;
let wrap_point = self.block_snapshot.to_wrap_point(block_point);
let wrap_point = self.block_snapshot.to_wrap_point(block_point, bias);
let tab_point = self.wrap_snapshot.to_tab_point(wrap_point);
let fold_point = self.tab_snapshot.to_fold_point(tab_point, bias).0;
fold_point.to_inlay_point(&self.fold_snapshot)
@@ -699,7 +699,7 @@ impl DisplaySnapshot {
pub fn display_point_to_fold_point(&self, point: DisplayPoint, bias: Bias) -> FoldPoint {
let block_point = point.0;
let wrap_point = self.block_snapshot.to_wrap_point(block_point);
let wrap_point = self.block_snapshot.to_wrap_point(block_point, bias);
let tab_point = self.wrap_snapshot.to_tab_point(wrap_point);
self.tab_snapshot.to_fold_point(tab_point, bias).0
}
@@ -990,7 +990,7 @@ impl DisplaySnapshot {
pub fn soft_wrap_indent(&self, display_row: DisplayRow) -> Option<u32> {
let wrap_row = self
.block_snapshot
.to_wrap_point(BlockPoint::new(display_row.0, 0))
.to_wrap_point(BlockPoint::new(display_row.0, 0), Bias::Left)
.row();
self.wrap_snapshot.soft_wrap_indent(wrap_row)
}
@@ -1172,7 +1172,7 @@ impl Sub for DisplayPoint {
#[serde(transparent)]
pub struct DisplayRow(pub u32);
impl Add for DisplayRow {
impl Add<DisplayRow> for DisplayRow {
type Output = Self;
fn add(self, other: Self) -> Self::Output {
@@ -1180,7 +1180,15 @@ impl Add for DisplayRow {
}
}
impl Sub for DisplayRow {
impl Add<u32> for DisplayRow {
type Output = Self;
fn add(self, other: u32) -> Self::Output {
DisplayRow(self.0 + other)
}
}
impl Sub<DisplayRow> for DisplayRow {
type Output = Self;
fn sub(self, other: Self) -> Self::Output {
@@ -1188,6 +1196,14 @@ impl Sub for DisplayRow {
}
}
impl Sub<u32> for DisplayRow {
type Output = Self;
fn sub(self, other: u32) -> Self::Output {
DisplayRow(self.0 - other)
}
}
impl DisplayPoint {
pub fn new(row: DisplayRow, column: u32) -> Self {
Self(BlockPoint(Point::new(row.0, column)))
@@ -1222,7 +1238,7 @@ impl DisplayPoint {
}
pub fn to_offset(self, map: &DisplaySnapshot, bias: Bias) -> usize {
let wrap_point = map.block_snapshot.to_wrap_point(self.0);
let wrap_point = map.block_snapshot.to_wrap_point(self.0, bias);
let tab_point = map.wrap_snapshot.to_tab_point(wrap_point);
let fold_point = map.tab_snapshot.to_fold_point(tab_point, bias).0;
let inlay_point = fold_point.to_inlay_point(&map.fold_snapshot);
@@ -2048,6 +2064,112 @@ pub mod tests {
);
}
#[gpui::test]
async fn test_point_translation_with_replace_blocks(cx: &mut gpui::TestAppContext) {
cx.background_executor
.set_block_on_ticks(usize::MAX..=usize::MAX);
cx.update(|cx| init_test(cx, |_| {}));
let buffer = cx.update(|cx| MultiBuffer::build_simple("abcde\nfghij\nklmno\npqrst", cx));
let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
let map = cx.new_model(|cx| {
DisplayMap::new(
buffer.clone(),
font("Courier"),
px(16.0),
None,
true,
1,
1,
0,
FoldPlaceholder::test(),
cx,
)
});
let snapshot = map.update(cx, |map, cx| {
map.insert_blocks(
[BlockProperties {
placement: BlockPlacement::Replace(
buffer_snapshot.anchor_before(Point::new(1, 2))
..buffer_snapshot.anchor_after(Point::new(2, 3)),
),
height: 4,
style: BlockStyle::Fixed,
render: Box::new(|_| div().into_any()),
priority: 0,
}],
cx,
);
map.snapshot(cx)
});
assert_eq!(snapshot.text(), "abcde\n\n\n\n\npqrst");
let point_to_display_points = [
(Point::new(1, 0), DisplayPoint::new(DisplayRow(1), 0)),
(Point::new(2, 0), DisplayPoint::new(DisplayRow(1), 0)),
(Point::new(3, 0), DisplayPoint::new(DisplayRow(5), 0)),
];
for (buffer_point, display_point) in point_to_display_points {
assert_eq!(
snapshot.point_to_display_point(buffer_point, Bias::Left),
display_point,
"point_to_display_point({:?}, Bias::Left)",
buffer_point
);
assert_eq!(
snapshot.point_to_display_point(buffer_point, Bias::Right),
display_point,
"point_to_display_point({:?}, Bias::Right)",
buffer_point
);
}
let display_points_to_points = [
(
DisplayPoint::new(DisplayRow(1), 0),
Point::new(1, 0),
Point::new(2, 5),
),
(
DisplayPoint::new(DisplayRow(2), 0),
Point::new(1, 0),
Point::new(2, 5),
),
(
DisplayPoint::new(DisplayRow(3), 0),
Point::new(1, 0),
Point::new(2, 5),
),
(
DisplayPoint::new(DisplayRow(4), 0),
Point::new(1, 0),
Point::new(2, 5),
),
(
DisplayPoint::new(DisplayRow(5), 0),
Point::new(3, 0),
Point::new(3, 0),
),
];
for (display_point, left_buffer_point, right_buffer_point) in display_points_to_points {
assert_eq!(
snapshot.display_point_to_point(display_point, Bias::Left),
left_buffer_point,
"display_point_to_point({:?}, Bias::Left)",
display_point
);
assert_eq!(
snapshot.display_point_to_point(display_point, Bias::Right),
right_buffer_point,
"display_point_to_point({:?}, Bias::Right)",
display_point
);
}
}
// todo(linux) fails due to pixel differences in text rendering
#[cfg(target_os = "macos")]
#[gpui::test]

View File

@@ -265,6 +265,7 @@ pub struct BlockContext<'a, 'b> {
pub em_width: Pixels,
pub line_height: Pixels,
pub block_id: BlockId,
pub selected: bool,
pub editor_style: &'b EditorStyle,
}
@@ -1311,7 +1312,6 @@ impl BlockSnapshot {
let (output_start_row, input_start_row) = cursor.start();
let (output_end_row, input_end_row) = cursor.end(&());
let output_start = Point::new(output_start_row.0, 0);
let output_end = Point::new(output_end_row.0, 0);
let input_start = Point::new(input_start_row.0, 0);
let input_end = Point::new(input_end_row.0, 0);
@@ -1319,10 +1319,10 @@ impl BlockSnapshot {
Some(Block::Custom(block))
if matches!(block.placement, BlockPlacement::Replace(_)) =>
{
if bias == Bias::Left {
if ((bias == Bias::Left || search_left) && output_start <= point.0)
|| (!search_left && output_start >= point.0)
{
return BlockPoint(output_start);
} else {
return BlockPoint(Point::new(output_end.row - 1, 0));
}
}
None => {
@@ -1364,12 +1364,7 @@ impl BlockSnapshot {
cursor.seek(&WrapRow(wrap_point.row()), Bias::Right, &());
if let Some(transform) = cursor.item() {
if transform.block.is_some() {
let wrap_start = WrapPoint::new(cursor.start().0 .0, 0);
if wrap_start == wrap_point {
BlockPoint::new(cursor.start().1 .0, 0)
} else {
BlockPoint::new(cursor.end(&()).1 .0 - 1, 0)
}
BlockPoint::new(cursor.start().1 .0, 0)
} else {
let (input_start_row, output_start_row) = cursor.start();
let input_start = Point::new(input_start_row.0, 0);
@@ -1382,7 +1377,7 @@ impl BlockSnapshot {
}
}
pub fn to_wrap_point(&self, block_point: BlockPoint) -> WrapPoint {
pub fn to_wrap_point(&self, block_point: BlockPoint, bias: Bias) -> WrapPoint {
let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
cursor.seek(&BlockRow(block_point.row), Bias::Right, &());
if let Some(transform) = cursor.item() {
@@ -1391,7 +1386,9 @@ impl BlockSnapshot {
if block.place_below() {
let wrap_row = cursor.start().1 .0 - 1;
WrapPoint::new(wrap_row, self.wrap_snapshot.line_len(wrap_row))
} else if block.place_above() || block_point.row == cursor.start().0 .0 {
} else if block.place_above() {
WrapPoint::new(cursor.start().1 .0, 0)
} else if bias == Bias::Left {
WrapPoint::new(cursor.start().1 .0, 0)
} else {
let wrap_row = cursor.end(&()).1 .0 - 1;
@@ -1766,19 +1763,19 @@ mod tests {
);
assert_eq!(
snapshot.to_wrap_point(BlockPoint::new(0, 3)),
snapshot.to_wrap_point(BlockPoint::new(0, 3), Bias::Left),
WrapPoint::new(0, 3)
);
assert_eq!(
snapshot.to_wrap_point(BlockPoint::new(1, 0)),
snapshot.to_wrap_point(BlockPoint::new(1, 0), Bias::Left),
WrapPoint::new(1, 0)
);
assert_eq!(
snapshot.to_wrap_point(BlockPoint::new(3, 0)),
snapshot.to_wrap_point(BlockPoint::new(3, 0), Bias::Left),
WrapPoint::new(1, 0)
);
assert_eq!(
snapshot.to_wrap_point(BlockPoint::new(7, 0)),
snapshot.to_wrap_point(BlockPoint::new(7, 0), Bias::Left),
WrapPoint::new(3, 3)
);
@@ -2616,10 +2613,15 @@ mod tests {
// Ensure that conversion between block points and wrap points is stable.
for row in 0..=blocks_snapshot.wrap_snapshot.max_point().row() {
let original_wrap_point = WrapPoint::new(row, 0);
let block_point = blocks_snapshot.to_block_point(original_wrap_point);
let wrap_point = blocks_snapshot.to_wrap_point(block_point);
assert_eq!(blocks_snapshot.to_block_point(wrap_point), block_point);
let wrap_point = WrapPoint::new(row, 0);
let block_point = blocks_snapshot.to_block_point(wrap_point);
let left_wrap_point = blocks_snapshot.to_wrap_point(block_point, Bias::Left);
let right_wrap_point = blocks_snapshot.to_wrap_point(block_point, Bias::Right);
assert_eq!(blocks_snapshot.to_block_point(left_wrap_point), block_point);
assert_eq!(
blocks_snapshot.to_block_point(right_wrap_point),
block_point
);
}
let mut block_point = BlockPoint::new(0, 0);
@@ -2627,10 +2629,12 @@ mod tests {
let left_point = blocks_snapshot.clip_point(block_point, Bias::Left);
let left_buffer_point = blocks_snapshot.to_point(left_point, Bias::Left);
assert_eq!(
blocks_snapshot.to_block_point(blocks_snapshot.to_wrap_point(left_point)),
blocks_snapshot
.to_block_point(blocks_snapshot.to_wrap_point(left_point, Bias::Left)),
left_point,
"wrap point: {:?}",
blocks_snapshot.to_wrap_point(left_point)
"block point: {:?}, wrap point: {:?}",
block_point,
blocks_snapshot.to_wrap_point(left_point, Bias::Left)
);
assert_eq!(
left_buffer_point,
@@ -2642,10 +2646,12 @@ mod tests {
let right_point = blocks_snapshot.clip_point(block_point, Bias::Right);
let right_buffer_point = blocks_snapshot.to_point(right_point, Bias::Right);
assert_eq!(
blocks_snapshot.to_block_point(blocks_snapshot.to_wrap_point(right_point)),
blocks_snapshot
.to_block_point(blocks_snapshot.to_wrap_point(right_point, Bias::Right)),
right_point,
"wrap point: {:?}",
blocks_snapshot.to_wrap_point(right_point)
"block point: {:?}, wrap point: {:?}",
block_point,
blocks_snapshot.to_wrap_point(right_point, Bias::Right)
);
assert_eq!(
right_buffer_point,
@@ -2681,7 +2687,8 @@ mod tests {
impl BlockSnapshot {
fn to_point(&self, point: BlockPoint, bias: Bias) -> Point {
self.wrap_snapshot.to_point(self.to_wrap_point(point), bias)
self.wrap_snapshot
.to_point(self.to_wrap_point(point, bias), bias)
}
}
}

View File

@@ -103,6 +103,7 @@ pub use proposed_changes_editor::{
ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
};
use similar::{ChangeTag, TextDiff};
use std::iter::Peekable;
use task::{ResolvedTask, TaskTemplate, TaskVariables};
use hover_links::{find_file, HoverLink, HoveredLinkState, InlayHighlight};
@@ -131,7 +132,9 @@ use project::{
use rand::prelude::*;
use rpc::{proto::*, ErrorExt};
use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
use selections_collection::{resolve_multiple, MutableSelectionsCollection, SelectionsCollection};
use selections_collection::{
resolve_selections, MutableSelectionsCollection, SelectionsCollection,
};
use serde::{Deserialize, Serialize};
use settings::{update_settings_file, Settings, SettingsLocation, SettingsStore};
use smallvec::SmallVec;
@@ -502,6 +505,19 @@ struct RunnableTasks {
context_range: Range<BufferOffset>,
}
impl RunnableTasks {
fn resolve<'a>(
&'a self,
cx: &'a task::TaskContext,
) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
self.templates.iter().filter_map(|(kind, template)| {
template
.resolve_task(&kind.to_id_base(), cx)
.map(|task| (kind.clone(), task))
})
}
}
#[derive(Clone)]
struct ResolvedTasks {
templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
@@ -1338,22 +1354,22 @@ impl CompletionsMenu {
// Strong matches are the ones with a high fuzzy-matcher score (the "obvious" matches)
// and the Weak matches are the rest.
//
// For the strong matches, we sort by the language-servers score first and for the weak
// matches, we prefer our fuzzy finder first.
// For the strong matches, we sort by our fuzzy-finder score first and for the weak
// matches, we prefer language-server sort_text first.
//
// The thinking behind that: it's useless to take the sort_text the language-server gives
// us into account when it's obviously a bad match.
// The thinking behind that: we want to show strong matches first in order of relevance(fuzzy score).
// Rest of the matches(weak) can be sorted as language-server expects.
#[derive(PartialEq, Eq, PartialOrd, Ord)]
enum MatchScore<'a> {
Strong {
sort_text: Option<&'a str>,
score: Reverse<OrderedFloat<f64>>,
sort_text: Option<&'a str>,
sort_key: (usize, &'a str),
},
Weak {
score: Reverse<OrderedFloat<f64>>,
sort_text: Option<&'a str>,
score: Reverse<OrderedFloat<f64>>,
sort_key: (usize, &'a str),
},
}
@@ -1365,14 +1381,14 @@ impl CompletionsMenu {
if mat.score >= 0.2 {
MatchScore::Strong {
sort_text,
score,
sort_text,
sort_key,
}
} else {
MatchScore::Weak {
score,
sort_text,
score,
sort_key,
}
}
@@ -3471,8 +3487,8 @@ impl Editor {
}
let new_anchor_selections = new_selections.iter().map(|e| &e.0);
let new_selection_deltas = new_selections.iter().map(|e| e.1);
let snapshot = this.buffer.read(cx).read(cx);
let new_selections = resolve_multiple::<usize, _>(new_anchor_selections, &snapshot)
let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
.zip(new_selection_deltas)
.map(|(selection, delta)| Selection {
id: selection.id,
@@ -3485,18 +3501,20 @@ impl Editor {
let mut i = 0;
for (position, delta, selection_id, pair) in new_autoclose_regions {
let position = position.to_offset(&snapshot) + delta;
let start = snapshot.anchor_before(position);
let end = snapshot.anchor_after(position);
let position = position.to_offset(&map.buffer_snapshot) + delta;
let start = map.buffer_snapshot.anchor_before(position);
let end = map.buffer_snapshot.anchor_after(position);
while let Some(existing_state) = this.autoclose_regions.get(i) {
match existing_state.range.start.cmp(&start, &snapshot) {
match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
Ordering::Less => i += 1,
Ordering::Greater => break,
Ordering::Equal => match end.cmp(&existing_state.range.end, &snapshot) {
Ordering::Less => i += 1,
Ordering::Equal => break,
Ordering::Greater => break,
},
Ordering::Equal => {
match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
Ordering::Less => i += 1,
Ordering::Equal => break,
Ordering::Greater => break,
}
}
}
}
this.autoclose_regions.insert(
@@ -3509,7 +3527,6 @@ impl Editor {
);
}
drop(snapshot);
let had_active_inline_completion = this.has_active_inline_completion(cx);
this.change_selections_inner(Some(Autoscroll::fit()), false, cx, |s| {
s.select(new_selections)
@@ -4025,7 +4042,7 @@ impl Editor {
}
}
(selection.clone(), enclosing)
(selection, enclosing)
})
}
@@ -4723,29 +4740,7 @@ impl Editor {
.as_ref()
.zip(editor.project.clone())
.map(|(tasks, project)| {
let position = Point::new(buffer_row, tasks.column);
let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
let location = Location {
buffer: buffer.clone(),
range: range_start..range_start,
};
// Fill in the environmental variables from the tree-sitter captures
let mut captured_task_variables = TaskVariables::default();
for (capture_name, value) in tasks.extra_variables.clone() {
captured_task_variables.insert(
task::VariableName::Custom(capture_name.into()),
value.clone(),
);
}
project.update(cx, |project, cx| {
project.task_store().update(cx, |task_store, cx| {
task_store.task_context_for_location(
captured_task_variables,
location,
cx,
)
})
})
Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
});
Some(cx.spawn(|editor, mut cx| async move {
@@ -4756,15 +4751,7 @@ impl Editor {
let resolved_tasks =
tasks.zip(task_context).map(|(tasks, task_context)| {
Arc::new(ResolvedTasks {
templates: tasks
.templates
.iter()
.filter_map(|(kind, template)| {
template
.resolve_task(&kind.to_id_base(), &task_context)
.map(|task| (kind.clone(), task))
})
.collect(),
templates: tasks.resolve(&task_context).collect(),
position: snapshot.buffer_snapshot.anchor_before(Point::new(
multibuffer_point.row,
tasks.column,
@@ -5470,6 +5457,132 @@ impl Editor {
}
}
fn build_tasks_context(
project: &Model<Project>,
buffer: &Model<Buffer>,
buffer_row: u32,
tasks: &Arc<RunnableTasks>,
cx: &mut ViewContext<Self>,
) -> Task<Option<task::TaskContext>> {
let position = Point::new(buffer_row, tasks.column);
let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
let location = Location {
buffer: buffer.clone(),
range: range_start..range_start,
};
// Fill in the environmental variables from the tree-sitter captures
let mut captured_task_variables = TaskVariables::default();
for (capture_name, value) in tasks.extra_variables.clone() {
captured_task_variables.insert(
task::VariableName::Custom(capture_name.into()),
value.clone(),
);
}
project.update(cx, |project, cx| {
project.task_store().update(cx, |task_store, cx| {
task_store.task_context_for_location(captured_task_variables, location, cx)
})
})
}
pub fn spawn_nearest_task(&mut self, action: &SpawnNearestTask, cx: &mut ViewContext<Self>) {
let Some((workspace, _)) = self.workspace.clone() else {
return;
};
let Some(project) = self.project.clone() else {
return;
};
// Try to find a closest, enclosing node using tree-sitter that has a
// task
let Some((buffer, buffer_row, tasks)) = self
.find_enclosing_node_task(cx)
// Or find the task that's closest in row-distance.
.or_else(|| self.find_closest_task(cx))
else {
return;
};
let reveal_strategy = action.reveal;
let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
cx.spawn(|_, mut cx| async move {
let context = task_context.await?;
let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
let resolved = resolved_task.resolved.as_mut()?;
resolved.reveal = reveal_strategy;
workspace
.update(&mut cx, |workspace, cx| {
workspace::tasks::schedule_resolved_task(
workspace,
task_source_kind,
resolved_task,
false,
cx,
);
})
.ok()
})
.detach();
}
fn find_closest_task(
&mut self,
cx: &mut ViewContext<Self>,
) -> Option<(Model<Buffer>, u32, Arc<RunnableTasks>)> {
let cursor_row = self.selections.newest_adjusted(cx).head().row;
let ((buffer_id, row), tasks) = self
.tasks
.iter()
.min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
let tasks = Arc::new(tasks.to_owned());
Some((buffer, *row, tasks))
}
fn find_enclosing_node_task(
&mut self,
cx: &mut ViewContext<Self>,
) -> Option<(Model<Buffer>, u32, Arc<RunnableTasks>)> {
let snapshot = self.buffer.read(cx).snapshot(cx);
let offset = self.selections.newest::<usize>(cx).head();
let excerpt = snapshot.excerpt_containing(offset..offset)?;
let buffer_id = excerpt.buffer().remote_id();
let layer = excerpt.buffer().syntax_layer_at(offset)?;
let mut cursor = layer.node().walk();
while cursor.goto_first_child_for_byte(offset).is_some() {
if cursor.node().end_byte() == offset {
cursor.goto_next_sibling();
}
}
// Ascend to the smallest ancestor that contains the range and has a task.
loop {
let node = cursor.node();
let node_range = node.byte_range();
let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
// Check if this node contains our offset
if node_range.start <= offset && node_range.end >= offset {
// If it contains offset, check for task
if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
let buffer = self.buffer.read(cx).buffer(buffer_id)?;
return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
}
}
if !cursor.goto_parent() {
break;
}
}
None
}
fn render_run_indicator(
&self,
_style: &EditorStyle,
@@ -6896,6 +7009,8 @@ impl Editor {
}
}
let tab_size = buffer.settings_at(selection.head(), cx).tab_size;
// Since not all lines in the selection may be at the same indent
// level, choose the indent size that is the most common between all
// of the lines.
@@ -6913,7 +7028,7 @@ impl Editor {
let indent_size = indent_size_occurrences
.into_iter()
.max_by_key(|(indent, count)| (*count, indent.len))
.max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
.map(|(indent, _)| indent)
.unwrap_or_default();
let row = rows_by_indent_size[&indent_size][0];
@@ -6939,6 +7054,10 @@ impl Editor {
should_rewrap = true;
}
if !should_rewrap {
continue;
}
if selection.is_empty() {
'expand_upwards: while start_row > 0 {
let prev_row = start_row - 1;
@@ -6963,10 +7082,6 @@ impl Editor {
}
}
if !should_rewrap {
continue;
}
let start = Point::new(start_row, 0);
let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
let selection_text = buffer.text_for_range(start..end).collect::<String>();
@@ -6985,29 +7100,15 @@ impl Editor {
continue;
};
let unwrapped_text = lines_without_prefixes.join(" ");
let wrap_column = buffer
.settings_at(Point::new(start_row, 0), cx)
.preferred_line_length as usize;
let mut wrapped_text = String::new();
let mut current_line = line_prefix.clone();
for word in unwrapped_text.split_whitespace() {
if current_line.len() + word.len() >= wrap_column {
wrapped_text.push_str(&current_line);
wrapped_text.push('\n');
current_line.truncate(line_prefix.len());
}
if current_line.len() > line_prefix.len() {
current_line.push(' ');
}
current_line.push_str(word);
}
if !current_line.is_empty() {
wrapped_text.push_str(&current_line);
}
let wrapped_text = wrap_with_prefix(
line_prefix,
lines_without_prefixes.join(" "),
wrap_column,
tab_size,
);
let diff = TextDiff::from_lines(&selection_text, &wrapped_text);
let mut offset = start.to_offset(&buffer);
@@ -7168,9 +7269,14 @@ impl Editor {
if clipboard_selections.len() != old_selections.len() {
clipboard_selections.drain(..);
}
let cursor_offset = this.selections.last::<usize>(cx).head();
let mut auto_indent_on_paste = true;
this.buffer.update(cx, |buffer, cx| {
let snapshot = buffer.read(cx);
auto_indent_on_paste =
snapshot.settings_at(cursor_offset, cx).auto_indent_on_paste;
let mut start_offset = 0;
let mut edits = Vec::new();
let mut original_indent_columns = Vec::new();
@@ -7209,9 +7315,13 @@ impl Editor {
buffer.edit(
edits,
Some(AutoindentMode::Block {
original_indent_columns,
}),
if auto_indent_on_paste {
Some(AutoindentMode::Block {
original_indent_columns,
})
} else {
None
},
cx,
);
});
@@ -8664,14 +8774,22 @@ impl Editor {
let snapshot = this.buffer.read(cx).read(cx);
let empty_str: Arc<str> = Arc::default();
let mut suffixes_inserted = Vec::new();
let ignore_indent = action.ignore_indent;
fn comment_prefix_range(
snapshot: &MultiBufferSnapshot,
row: MultiBufferRow,
comment_prefix: &str,
comment_prefix_whitespace: &str,
ignore_indent: bool,
) -> Range<Point> {
let start = Point::new(row.0, snapshot.indent_size_for_line(row).len);
let indent_size = if ignore_indent {
0
} else {
snapshot.indent_size_for_line(row).len
};
let start = Point::new(row.0, indent_size);
let mut line_bytes = snapshot
.bytes_in_range(start..snapshot.max_point())
@@ -8767,7 +8885,16 @@ impl Editor {
}
// If the language has line comments, toggle those.
let full_comment_prefixes = language.line_comment_prefixes();
let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
// If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
if ignore_indent {
full_comment_prefixes = full_comment_prefixes
.into_iter()
.map(|s| Arc::from(s.trim_end()))
.collect();
}
if !full_comment_prefixes.is_empty() {
let first_prefix = full_comment_prefixes
.first()
@@ -8794,6 +8921,7 @@ impl Editor {
row,
&prefix[..trimmed_prefix_len],
&prefix[trimmed_prefix_len..],
ignore_indent,
)
})
.max_by_key(|range| range.end.column - range.start.column)
@@ -8834,6 +8962,7 @@ impl Editor {
start_row,
comment_prefix,
comment_prefix_whitespace,
ignore_indent,
);
let suffix_range = comment_suffix_range(
snapshot.deref(),
@@ -12916,6 +13045,289 @@ impl Editor {
}
}
fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
let tab_size = tab_size.get() as usize;
let mut width = offset;
for ch in text.chars() {
width += if ch == '\t' {
tab_size - (width % tab_size)
} else {
1
};
}
width - offset
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_string_size_with_expanded_tabs() {
let nz = |val| NonZeroU32::new(val).unwrap();
assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
}
}
/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
struct WordBreakingTokenizer<'a> {
input: &'a str,
}
impl<'a> WordBreakingTokenizer<'a> {
fn new(input: &'a str) -> Self {
Self { input }
}
}
fn is_char_ideographic(ch: char) -> bool {
use unicode_script::Script::*;
use unicode_script::UnicodeScript;
matches!(ch.script(), Han | Tangut | Yi)
}
fn is_grapheme_ideographic(text: &str) -> bool {
text.chars().any(is_char_ideographic)
}
fn is_grapheme_whitespace(text: &str) -> bool {
text.chars().any(|x| x.is_whitespace())
}
fn should_stay_with_preceding_ideograph(text: &str) -> bool {
text.chars().next().map_or(false, |ch| {
matches!(ch, '。' | '、' | '' | '' | '' | '' | '' | '…')
})
}
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
struct WordBreakToken<'a> {
token: &'a str,
grapheme_len: usize,
is_whitespace: bool,
}
impl<'a> Iterator for WordBreakingTokenizer<'a> {
/// Yields a span, the count of graphemes in the token, and whether it was
/// whitespace. Note that it also breaks at word boundaries.
type Item = WordBreakToken<'a>;
fn next(&mut self) -> Option<Self::Item> {
use unicode_segmentation::UnicodeSegmentation;
if self.input.is_empty() {
return None;
}
let mut iter = self.input.graphemes(true).peekable();
let mut offset = 0;
let mut graphemes = 0;
if let Some(first_grapheme) = iter.next() {
let is_whitespace = is_grapheme_whitespace(first_grapheme);
offset += first_grapheme.len();
graphemes += 1;
if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
if let Some(grapheme) = iter.peek().copied() {
if should_stay_with_preceding_ideograph(grapheme) {
offset += grapheme.len();
graphemes += 1;
}
}
} else {
let mut words = self.input[offset..].split_word_bound_indices().peekable();
let mut next_word_bound = words.peek().copied();
if next_word_bound.map_or(false, |(i, _)| i == 0) {
next_word_bound = words.next();
}
while let Some(grapheme) = iter.peek().copied() {
if next_word_bound.map_or(false, |(i, _)| i == offset) {
break;
};
if is_grapheme_whitespace(grapheme) != is_whitespace {
break;
};
offset += grapheme.len();
graphemes += 1;
iter.next();
}
}
let token = &self.input[..offset];
self.input = &self.input[offset..];
if is_whitespace {
Some(WordBreakToken {
token: " ",
grapheme_len: 1,
is_whitespace: true,
})
} else {
Some(WordBreakToken {
token,
grapheme_len: graphemes,
is_whitespace: false,
})
}
} else {
None
}
}
}
#[test]
fn test_word_breaking_tokenizer() {
let tests: &[(&str, &[(&str, usize, bool)])] = &[
("", &[]),
(" ", &[(" ", 1, true)]),
("Ʒ", &[("Ʒ", 1, false)]),
("Ǽ", &[("Ǽ", 1, false)]),
("", &[("", 1, false)]),
("⋑⋑", &[("⋑⋑", 2, false)]),
(
"原理,进而",
&[
("", 1, false),
("理,", 2, false),
("", 1, false),
("", 1, false),
],
),
(
"hello world",
&[("hello", 5, false), (" ", 1, true), ("world", 5, false)],
),
(
"hello, world",
&[("hello,", 6, false), (" ", 1, true), ("world", 5, false)],
),
(
" hello world",
&[
(" ", 1, true),
("hello", 5, false),
(" ", 1, true),
("world", 5, false),
],
),
(
"这是什么 \n 钢笔",
&[
("", 1, false),
("", 1, false),
("", 1, false),
("", 1, false),
(" ", 1, true),
("", 1, false),
("", 1, false),
],
),
("mutton", &[(" ", 1, true), ("mutton", 6, false)]),
];
for (input, result) in tests {
assert_eq!(
WordBreakingTokenizer::new(input).collect::<Vec<_>>(),
result
.iter()
.copied()
.map(|(token, grapheme_len, is_whitespace)| WordBreakToken {
token,
grapheme_len,
is_whitespace,
})
.collect::<Vec<_>>()
);
}
}
fn wrap_with_prefix(
line_prefix: String,
unwrapped_text: String,
wrap_column: usize,
tab_size: NonZeroU32,
) -> String {
let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
let mut wrapped_text = String::new();
let mut current_line = line_prefix.clone();
let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
let mut current_line_len = line_prefix_len;
for WordBreakToken {
token,
grapheme_len,
is_whitespace,
} in tokenizer
{
if current_line_len + grapheme_len > wrap_column && current_line_len != line_prefix_len {
wrapped_text.push_str(current_line.trim_end());
wrapped_text.push('\n');
current_line.truncate(line_prefix.len());
current_line_len = line_prefix_len;
if !is_whitespace {
current_line.push_str(token);
current_line_len += grapheme_len;
}
} else if !is_whitespace {
current_line.push_str(token);
current_line_len += grapheme_len;
} else if current_line_len != line_prefix_len {
current_line.push(' ');
current_line_len += 1;
}
}
if !current_line.is_empty() {
wrapped_text.push_str(&current_line);
}
wrapped_text
}
#[test]
fn test_wrap_with_prefix() {
assert_eq!(
wrap_with_prefix(
"# ".to_string(),
"abcdefg".to_string(),
4,
NonZeroU32::new(4).unwrap()
),
"# abcdefg"
);
assert_eq!(
wrap_with_prefix(
"".to_string(),
"\thello world".to_string(),
8,
NonZeroU32::new(4).unwrap()
),
"hello\nworld"
);
assert_eq!(
wrap_with_prefix(
"// ".to_string(),
"xx \nyy zz aa bb cc".to_string(),
12,
NonZeroU32::new(4).unwrap()
),
"// xx yy zz\n// aa bb cc"
);
assert_eq!(
wrap_with_prefix(
String::new(),
"这是什么 \n 钢笔".to_string(),
3,
NonZeroU32::new(4).unwrap()
),
"这是什\n么 钢\n"
);
}
fn hunks_for_selections(
multi_buffer_snapshot: &MultiBufferSnapshot,
selections: &[Selection<Anchor>],
@@ -13415,7 +13827,7 @@ fn consume_contiguous_rows(
contiguous_row_selections: &mut Vec<Selection<Point>>,
selection: &Selection<Point>,
display_map: &DisplaySnapshot,
selections: &mut std::iter::Peekable<std::slice::Iter<Selection<Point>>>,
selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
) -> (MultiBufferRow, MultiBufferRow) {
contiguous_row_selections.push(selection.clone());
let start_row = MultiBufferRow(selection.start.row);

View File

@@ -3989,6 +3989,76 @@ fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
});
}
#[gpui::test]
async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorTestContext::new(cx).await;
cx.set_state(
&"
ˇzero
one
two
three
four
five
"
.unindent(),
);
// Create a four-line block that replaces three lines of text.
cx.update_editor(|editor, cx| {
let snapshot = editor.snapshot(cx);
let snapshot = &snapshot.buffer_snapshot;
let placement = BlockPlacement::Replace(
snapshot.anchor_after(Point::new(1, 0))..snapshot.anchor_after(Point::new(3, 0)),
);
editor.insert_blocks(
[BlockProperties {
placement,
height: 4,
style: BlockStyle::Sticky,
render: Box::new(|_| gpui::div().into_any_element()),
priority: 0,
}],
None,
cx,
);
});
// Move down so that the cursor touches the block.
cx.update_editor(|editor, cx| {
editor.move_down(&Default::default(), cx);
});
cx.assert_editor_state(
&"
zero
«one
two
threeˇ»
four
five
"
.unindent(),
);
// Move down past the block.
cx.update_editor(|editor, cx| {
editor.move_down(&Default::default(), cx);
});
cx.assert_editor_state(
&"
zero
one
two
three
ˇfour
five
"
.unindent(),
);
}
#[gpui::test]
fn test_transpose(cx: &mut TestAppContext) {
init_test(cx, |_| {});
@@ -4182,7 +4252,7 @@ async fn test_rewrap(cx: &mut TestAppContext) {
// et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
// dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
// viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
// porttitor id. Aliquam id accumsan eros.ˇˇˇˇ
// porttitor id. Aliquam id accumsan eros.ˇ
"};
cx.set_state(unwrapped_text);
@@ -4212,7 +4282,7 @@ async fn test_rewrap(cx: &mut TestAppContext) {
let wrapped_text = indoc! {"
// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
// purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
// auctor, eu lacinia sapien scelerisque.ˇˇ
// auctor, eu lacinia sapien scelerisque.ˇ
//
// Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
// tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
@@ -4220,7 +4290,7 @@ async fn test_rewrap(cx: &mut TestAppContext) {
// molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
// nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
// porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
// vulputate turpis porttitor id. Aliquam id accumsan eros.ˇˇ
// vulputate turpis porttitor id. Aliquam id accumsan eros.ˇ
"};
cx.set_state(unwrapped_text);
@@ -8315,6 +8385,74 @@ async fn test_completion_page_up_down_keys(cx: &mut gpui::TestAppContext) {
});
}
#[gpui::test]
async fn test_completion_sort(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorLspTestContext::new_rust(
lsp::ServerCapabilities {
completion_provider: Some(lsp::CompletionOptions {
trigger_characters: Some(vec![".".to_string()]),
..Default::default()
}),
..Default::default()
},
cx,
)
.await;
cx.lsp
.handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
Ok(Some(lsp::CompletionResponse::Array(vec![
lsp::CompletionItem {
label: "Range".into(),
sort_text: Some("a".into()),
..Default::default()
},
lsp::CompletionItem {
label: "r".into(),
sort_text: Some("b".into()),
..Default::default()
},
lsp::CompletionItem {
label: "ret".into(),
sort_text: Some("c".into()),
..Default::default()
},
lsp::CompletionItem {
label: "return".into(),
sort_text: Some("d".into()),
..Default::default()
},
lsp::CompletionItem {
label: "slice".into(),
sort_text: Some("d".into()),
..Default::default()
},
])))
});
cx.set_state("");
cx.executor().run_until_parked();
cx.update_editor(|editor, cx| {
editor.show_completions(
&ShowCompletions {
trigger: Some("r".into()),
},
cx,
);
});
cx.executor().run_until_parked();
cx.update_editor(|editor, _| {
if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
assert_eq!(
menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
&["r", "ret", "Range", "return"]
);
} else {
panic!("expected completion menu to be open");
}
});
}
#[gpui::test]
async fn test_no_duplicated_completion_requests(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
@@ -8533,6 +8671,131 @@ async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
"});
}
#[gpui::test]
async fn test_toggle_comment_ignore_indent(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorTestContext::new(cx).await;
let language = Arc::new(Language::new(
LanguageConfig {
line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
..Default::default()
},
Some(tree_sitter_rust::LANGUAGE.into()),
));
cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
let toggle_comments = &ToggleComments {
advance_downwards: false,
ignore_indent: true,
};
// If multiple selections intersect a line, the line is only toggled once.
cx.set_state(indoc! {"
fn a() {
// «b();
// c();
// ˇ» d();
}
"});
cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
cx.assert_editor_state(indoc! {"
fn a() {
«b();
c();
ˇ» d();
}
"});
// The comment prefix is inserted at the beginning of each line
cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
cx.assert_editor_state(indoc! {"
fn a() {
// «b();
// c();
// ˇ» d();
}
"});
// If a selection ends at the beginning of a line, that line is not toggled.
cx.set_selections_state(indoc! {"
fn a() {
// b();
// «c();
ˇ»// d();
}
"});
cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
cx.assert_editor_state(indoc! {"
fn a() {
// b();
«c();
ˇ»// d();
}
"});
// If a selection span a single line and is empty, the line is toggled.
cx.set_state(indoc! {"
fn a() {
a();
b();
ˇ
}
"});
cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
cx.assert_editor_state(indoc! {"
fn a() {
a();
b();
//ˇ
}
"});
// If a selection span multiple lines, empty lines are not toggled.
cx.set_state(indoc! {"
fn a() {
«a();
c();ˇ»
}
"});
cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
cx.assert_editor_state(indoc! {"
fn a() {
// «a();
// c();ˇ»
}
"});
// If a selection includes multiple comment prefixes, all lines are uncommented.
cx.set_state(indoc! {"
fn a() {
// «a();
/// b();
//! c();ˇ»
}
"});
cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
cx.assert_editor_state(indoc! {"
fn a() {
«a();
b();
c();ˇ»
}
"});
}
#[gpui::test]
async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
@@ -8554,6 +8817,7 @@ async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext)
let toggle_comments = &ToggleComments {
advance_downwards: true,
ignore_indent: false,
};
// Single cursor on one line -> advance
@@ -13204,6 +13468,89 @@ async fn test_goto_definition_with_find_all_references_fallback(cx: &mut gpui::T
});
}
#[gpui::test]
async fn test_find_enclosing_node_with_task(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let language = Arc::new(Language::new(
LanguageConfig::default(),
Some(tree_sitter_rust::LANGUAGE.into()),
));
let text = r#"
#[cfg(test)]
mod tests() {
#[test]
fn runnable_1() {
let a = 1;
}
#[test]
fn runnable_2() {
let a = 1;
let b = 2;
}
}
"#
.unindent();
let fs = FakeFs::new(cx.executor());
fs.insert_file("/file.rs", Default::default()).await;
let project = Project::test(fs, ["/a".as_ref()], cx).await;
let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
let multi_buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer.clone(), cx));
let editor = cx.new_view(|cx| {
Editor::new(
EditorMode::Full,
multi_buffer,
Some(project.clone()),
true,
cx,
)
});
editor.update(cx, |editor, cx| {
editor.tasks.insert(
(buffer.read(cx).remote_id(), 3),
RunnableTasks {
templates: vec![],
offset: MultiBufferOffset(43),
column: 0,
extra_variables: HashMap::default(),
context_range: BufferOffset(43)..BufferOffset(85),
},
);
editor.tasks.insert(
(buffer.read(cx).remote_id(), 8),
RunnableTasks {
templates: vec![],
offset: MultiBufferOffset(86),
column: 0,
extra_variables: HashMap::default(),
context_range: BufferOffset(86)..BufferOffset(191),
},
);
// Test finding task when cursor is inside function body
editor.change_selections(None, cx, |s| {
s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
});
let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
// Test finding task when cursor is on function name
editor.change_selections(None, cx, |s| {
s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
});
let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
assert_eq!(row, 8, "Should find task when cursor is on function name");
});
}
fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
point..point

View File

@@ -25,7 +25,7 @@ use crate::{
MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
};
use client::ParticipantIndex;
use collections::{BTreeMap, HashMap};
use collections::{BTreeMap, HashMap, HashSet};
use git::{blame::BlameEntry, diff::DiffHunkStatus, Oid};
use gpui::Subscription;
use gpui::{
@@ -449,7 +449,8 @@ impl EditorElement {
register_action(view, cx, Editor::apply_all_diff_hunks);
register_action(view, cx, Editor::apply_selected_diff_hunks);
register_action(view, cx, Editor::open_active_item_in_terminal);
register_action(view, cx, Editor::reload_file)
register_action(view, cx, Editor::reload_file);
register_action(view, cx, Editor::spawn_nearest_task);
}
fn register_key_listeners(&self, cx: &mut WindowContext, layout: &EditorLayout) {
@@ -808,10 +809,12 @@ impl EditorElement {
cx.notify()
}
#[allow(clippy::too_many_arguments)]
fn layout_selections(
&self,
start_anchor: Anchor,
end_anchor: Anchor,
local_selections: &[Selection<Point>],
snapshot: &EditorSnapshot,
start_row: DisplayRow,
end_row: DisplayRow,
@@ -826,13 +829,9 @@ impl EditorElement {
let mut newest_selection_head = None;
self.editor.update(cx, |editor, cx| {
if editor.show_local_selections {
let mut local_selections: Vec<Selection<Point>> = editor
.selections
.disjoint_in_range(start_anchor..end_anchor, cx);
local_selections.extend(editor.selections.pending(cx));
let mut layouts = Vec::new();
let newest = editor.selections.newest(cx);
for selection in local_selections.drain(..) {
for selection in local_selections.iter().cloned() {
let is_empty = selection.start == selection.end;
let is_newest = selection == newest;
@@ -995,6 +994,7 @@ impl EditorElement {
&self,
snapshot: &EditorSnapshot,
selections: &[(PlayerColor, Vec<SelectionLayout>)],
block_start_rows: &HashSet<DisplayRow>,
visible_display_row_range: Range<DisplayRow>,
line_layouts: &[LineWithInvisibles],
text_hitbox: &Hitbox,
@@ -1014,7 +1014,10 @@ impl EditorElement {
let cursor_position = selection.head;
let in_range = visible_display_row_range.contains(&cursor_position.row());
if (selection.is_local && !editor.show_local_cursors(cx)) || !in_range {
if (selection.is_local && !editor.show_local_cursors(cx))
|| !in_range
|| block_start_rows.contains(&cursor_position.row())
{
continue;
}
@@ -2067,14 +2070,14 @@ impl EditorElement {
editor_width: Pixels,
scroll_width: &mut Pixels,
resized_blocks: &mut HashMap<CustomBlockId, u32>,
selections: &[Selection<Point>],
cx: &mut WindowContext,
) -> (AnyElement, Size<Pixels>) {
let mut element = match block {
Block::Custom(block) => {
let align_to = block
.start()
.to_point(&snapshot.buffer_snapshot)
.to_display_point(snapshot);
let block_start = block.start().to_point(&snapshot.buffer_snapshot);
let block_end = block.end().to_point(&snapshot.buffer_snapshot);
let align_to = block_start.to_display_point(snapshot);
let anchor_x = text_x
+ if rows.contains(&align_to.row()) {
line_layouts[align_to.row().minus(rows.start) as usize]
@@ -2084,6 +2087,18 @@ impl EditorElement {
.x_for_index(align_to.column() as usize)
};
let selected = selections
.binary_search_by(|selection| {
if selection.end <= block_start {
Ordering::Less
} else if selection.start >= block_end {
Ordering::Greater
} else {
Ordering::Equal
}
})
.is_ok();
div()
.size_full()
.child(block.render(&mut BlockContext {
@@ -2093,6 +2108,7 @@ impl EditorElement {
line_height,
em_width,
block_id,
selected,
max_width: text_hitbox.size.width.max(*scroll_width),
editor_style: &self.style,
}))
@@ -2430,6 +2446,7 @@ impl EditorElement {
text_x: Pixels,
line_height: Pixels,
line_layouts: &[LineWithInvisibles],
selections: &[Selection<Point>],
cx: &mut WindowContext,
) -> Result<Vec<BlockLayout>, HashMap<CustomBlockId, u32>> {
let (fixed_blocks, non_fixed_blocks) = snapshot
@@ -2466,6 +2483,7 @@ impl EditorElement {
editor_width,
scroll_width,
&mut resized_blocks,
selections,
cx,
);
fixed_block_max_width = fixed_block_max_width.max(element_size.width + em_width);
@@ -2510,6 +2528,7 @@ impl EditorElement {
editor_width,
scroll_width,
&mut resized_blocks,
selections,
cx,
);
@@ -2555,6 +2574,7 @@ impl EditorElement {
editor_width,
scroll_width,
&mut resized_blocks,
selections,
cx,
);
@@ -2583,6 +2603,7 @@ impl EditorElement {
fn layout_blocks(
&self,
blocks: &mut Vec<BlockLayout>,
block_starts: &mut HashSet<DisplayRow>,
hitbox: &Hitbox,
line_height: Pixels,
scroll_pixel_position: gpui::Point<Pixels>,
@@ -2590,6 +2611,7 @@ impl EditorElement {
) {
for block in blocks {
let mut origin = if let Some(row) = block.row {
block_starts.insert(row);
hitbox.origin
+ point(
Pixels::ZERO,
@@ -5100,9 +5122,19 @@ impl Element for EditorElement {
cx,
);
let local_selections: Vec<Selection<Point>> =
self.editor.update(cx, |editor, cx| {
let mut selections = editor
.selections
.disjoint_in_range(start_anchor..end_anchor, cx);
selections.extend(editor.selections.pending(cx));
selections
});
let (selections, active_rows, newest_selection_head) = self.layout_selections(
start_anchor,
end_anchor,
&local_selections,
&snapshot,
start_row,
end_row,
@@ -5175,6 +5207,7 @@ impl Element for EditorElement {
gutter_dimensions.full_width(),
line_height,
&line_layouts,
&local_selections,
cx,
)
});
@@ -5314,9 +5347,11 @@ impl Element for EditorElement {
cx,
);
let mut block_start_rows = HashSet::default();
cx.with_element_namespace("blocks", |cx| {
self.layout_blocks(
&mut blocks,
&mut block_start_rows,
&hitbox,
line_height,
scroll_pixel_position,
@@ -5333,6 +5368,7 @@ impl Element for EditorElement {
let visible_cursors = self.layout_visible_cursors(
&snapshot,
&selections,
&block_start_rows,
start_row..end_row,
&line_layouts,
&text_hitbox,

View File

@@ -7,7 +7,7 @@ use crate::{
};
use gpui::{
div, px, AnyElement, AsyncWindowContext, FontWeight, Hsla, InteractiveElement, IntoElement,
MouseButton, ParentElement, Pixels, ScrollHandle, Size, StatefulInteractiveElement,
MouseButton, ParentElement, Pixels, ScrollHandle, Size, Stateful, StatefulInteractiveElement,
StyleRefinement, Styled, Task, TextStyleRefinement, View, ViewContext,
};
use itertools::Itertools;
@@ -21,7 +21,7 @@ use std::rc::Rc;
use std::{borrow::Cow, cell::RefCell};
use std::{ops::Range, sync::Arc, time::Duration};
use theme::ThemeSettings;
use ui::{prelude::*, window_is_transparent};
use ui::{prelude::*, window_is_transparent, Scrollbar, ScrollbarState};
use util::TryFutureExt;
pub const HOVER_DELAY_MILLIS: u64 = 350;
pub const HOVER_REQUEST_DELAY_MILLIS: u64 = 200;
@@ -144,10 +144,12 @@ pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut Vie
let blocks = vec![inlay_hover.tooltip];
let parsed_content = parse_blocks(&blocks, &language_registry, None, &mut cx).await;
let scroll_handle = ScrollHandle::new();
let hover_popover = InfoPopover {
symbol_range: RangeInEditor::Inlay(inlay_hover.range.clone()),
parsed_content,
scroll_handle: ScrollHandle::new(),
scrollbar_state: ScrollbarState::new(scroll_handle.clone()),
scroll_handle,
keyboard_grace: Rc::new(RefCell::new(false)),
anchor: None,
};
@@ -435,12 +437,14 @@ fn show_hover(
let language = hover_result.language;
let parsed_content =
parse_blocks(&blocks, &language_registry, language, &mut cx).await;
let scroll_handle = ScrollHandle::new();
info_popover_tasks.push((
range.clone(),
InfoPopover {
symbol_range: RangeInEditor::Text(range),
parsed_content,
scroll_handle: ScrollHandle::new(),
scrollbar_state: ScrollbarState::new(scroll_handle.clone()),
scroll_handle,
keyboard_grace: Rc::new(RefCell::new(ignore_timeout)),
anchor: Some(anchor),
},
@@ -611,7 +615,7 @@ impl HoverState {
!self.info_popovers.is_empty() || self.diagnostic_popover.is_some()
}
pub fn render(
pub(crate) fn render(
&mut self,
snapshot: &EditorSnapshot,
visible_rows: Range<DisplayRow>,
@@ -680,25 +684,25 @@ impl HoverState {
}
#[derive(Debug, Clone)]
pub struct InfoPopover {
pub symbol_range: RangeInEditor,
pub parsed_content: Option<View<Markdown>>,
pub scroll_handle: ScrollHandle,
pub keyboard_grace: Rc<RefCell<bool>>,
pub anchor: Option<Anchor>,
pub(crate) struct InfoPopover {
pub(crate) symbol_range: RangeInEditor,
pub(crate) parsed_content: Option<View<Markdown>>,
pub(crate) scroll_handle: ScrollHandle,
pub(crate) scrollbar_state: ScrollbarState,
pub(crate) keyboard_grace: Rc<RefCell<bool>>,
pub(crate) anchor: Option<Anchor>,
}
impl InfoPopover {
pub fn render(&mut self, max_size: Size<Pixels>, cx: &mut ViewContext<Editor>) -> AnyElement {
pub(crate) fn render(
&mut self,
max_size: Size<Pixels>,
cx: &mut ViewContext<Editor>,
) -> AnyElement {
let keyboard_grace = Rc::clone(&self.keyboard_grace);
let mut d = div()
.id("info_popover")
.elevation_2(cx)
.overflow_y_scroll()
.track_scroll(&self.scroll_handle)
.max_w(max_size.width)
.max_h(max_size.height)
// Prevent a mouse down/move on the popover from being propagated to the editor,
// because that would dismiss the popover.
.on_mouse_move(|_, cx| cx.stop_propagation())
@@ -706,11 +710,21 @@ impl InfoPopover {
let mut keyboard_grace = keyboard_grace.borrow_mut();
*keyboard_grace = false;
cx.stop_propagation();
})
.p_2();
});
if let Some(markdown) = &self.parsed_content {
d = d.child(markdown.clone());
d = d
.child(
div()
.id("info-md-container")
.overflow_y_scroll()
.max_w(max_size.width)
.max_h(max_size.height)
.p_2()
.track_scroll(&self.scroll_handle)
.child(markdown.clone()),
)
.child(self.render_vertical_scrollbar(cx));
}
d.into_any_element()
}
@@ -724,6 +738,38 @@ impl InfoPopover {
cx.notify();
self.scroll_handle.set_offset(current);
}
fn render_vertical_scrollbar(&self, cx: &mut ViewContext<Editor>) -> Stateful<Div> {
div()
.occlude()
.id("info-popover-vertical-scroll")
.on_mouse_move(cx.listener(|_, _, cx| {
cx.notify();
cx.stop_propagation()
}))
.on_hover(|_, cx| {
cx.stop_propagation();
})
.on_any_mouse_down(|_, cx| {
cx.stop_propagation();
})
.on_mouse_up(
MouseButton::Left,
cx.listener(|_, _, cx| {
cx.stop_propagation();
}),
)
.on_scroll_wheel(cx.listener(|_, _, cx| {
cx.notify();
}))
.h_full()
.absolute()
.right_1()
.top_1()
.bottom_0()
.w(px(12.))
.cursor_default()
.children(Scrollbar::vertical(self.scrollbar_state.clone()))
}
}
#[derive(Debug, Clone)]
@@ -1351,6 +1397,61 @@ mod tests {
});
}
#[gpui::test]
// https://github.com/zed-industries/zed/issues/15498
async fn test_info_hover_with_hrs(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorLspTestContext::new_rust(
lsp::ServerCapabilities {
hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
..Default::default()
},
cx,
)
.await;
cx.set_state(indoc! {"
fn fuˇnc(abc def: i32) -> u32 {
}
"});
cx.lsp.handle_request::<lsp::request::HoverRequest, _, _>({
|_, _| async move {
Ok(Some(lsp::Hover {
contents: lsp::HoverContents::Markup(lsp::MarkupContent {
kind: lsp::MarkupKind::Markdown,
value: indoc!(
r#"
### function `errands_data_read`
---
→ `char *`
Function to read a file into a string
---
```cpp
static char *errands_data_read()
```
"#
)
.to_string(),
}),
range: None,
}))
}
});
cx.update_editor(|editor, cx| hover(editor, &Default::default(), cx));
cx.run_until_parked();
cx.update_editor(|editor, cx| {
let popover = editor.hover_state.info_popovers.first().unwrap();
let content = popover.get_rendered_text(cx);
assert!(content.contains("Function to read a file"));
});
}
#[gpui::test]
async fn test_hover_inlay_label_parts(cx: &mut gpui::TestAppContext) {
init_test(cx, |settings| {

View File

@@ -992,12 +992,15 @@ impl SerializableItem for Editor {
};
// First create the empty buffer
let buffer = project.update(&mut cx, |project, cx| {
project.create_local_buffer("", language, cx)
})?;
let buffer = project
.update(&mut cx, |project, cx| project.create_buffer(cx))?
.await?;
// Then set the text so that the dirty bit is set correctly
buffer.update(&mut cx, |buffer, cx| {
if let Some(language) = language {
buffer.set_language(Some(language), cx);
}
buffer.set_text(contents, cx);
})?;

View File

@@ -1,4 +1,4 @@
use std::ops::Range;
use std::{ops::Range, time::Duration};
use collections::HashMap;
use itertools::Itertools;
@@ -36,35 +36,53 @@ impl LinkedEditingRanges {
self.0.is_empty()
}
}
pub(super) fn refresh_linked_ranges(this: &mut Editor, cx: &mut ViewContext<Editor>) -> Option<()> {
if this.pending_rename.is_some() {
const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
// TODO do not refresh anything at all, if the settings/capabilities do not have it enabled.
pub(super) fn refresh_linked_ranges(
editor: &mut Editor,
cx: &mut ViewContext<Editor>,
) -> Option<()> {
if editor.pending_rename.is_some() {
return None;
}
let project = this.project.clone()?;
let selections = this.selections.all::<usize>(cx);
let buffer = this.buffer.read(cx);
let mut applicable_selections = vec![];
let snapshot = buffer.snapshot(cx);
for selection in selections {
let cursor_position = selection.head();
let start_position = snapshot.anchor_before(cursor_position);
let end_position = snapshot.anchor_after(selection.tail());
if start_position.buffer_id != end_position.buffer_id || end_position.buffer_id.is_none() {
// Throw away selections spanning multiple buffers.
continue;
let project = editor.project.as_ref()?.downgrade();
editor.linked_editing_range_task = Some(cx.spawn(|editor, mut cx| async move {
cx.background_executor().timer(UPDATE_DEBOUNCE).await;
let mut applicable_selections = Vec::new();
editor
.update(&mut cx, |editor, cx| {
let selections = editor.selections.all::<usize>(cx);
let snapshot = editor.buffer.read(cx).snapshot(cx);
let buffer = editor.buffer.read(cx);
for selection in selections {
let cursor_position = selection.head();
let start_position = snapshot.anchor_before(cursor_position);
let end_position = snapshot.anchor_after(selection.tail());
if start_position.buffer_id != end_position.buffer_id
|| end_position.buffer_id.is_none()
{
// Throw away selections spanning multiple buffers.
continue;
}
if let Some(buffer) = end_position.buffer_id.and_then(|id| buffer.buffer(id)) {
applicable_selections.push((
buffer,
start_position.text_anchor,
end_position.text_anchor,
));
}
}
})
.ok()?;
if applicable_selections.is_empty() {
return None;
}
if let Some(buffer) = end_position.buffer_id.and_then(|id| buffer.buffer(id)) {
applicable_selections.push((
buffer,
start_position.text_anchor,
end_position.text_anchor,
));
}
}
if applicable_selections.is_empty() {
return None;
}
this.linked_editing_range_task = Some(cx.spawn(|this, mut cx| async move {
let highlights = project
.update(&mut cx, |project, cx| {
let mut linked_edits_tasks = vec![];
@@ -110,37 +128,38 @@ pub(super) fn refresh_linked_ranges(this: &mut Editor, cx: &mut ViewContext<Edit
}
linked_edits_tasks
})
.log_err()?;
.ok()?;
let highlights = futures::future::join_all(highlights).await;
this.update(&mut cx, |this, cx| {
this.linked_edit_ranges.0.clear();
if this.pending_rename.is_some() {
return;
}
for (buffer_id, ranges) in highlights.into_iter().flatten() {
this.linked_edit_ranges
.0
.entry(buffer_id)
.or_default()
.extend(ranges);
}
for (buffer_id, values) in this.linked_edit_ranges.0.iter_mut() {
let Some(snapshot) = this
.buffer
.read(cx)
.buffer(*buffer_id)
.map(|buffer| buffer.read(cx).snapshot())
else {
continue;
};
values.sort_by(|lhs, rhs| lhs.0.cmp(&rhs.0, &snapshot));
}
editor
.update(&mut cx, |this, cx| {
this.linked_edit_ranges.0.clear();
if this.pending_rename.is_some() {
return;
}
for (buffer_id, ranges) in highlights.into_iter().flatten() {
this.linked_edit_ranges
.0
.entry(buffer_id)
.or_default()
.extend(ranges);
}
for (buffer_id, values) in this.linked_edit_ranges.0.iter_mut() {
let Some(snapshot) = this
.buffer
.read(cx)
.buffer(*buffer_id)
.map(|buffer| buffer.read(cx).snapshot())
else {
continue;
};
values.sort_by(|lhs, rhs| lhs.0.cmp(&rhs.0, &snapshot));
}
cx.notify();
})
.log_err();
cx.notify();
})
.ok()?;
Some(())
}));

View File

@@ -1,6 +1,6 @@
use std::{
cell::Ref,
iter, mem,
cmp, iter, mem,
ops::{Deref, DerefMut, Range, Sub},
sync::Arc,
};
@@ -98,9 +98,9 @@ impl SelectionsCollection {
&self,
cx: &mut AppContext,
) -> Option<Selection<D>> {
self.pending_anchor()
.as_ref()
.map(|pending| pending.map(|p| p.summary::<D>(&self.buffer(cx))))
let map = self.display_map(cx);
let selection = resolve_selections(self.pending_anchor().as_ref(), &map).next();
selection
}
pub(crate) fn pending_mode(&self) -> Option<SelectMode> {
@@ -111,12 +111,10 @@ impl SelectionsCollection {
where
D: 'a + TextDimension + Ord + Sub<D, Output = D>,
{
let map = self.display_map(cx);
let disjoint_anchors = &self.disjoint;
let mut disjoint =
resolve_multiple::<D, _>(disjoint_anchors.iter(), &self.buffer(cx)).peekable();
let mut disjoint = resolve_selections::<D, _>(disjoint_anchors.iter(), &map).peekable();
let mut pending_opt = self.pending::<D>(cx);
iter::from_fn(move || {
if let Some(pending) = pending_opt.as_mut() {
while let Some(next_selection) = disjoint.peek() {
@@ -199,34 +197,57 @@ impl SelectionsCollection {
where
D: 'a + TextDimension + Ord + Sub<D, Output = D> + std::fmt::Debug,
{
let buffer = self.buffer(cx);
let map = self.display_map(cx);
let start_ix = match self
.disjoint
.binary_search_by(|probe| probe.end.cmp(&range.start, &buffer))
.binary_search_by(|probe| probe.end.cmp(&range.start, &map.buffer_snapshot))
{
Ok(ix) | Err(ix) => ix,
};
let end_ix = match self
.disjoint
.binary_search_by(|probe| probe.start.cmp(&range.end, &buffer))
.binary_search_by(|probe| probe.start.cmp(&range.end, &map.buffer_snapshot))
{
Ok(ix) => ix + 1,
Err(ix) => ix,
};
resolve_multiple(&self.disjoint[start_ix..end_ix], &buffer).collect()
resolve_selections(&self.disjoint[start_ix..end_ix], &map).collect()
}
pub fn all_display(
&self,
cx: &mut AppContext,
) -> (DisplaySnapshot, Vec<Selection<DisplayPoint>>) {
let display_map = self.display_map(cx);
let selections = self
.all::<Point>(cx)
.into_iter()
.map(|selection| selection.map(|point| point.to_display_point(&display_map)))
.collect();
(display_map, selections)
let map = self.display_map(cx);
let disjoint_anchors = &self.disjoint;
let mut disjoint = resolve_selections_display(disjoint_anchors.iter(), &map).peekable();
let mut pending_opt =
resolve_selections_display(self.pending_anchor().as_ref(), &map).next();
let selections = iter::from_fn(move || {
if let Some(pending) = pending_opt.as_mut() {
while let Some(next_selection) = disjoint.peek() {
if pending.start <= next_selection.end && pending.end >= next_selection.start {
let next_selection = disjoint.next().unwrap();
if next_selection.start < pending.start {
pending.start = next_selection.start;
}
if next_selection.end > pending.end {
pending.end = next_selection.end;
}
} else if next_selection.end < pending.start {
return disjoint.next();
} else {
break;
}
}
pending_opt.take()
} else {
disjoint.next()
}
})
.collect();
(map, selections)
}
pub fn newest_anchor(&self) -> &Selection<Anchor> {
@@ -241,15 +262,18 @@ impl SelectionsCollection {
&self,
cx: &mut AppContext,
) -> Selection<D> {
let buffer = self.buffer(cx);
self.newest_anchor().map(|p| p.summary::<D>(&buffer))
let map = self.display_map(cx);
let selection = resolve_selections([self.newest_anchor()], &map)
.next()
.unwrap();
selection
}
pub fn newest_display(&self, cx: &mut AppContext) -> Selection<DisplayPoint> {
let display_map = self.display_map(cx);
let selection = self
.newest_anchor()
.map(|point| point.to_display_point(&display_map));
let map = self.display_map(cx);
let selection = resolve_selections_display([self.newest_anchor()], &map)
.next()
.unwrap();
selection
}
@@ -265,8 +289,11 @@ impl SelectionsCollection {
&self,
cx: &mut AppContext,
) -> Selection<D> {
let buffer = self.buffer(cx);
self.oldest_anchor().map(|p| p.summary::<D>(&buffer))
let map = self.display_map(cx);
let selection = resolve_selections([self.oldest_anchor()], &map)
.next()
.unwrap();
selection
}
pub fn first_anchor(&self) -> Selection<Anchor> {
@@ -538,9 +565,9 @@ impl<'a> MutableSelectionsCollection<'a> {
}
pub fn select_anchors(&mut self, selections: Vec<Selection<Anchor>>) {
let buffer = self.buffer.read(self.cx).snapshot(self.cx);
let map = self.display_map();
let resolved_selections =
resolve_multiple::<usize, _>(&selections, &buffer).collect::<Vec<_>>();
resolve_selections::<usize, _>(&selections, &map).collect::<Vec<_>>();
self.select(resolved_selections);
}
@@ -650,20 +677,16 @@ impl<'a> MutableSelectionsCollection<'a> {
) {
let mut changed = false;
let display_map = self.display_map();
let selections = self
.collection
.all::<Point>(self.cx)
let (_, selections) = self.collection.all_display(self.cx);
let selections = selections
.into_iter()
.map(|selection| {
let mut moved_selection =
selection.map(|point| point.to_display_point(&display_map));
let mut moved_selection = selection.clone();
move_selection(&display_map, &mut moved_selection);
let moved_selection =
moved_selection.map(|display_point| display_point.to_point(&display_map));
if selection != moved_selection {
changed = true;
}
moved_selection
moved_selection.map(|display_point| display_point.to_point(&display_map))
})
.collect();
@@ -804,8 +827,8 @@ impl<'a> MutableSelectionsCollection<'a> {
.collect();
if !adjusted_disjoint.is_empty() {
let resolved_selections =
resolve_multiple(adjusted_disjoint.iter(), &self.buffer()).collect();
let map = self.display_map();
let resolved_selections = resolve_selections(adjusted_disjoint.iter(), &map).collect();
self.select::<usize>(resolved_selections);
}
@@ -849,27 +872,76 @@ impl<'a> DerefMut for MutableSelectionsCollection<'a> {
}
// Panics if passed selections are not in order
pub(crate) fn resolve_multiple<'a, D, I>(
selections: I,
snapshot: &MultiBufferSnapshot,
) -> impl 'a + Iterator<Item = Selection<D>>
where
D: TextDimension + Ord + Sub<D, Output = D>,
I: 'a + IntoIterator<Item = &'a Selection<Anchor>>,
{
fn resolve_selections_display<'a>(
selections: impl 'a + IntoIterator<Item = &'a Selection<Anchor>>,
map: &'a DisplaySnapshot,
) -> impl 'a + Iterator<Item = Selection<DisplayPoint>> {
let (to_summarize, selections) = selections.into_iter().tee();
let mut summaries = snapshot
.summaries_for_anchors::<D, _>(
to_summarize
.flat_map(|s| [&s.start, &s.end])
.collect::<Vec<_>>(),
)
let mut summaries = map
.buffer_snapshot
.summaries_for_anchors::<Point, _>(to_summarize.flat_map(|s| [&s.start, &s.end]))
.into_iter();
selections.map(move |s| Selection {
id: s.id,
start: summaries.next().unwrap(),
end: summaries.next().unwrap(),
reversed: s.reversed,
goal: s.goal,
let mut selections = selections
.map(move |s| {
let start = summaries.next().unwrap();
let end = summaries.next().unwrap();
let display_start = map.point_to_display_point(start, Bias::Left);
let display_end = if start == end {
map.point_to_display_point(end, Bias::Right)
} else {
map.point_to_display_point(end, Bias::Left)
};
Selection {
id: s.id,
start: display_start,
end: display_end,
reversed: s.reversed,
goal: s.goal,
}
})
.peekable();
iter::from_fn(move || {
let mut selection = selections.next()?;
while let Some(next_selection) = selections.peek() {
if selection.end >= next_selection.start {
selection.end = cmp::max(selection.end, next_selection.end);
selections.next();
} else {
break;
}
}
Some(selection)
})
}
// Panics if passed selections are not in order
pub(crate) fn resolve_selections<'a, D, I>(
selections: I,
map: &'a DisplaySnapshot,
) -> impl 'a + Iterator<Item = Selection<D>>
where
D: TextDimension + Clone + Ord + Sub<D, Output = D>,
I: 'a + IntoIterator<Item = &'a Selection<Anchor>>,
{
let (to_convert, selections) = resolve_selections_display(selections, map).tee();
let mut converted_endpoints =
map.buffer_snapshot
.dimensions_from_points::<D>(to_convert.flat_map(|s| {
let start = map.display_point_to_point(s.start, Bias::Left);
let end = map.display_point_to_point(s.end, Bias::Right);
[start, end]
}));
selections.map(move |s| {
let start = converted_endpoints.next().unwrap();
let end = converted_endpoints.next().unwrap();
Selection {
id: s.id,
start,
end,
reversed: s.reversed,
goal: s.goal,
}
})
}

View File

@@ -9,59 +9,23 @@ license = "GPL-3.0-or-later"
workspace = true
[lib]
path = "src/extension_store.rs"
doctest = false
[features]
no-webrtc = ["workspace/no-webrtc"]
path = "src/extension.rs"
[dependencies]
anyhow.workspace = true
assistant_slash_command.workspace = true
async-compression.workspace = true
async-tar.workspace = true
async-trait.workspace = true
client.workspace = true
collections.workspace = true
fs.workspace = true
futures.workspace = true
gpui.workspace = true
http_client.workspace = true
indexed_docs.workspace = true
language.workspace = true
log.workspace = true
lsp.workspace = true
node_runtime.workspace = true
paths.workspace = true
project.workspace = true
release_channel.workspace = true
schemars.workspace = true
semantic_version.workspace = true
serde.workspace = true
serde_json.workspace = true
serde_json_lenient.workspace = true
settings.workspace = true
snippet_provider.workspace = true
task.workspace = true
theme.workspace = true
toml.workspace = true
ui.workspace = true
url.workspace = true
util.workspace = true
wasm-encoder.workspace = true
wasmparser.workspace = true
wasmtime-wasi.workspace = true
wasmtime.workspace = true
wit-component.workspace = true
workspace.workspace = true
[dev-dependencies]
ctor.workspace = true
env_logger.workspace = true
fs = { workspace = true, features = ["test-support"] }
gpui = { workspace = true, features = ["test-support"] }
language = { workspace = true, features = ["test-support"] }
parking_lot.workspace = true
project = { workspace = true, features = ["test-support"] }
reqwest_client.workspace = true
workspace = { workspace = true, features = ["test-support"] }

View File

@@ -0,0 +1,50 @@
pub mod extension_builder;
mod extension_manifest;
use anyhow::{anyhow, bail, Context as _, Result};
use semantic_version::SemanticVersion;
pub use crate::extension_manifest::*;
pub fn parse_wasm_extension_version(
extension_id: &str,
wasm_bytes: &[u8],
) -> Result<SemanticVersion> {
let mut version = None;
for part in wasmparser::Parser::new(0).parse_all(wasm_bytes) {
if let wasmparser::Payload::CustomSection(s) =
part.context("error parsing wasm extension")?
{
if s.name() == "zed:api-version" {
version = parse_wasm_extension_version_custom_section(s.data());
if version.is_none() {
bail!(
"extension {} has invalid zed:api-version section: {:?}",
extension_id,
s.data()
);
}
}
}
}
// The reason we wait until we're done parsing all of the Wasm bytes to return the version
// is to work around a panic that can happen inside of Wasmtime when the bytes are invalid.
//
// By parsing the entirety of the Wasm bytes before we return, we're able to detect this problem
// earlier as an `Err` rather than as a panic.
version.ok_or_else(|| anyhow!("extension {} has no zed:api-version section", extension_id))
}
fn parse_wasm_extension_version_custom_section(data: &[u8]) -> Option<SemanticVersion> {
if data.len() == 6 {
Some(SemanticVersion::new(
u16::from_be_bytes([data[0], data[1]]) as _,
u16::from_be_bytes([data[2], data[3]]) as _,
u16::from_be_bytes([data[4], data[5]]) as _,
))
} else {
None
}
}

View File

@@ -1,6 +1,6 @@
use crate::wasm_host::parse_wasm_extension_version;
use crate::ExtensionManifest;
use crate::{extension_manifest::ExtensionLibraryKind, GrammarManifestEntry};
use crate::{
parse_wasm_extension_version, ExtensionLibraryKind, ExtensionManifest, GrammarManifestEntry,
};
use anyhow::{anyhow, bail, Context as _, Result};
use async_compression::futures::bufread::GzipDecoder;
use async_tar::Archive;
@@ -135,6 +135,8 @@ impl ExtensionBuilder {
.args(options.release.then_some("--release"))
.arg("--target-dir")
.arg(extension_dir.join("target"))
// WASI builds do not work with sccache and just stuck, so disable it.
.env("RUSTC_WRAPPER", "")
.current_dir(extension_dir)
.output()
.context("failed to run `cargo`")?;

View File

@@ -2,6 +2,7 @@ interface lsp {
/// An LSP completion.
record completion {
label: string,
label-details: option<completion-label-details>,
detail: option<string>,
kind: option<completion-kind>,
insert-text-format: option<insert-text-format>,
@@ -37,6 +38,12 @@ interface lsp {
other(s32),
}
/// Label details for an LSP completion.
record completion-label-details {
detail: option<string>,
description: option<string>,
}
/// Defines how to interpret the insert text in a completion item.
variant insert-text-format {
plain-text,

View File

@@ -16,7 +16,7 @@ path = "src/main.rs"
anyhow.workspace = true
clap = { workspace = true, features = ["derive"] }
env_logger.workspace = true
extension = { workspace = true, features = ["no-webrtc"] }
extension.workspace = true
fs.workspace = true
language.workspace = true
log.workspace = true

View File

@@ -15,7 +15,6 @@ use extension::{
};
use language::LanguageConfig;
use reqwest_client::ReqwestClient;
use theme::ThemeRegistry;
use tree_sitter::{Language, Query, WasmStore};
#[derive(Parser, Debug)]
@@ -267,7 +266,7 @@ async fn test_themes(
) -> Result<()> {
for relative_theme_path in &manifest.themes {
let theme_path = extension_path.join(relative_theme_path);
let theme_family = ThemeRegistry::read_user_theme(&theme_path, fs.clone()).await?;
let theme_family = theme::read_user_theme(&theme_path, fs.clone()).await?;
log::info!("loaded theme family {}", theme_family.name);
}

View File

@@ -0,0 +1,62 @@
[package]
name = "extension_host"
version = "0.1.0"
edition = "2021"
publish = false
license = "GPL-3.0-or-later"
[lints]
workspace = true
[lib]
path = "src/extension_host.rs"
doctest = false
[dependencies]
anyhow.workspace = true
assistant_slash_command.workspace = true
async-compression.workspace = true
async-tar.workspace = true
async-trait.workspace = true
client.workspace = true
collections.workspace = true
extension.workspace = true
fs.workspace = true
futures.workspace = true
gpui.workspace = true
http_client.workspace = true
indexed_docs.workspace = true
language.workspace = true
log.workspace = true
lsp.workspace = true
node_runtime.workspace = true
paths.workspace = true
project.workspace = true
release_channel.workspace = true
schemars.workspace = true
semantic_version.workspace = true
serde.workspace = true
serde_json.workspace = true
serde_json_lenient.workspace = true
settings.workspace = true
snippet_provider.workspace = true
task.workspace = true
theme.workspace = true
toml.workspace = true
ui.workspace = true
url.workspace = true
util.workspace = true
wasmtime-wasi.workspace = true
wasmtime.workspace = true
workspace.workspace = true
[dev-dependencies]
ctor.workspace = true
env_logger.workspace = true
fs = { workspace = true, features = ["test-support"] }
gpui = { workspace = true, features = ["test-support"] }
language = { workspace = true, features = ["test-support"] }
parking_lot.workspace = true
project = { workspace = true, features = ["test-support"] }
reqwest_client.workspace = true
workspace = { workspace = true, features = ["test-support"] }

View File

@@ -0,0 +1 @@
../../LICENSE-GPL

View File

@@ -1,7 +1,5 @@
pub mod extension_builder;
mod extension_indexed_docs_provider;
mod extension_lsp_adapter;
mod extension_manifest;
mod extension_settings;
mod extension_slash_command;
mod wasm_host;
@@ -10,7 +8,6 @@ mod wasm_host;
mod extension_store_test;
use crate::extension_indexed_docs_provider::ExtensionIndexedDocsProvider;
use crate::extension_manifest::SchemaVersion;
use crate::extension_slash_command::ExtensionSlashCommand;
use crate::{extension_lsp_adapter::ExtensionLspAdapter, wasm_host::wit};
use anyhow::{anyhow, bail, Context as _, Result};
@@ -19,7 +16,8 @@ use async_compression::futures::bufread::GzipDecoder;
use async_tar::Archive;
use client::{telemetry::Telemetry, Client, ExtensionMetadata, GetExtensionsResponse};
use collections::{btree_map, BTreeMap, HashSet};
use extension_builder::{CompileExtensionOptions, ExtensionBuilder};
use extension::extension_builder::{CompileExtensionOptions, ExtensionBuilder};
use extension::SchemaVersion;
use fs::{Fs, RemoveOptions};
use futures::{
channel::{
@@ -62,7 +60,7 @@ use wasm_host::{
WasmExtension, WasmHost,
};
pub use extension_manifest::{
pub use extension::{
ExtensionLibraryKind, ExtensionManifest, GrammarManifestEntry, OldExtensionManifest,
};
pub use extension_settings::ExtensionSettings;
@@ -1358,7 +1356,7 @@ impl ExtensionStore {
continue;
};
let Some(theme_family) = ThemeRegistry::read_user_theme(&theme_path, fs.clone())
let Some(theme_family) = theme::read_user_theme(&theme_path, fs.clone())
.await
.log_err()
else {

View File

@@ -387,6 +387,7 @@ impl From<lsp::CompletionItem> for wit::Completion {
fn from(value: lsp::CompletionItem) -> Self {
Self {
label: value.label,
label_details: value.label_details.map(Into::into),
detail: value.detail,
kind: value.kind.map(Into::into),
insert_text_format: value.insert_text_format.map(Into::into),
@@ -394,6 +395,15 @@ impl From<lsp::CompletionItem> for wit::Completion {
}
}
impl From<lsp::CompletionItemLabelDetails> for wit::CompletionLabelDetails {
fn from(value: lsp::CompletionItemLabelDetails) -> Self {
Self {
detail: value.detail,
description: value.description,
}
}
}
impl From<lsp::CompletionItemKind> for wit::CompletionKind {
fn from(value: lsp::CompletionItemKind) -> Self {
match value {

View File

@@ -1,4 +1,3 @@
use crate::extension_manifest::SchemaVersion;
use crate::extension_settings::ExtensionSettings;
use crate::{
Event, ExtensionIndex, ExtensionIndexEntry, ExtensionIndexLanguageEntry,
@@ -8,6 +7,7 @@ use crate::{
use assistant_slash_command::SlashCommandRegistry;
use async_compression::futures::bufread::GzipEncoder;
use collections::BTreeMap;
use extension::SchemaVersion;
use fs::{FakeFs, Fs, RealFs};
use futures::{io::BufReader, AsyncReadExt, StreamExt};
use gpui::{Context, SemanticVersion, TestAppContext};
@@ -296,7 +296,7 @@ async fn test_extension_store(cx: &mut TestAppContext) {
["ERB", "Plain Text", "Ruby"]
);
assert_eq!(
theme_registry.list_names(false),
theme_registry.list_names(),
[
"Monokai Dark",
"Monokai Light",
@@ -377,7 +377,7 @@ async fn test_extension_store(cx: &mut TestAppContext) {
assert_eq!(index.themes, expected_index.themes);
assert_eq!(
theme_registry.list_names(false),
theme_registry.list_names(),
[
"Gruvbox",
"Monokai Dark",
@@ -424,7 +424,7 @@ async fn test_extension_store(cx: &mut TestAppContext) {
["embedded_template".into(), "ruby".into()]
);
assert_eq!(
theme_registry.list_names(false),
theme_registry.list_names(),
[
"Gruvbox",
"Monokai Dark",

View File

@@ -1,7 +1,7 @@
pub(crate) mod wit;
use crate::ExtensionManifest;
use anyhow::{anyhow, bail, Context as _, Result};
use anyhow::{anyhow, Context as _, Result};
use fs::{normalize_path, Fs};
use futures::future::LocalBoxFuture;
use futures::{
@@ -112,7 +112,8 @@ impl WasmHost {
) -> Task<Result<WasmExtension>> {
let this = self.clone();
executor.clone().spawn(async move {
let zed_api_version = parse_wasm_extension_version(&manifest.id, &wasm_bytes)?;
let zed_api_version =
extension::parse_wasm_extension_version(&manifest.id, &wasm_bytes)?;
let component = Component::from_binary(&this.engine, &wasm_bytes)
.context("failed to compile wasm component")?;
@@ -197,49 +198,6 @@ impl WasmHost {
}
}
pub fn parse_wasm_extension_version(
extension_id: &str,
wasm_bytes: &[u8],
) -> Result<SemanticVersion> {
let mut version = None;
for part in wasmparser::Parser::new(0).parse_all(wasm_bytes) {
if let wasmparser::Payload::CustomSection(s) =
part.context("error parsing wasm extension")?
{
if s.name() == "zed:api-version" {
version = parse_wasm_extension_version_custom_section(s.data());
if version.is_none() {
bail!(
"extension {} has invalid zed:api-version section: {:?}",
extension_id,
s.data()
);
}
}
}
}
// The reason we wait until we're done parsing all of the Wasm bytes to return the version
// is to work around a panic that can happen inside of Wasmtime when the bytes are invalid.
//
// By parsing the entirety of the Wasm bytes before we return, we're able to detect this problem
// earlier as an `Err` rather than as a panic.
version.ok_or_else(|| anyhow!("extension {} has no zed:api-version section", extension_id))
}
fn parse_wasm_extension_version_custom_section(data: &[u8]) -> Option<SemanticVersion> {
if data.len() == 6 {
Some(SemanticVersion::new(
u16::from_be_bytes([data[0], data[1]]) as _,
u16::from_be_bytes([data[2], data[3]]) as _,
u16::from_be_bytes([data[4], data[5]]) as _,
))
} else {
None
}
}
impl WasmExtension {
pub async fn call<T, Fn>(&self, f: Fn) -> T
where

View File

@@ -20,7 +20,9 @@ use wasmtime::{
#[cfg(test)]
pub use latest::CodeLabelSpanLiteral;
pub use latest::{
zed::extension::lsp::{Completion, CompletionKind, InsertTextFormat, Symbol, SymbolKind},
zed::extension::lsp::{
Completion, CompletionKind, CompletionLabelDetails, InsertTextFormat, Symbol, SymbolKind,
},
zed::extension::slash_command::{SlashCommandArgumentCompletion, SlashCommandOutput},
CodeLabel, CodeLabelSpan, Command, Range, SlashCommand,
};
@@ -262,7 +264,11 @@ impl Extension {
.await
}
Extension::V010(ext) => Ok(ext
.call_labels_for_completions(store, &language_server_id.0, &completions)
.call_labels_for_completions(
store,
&language_server_id.0,
&completions.into_iter().map(Into::into).collect::<Vec<_>>(),
)
.await?
.map(|labels| {
labels
@@ -271,7 +277,11 @@ impl Extension {
.collect()
})),
Extension::V006(ext) => Ok(ext
.call_labels_for_completions(store, &language_server_id.0, &completions)
.call_labels_for_completions(
store,
&language_server_id.0,
&completions.into_iter().map(Into::into).collect::<Vec<_>>(),
)
.await?
.map(|labels| {
labels
@@ -295,7 +305,11 @@ impl Extension {
.await
}
Extension::V010(ext) => Ok(ext
.call_labels_for_symbols(store, &language_server_id.0, &symbols)
.call_labels_for_symbols(
store,
&language_server_id.0,
&symbols.into_iter().map(Into::into).collect::<Vec<_>>(),
)
.await?
.map(|labels| {
labels
@@ -304,7 +318,11 @@ impl Extension {
.collect()
})),
Extension::V006(ext) => Ok(ext
.call_labels_for_symbols(store, &language_server_id.0, &symbols)
.call_labels_for_symbols(
store,
&language_server_id.0,
&symbols.into_iter().map(Into::into).collect::<Vec<_>>(),
)
.await?
.map(|labels| {
labels

View File

@@ -1,4 +1,4 @@
use super::latest;
use super::{latest, since_v0_1_0};
use crate::wasm_host::WasmState;
use anyhow::Result;
use async_trait::async_trait;
@@ -16,7 +16,7 @@ wasmtime::component::bindgen!({
with: {
"worktree": ExtensionWorktree,
"zed:extension/github": latest::zed::extension::github,
"zed:extension/lsp": latest::zed::extension::lsp,
"zed:extension/lsp": since_v0_1_0::zed::extension::lsp,
"zed:extension/nodejs": latest::zed::extension::nodejs,
"zed:extension/platform": latest::zed::extension::platform,
},

View File

@@ -35,7 +35,6 @@ wasmtime::component::bindgen!({
"key-value-store": ExtensionKeyValueStore,
"zed:extension/http-client/http-response-stream": ExtensionHttpResponseStream,
"zed:extension/github": latest::zed::extension::github,
"zed:extension/lsp": latest::zed::extension::lsp,
"zed:extension/nodejs": latest::zed::extension::nodejs,
"zed:extension/platform": latest::zed::extension::platform,
"zed:extension/slash-command": latest::zed::extension::slash_command,
@@ -135,6 +134,103 @@ impl From<CodeLabel> for latest::CodeLabel {
}
}
impl From<latest::Completion> for Completion {
fn from(value: latest::Completion) -> Self {
Self {
label: value.label,
detail: value.detail,
kind: value.kind.map(Into::into),
insert_text_format: value.insert_text_format.map(Into::into),
}
}
}
impl From<latest::lsp::CompletionKind> for lsp::CompletionKind {
fn from(value: latest::lsp::CompletionKind) -> Self {
match value {
latest::lsp::CompletionKind::Text => Self::Text,
latest::lsp::CompletionKind::Method => Self::Method,
latest::lsp::CompletionKind::Function => Self::Function,
latest::lsp::CompletionKind::Constructor => Self::Constructor,
latest::lsp::CompletionKind::Field => Self::Field,
latest::lsp::CompletionKind::Variable => Self::Variable,
latest::lsp::CompletionKind::Class => Self::Class,
latest::lsp::CompletionKind::Interface => Self::Interface,
latest::lsp::CompletionKind::Module => Self::Module,
latest::lsp::CompletionKind::Property => Self::Property,
latest::lsp::CompletionKind::Unit => Self::Unit,
latest::lsp::CompletionKind::Value => Self::Value,
latest::lsp::CompletionKind::Enum => Self::Enum,
latest::lsp::CompletionKind::Keyword => Self::Keyword,
latest::lsp::CompletionKind::Snippet => Self::Snippet,
latest::lsp::CompletionKind::Color => Self::Color,
latest::lsp::CompletionKind::File => Self::File,
latest::lsp::CompletionKind::Reference => Self::Reference,
latest::lsp::CompletionKind::Folder => Self::Folder,
latest::lsp::CompletionKind::EnumMember => Self::EnumMember,
latest::lsp::CompletionKind::Constant => Self::Constant,
latest::lsp::CompletionKind::Struct => Self::Struct,
latest::lsp::CompletionKind::Event => Self::Event,
latest::lsp::CompletionKind::Operator => Self::Operator,
latest::lsp::CompletionKind::TypeParameter => Self::TypeParameter,
latest::lsp::CompletionKind::Other(kind) => Self::Other(kind),
}
}
}
impl From<latest::lsp::InsertTextFormat> for lsp::InsertTextFormat {
fn from(value: latest::lsp::InsertTextFormat) -> Self {
match value {
latest::lsp::InsertTextFormat::PlainText => Self::PlainText,
latest::lsp::InsertTextFormat::Snippet => Self::Snippet,
latest::lsp::InsertTextFormat::Other(value) => Self::Other(value),
}
}
}
impl From<latest::lsp::Symbol> for lsp::Symbol {
fn from(value: latest::lsp::Symbol) -> Self {
Self {
name: value.name,
kind: value.kind.into(),
}
}
}
impl From<latest::lsp::SymbolKind> for lsp::SymbolKind {
fn from(value: latest::lsp::SymbolKind) -> Self {
match value {
latest::lsp::SymbolKind::File => Self::File,
latest::lsp::SymbolKind::Module => Self::Module,
latest::lsp::SymbolKind::Namespace => Self::Namespace,
latest::lsp::SymbolKind::Package => Self::Package,
latest::lsp::SymbolKind::Class => Self::Class,
latest::lsp::SymbolKind::Method => Self::Method,
latest::lsp::SymbolKind::Property => Self::Property,
latest::lsp::SymbolKind::Field => Self::Field,
latest::lsp::SymbolKind::Constructor => Self::Constructor,
latest::lsp::SymbolKind::Enum => Self::Enum,
latest::lsp::SymbolKind::Interface => Self::Interface,
latest::lsp::SymbolKind::Function => Self::Function,
latest::lsp::SymbolKind::Variable => Self::Variable,
latest::lsp::SymbolKind::Constant => Self::Constant,
latest::lsp::SymbolKind::String => Self::String,
latest::lsp::SymbolKind::Number => Self::Number,
latest::lsp::SymbolKind::Boolean => Self::Boolean,
latest::lsp::SymbolKind::Array => Self::Array,
latest::lsp::SymbolKind::Object => Self::Object,
latest::lsp::SymbolKind::Key => Self::Key,
latest::lsp::SymbolKind::Null => Self::Null,
latest::lsp::SymbolKind::EnumMember => Self::EnumMember,
latest::lsp::SymbolKind::Struct => Self::Struct,
latest::lsp::SymbolKind::Event => Self::Event,
latest::lsp::SymbolKind::Operator => Self::Operator,
latest::lsp::SymbolKind::TypeParameter => Self::TypeParameter,
latest::lsp::SymbolKind::Other(kind) => Self::Other(kind),
}
}
}
#[async_trait]
impl HostKeyValueStore for WasmState {
async fn insert(
@@ -336,6 +432,9 @@ async fn convert_response(
Ok(extension_response)
}
#[async_trait]
impl lsp::Host for WasmState {}
#[async_trait]
impl ExtensionImports for WasmState {
async fn get_settings(

View File

@@ -20,7 +20,7 @@ client.workspace = true
collections.workspace = true
db.workspace = true
editor.workspace = true
extension.workspace = true
extension_host.workspace = true
fs.workspace = true
fuzzy.workspace = true
gpui.workspace = true

View File

@@ -4,7 +4,7 @@ use std::sync::{Arc, OnceLock};
use db::kvp::KEY_VALUE_STORE;
use editor::Editor;
use extension::ExtensionStore;
use extension_host::ExtensionStore;
use gpui::{Model, VisualContext};
use language::Buffer;
use ui::{SharedString, ViewContext};
@@ -17,6 +17,7 @@ const SUGGESTIONS_BY_EXTENSION_ID: &[(&str, &[&str])] = &[
("astro", &["astro"]),
("beancount", &["beancount"]),
("clojure", &["bb", "clj", "cljc", "cljs", "edn"]),
("neocmake", &["CMakeLists.txt", "cmake"]),
("csharp", &["cs"]),
("dart", &["dart"]),
("dockerfile", &["Dockerfile"]),

View File

@@ -2,7 +2,7 @@ use std::str::FromStr;
use std::sync::Arc;
use client::ExtensionMetadata;
use extension::{ExtensionSettings, ExtensionStore};
use extension_host::{ExtensionSettings, ExtensionStore};
use fs::Fs;
use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
use gpui::{
@@ -167,7 +167,7 @@ impl PickerDelegate for ExtensionVersionSelectorDelegate {
let candidate_id = self.matches[self.selected_index].candidate_id;
let extension_version = &self.extension_versions[candidate_id];
if !extension::is_version_compatible(ReleaseChannel::global(cx), extension_version) {
if !extension_host::is_version_compatible(ReleaseChannel::global(cx), extension_version) {
return;
}
@@ -203,7 +203,7 @@ impl PickerDelegate for ExtensionVersionSelectorDelegate {
let extension_version = &self.extension_versions[version_match.candidate_id];
let is_version_compatible =
extension::is_version_compatible(ReleaseChannel::global(cx), extension_version);
extension_host::is_version_compatible(ReleaseChannel::global(cx), extension_version);
let disabled = !is_version_compatible;
Some(

View File

@@ -11,7 +11,7 @@ use client::telemetry::Telemetry;
use client::ExtensionMetadata;
use collections::{BTreeMap, BTreeSet};
use editor::{Editor, EditorElement, EditorStyle};
use extension::{ExtensionManifest, ExtensionOperation, ExtensionStore};
use extension_host::{ExtensionManifest, ExtensionOperation, ExtensionStore};
use fuzzy::{match_strings, StringMatchCandidate};
use gpui::{
actions, uniform_list, AppContext, EventEmitter, Flatten, FocusableView, InteractiveElement,
@@ -203,8 +203,8 @@ impl ExtensionsPage {
let subscriptions = [
cx.observe(&store, |_, _, cx| cx.notify()),
cx.subscribe(&store, move |this, _, event, cx| match event {
extension::Event::ExtensionsUpdated => this.fetch_extensions_debounced(cx),
extension::Event::ExtensionInstalled(extension_id) => {
extension_host::Event::ExtensionsUpdated => this.fetch_extensions_debounced(cx),
extension_host::Event::ExtensionInstalled(extension_id) => {
this.on_extension_installed(workspace_handle.clone(), extension_id, cx)
}
_ => {}
@@ -691,7 +691,8 @@ impl ExtensionsPage {
has_dev_extension: bool,
cx: &mut ViewContext<Self>,
) -> (Button, Option<Button>) {
let is_compatible = extension::is_version_compatible(ReleaseChannel::global(cx), extension);
let is_compatible =
extension_host::is_version_compatible(ReleaseChannel::global(cx), extension);
if has_dev_extension {
// If we have a dev extension for the given extension, just treat it as uninstalled.

View File

@@ -29,7 +29,7 @@ const fn request_feature_url() -> &'static str {
fn file_bug_report_url(specs: &SystemSpecs) -> String {
format!(
"https://github.com/zed-industries/zed/issues/new?assignees=&labels=admin+read%2Ctriage%2Cdefect&projects=&template=1_bug_report.yml&environment={}",
"https://github.com/zed-industries/zed/issues/new?assignees=&labels=admin+read%2Ctriage%2Cbug&projects=&template=1_bug_report.yml&environment={}",
urlencoding::encode(&specs.to_string())
)
}

View File

@@ -1,3 +1,9 @@
#[cfg(target_os = "macos")]
mod mac_watcher;
#[cfg(target_os = "linux")]
pub mod linux_watcher;
use anyhow::{anyhow, Result};
use git::GitHostingProviderRegistry;
@@ -530,14 +536,21 @@ impl Fs for RealFs {
Pin<Box<dyn Send + Stream<Item = Vec<PathEvent>>>>,
Arc<dyn Watcher>,
) {
use fsevent::{EventStream, StreamFlags};
use fsevent::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(
let (events_tx, events_rx) = smol::channel::unbounded();
let handles = Arc::new(parking_lot::Mutex::new(collections::BTreeMap::default()));
let watcher = Arc::new(mac_watcher::MacWatcher::new(
events_tx,
Arc::downgrade(&handles),
latency,
));
watcher.add(path).expect("handles can't be dropped");
(
Box::pin(
events_rx
.map(|events| {
events
.into_iter()
.map(|event| {
@@ -555,19 +568,14 @@ impl Fs for RealFs {
kind,
}
})
.collect(),
),
)
.is_ok()
});
});
(
Box::pin(rx.chain(futures::stream::once(async move {
drop(handle);
vec![]
}))),
Arc::new(RealWatcher {}),
.collect()
})
.chain(futures::stream::once(async move {
drop(handles);
vec![]
})),
),
watcher,
)
}
@@ -580,81 +588,26 @@ impl Fs for RealFs {
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<PathEvent>>> = Default::default();
let root_path = path.to_path_buf();
let watcher = Arc::new(linux_watcher::LinuxWatcher::new(tx, pending_paths.clone()));
// Check if root path is a symlink
let target_path = self.read_link(&path).await.ok();
watcher::global({
let target_path = target_path.clone();
|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_map(|path| {
if let Some(target) = target_path.clone() {
if path.starts_with(target) {
return Some(PathEvent {
path: path.clone(),
kind,
});
}
} else if path.starts_with(&root_path) {
return Some(PathEvent {
path: path.clone(),
kind,
});
}
None
})
.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, |a, b| {
a.path.cmp(&b.path)
});
}
})
}
})
.log_err();
let watcher = Arc::new(RealWatcher {});
watcher.add(path).ok(); // Ignore "file doesn't exist error" and rely on parent watcher.
watcher.add(&path).ok(); // Ignore "file doesn't exist error" and rely on parent watcher.
if let Some(parent) = path.parent() {
// watch the parent dir so we can tell when settings.json is created
watcher.add(parent).log_err();
}
// Check if path is a symlink and follow the target parent
if let Some(target) = target_path {
if let Some(target) = self.read_link(&path).await.ok() {
watcher.add(&target).ok();
if let Some(parent) = target.parent() {
watcher.add(parent).log_err();
}
}
// watch the parent dir so we can tell when settings.json is created
if let Some(parent) = path.parent() {
watcher.add(parent).log_err();
}
(
Box::pin(rx.filter_map({
let watcher = watcher.clone();
@@ -784,23 +737,6 @@ impl Watcher for RealWatcher {
}
}
#[cfg(target_os = "linux")]
impl Watcher for RealWatcher {
fn add(&self, path: &Path) -> Result<()> {
use notify::Watcher;
Ok(watcher::global(|w| {
w.inotify
.lock()
.watch(path, notify::RecursiveMode::NonRecursive)
})??)
}
fn remove(&self, path: &Path) -> Result<()> {
use notify::Watcher;
Ok(watcher::global(|w| w.inotify.lock().unwatch(path))??)
}
}
#[cfg(any(test, feature = "test-support"))]
pub struct FakeFs {
// Use an unfair lock to ensure tests are deterministic.
@@ -2084,49 +2020,3 @@ mod tests {
);
}
}
#[cfg(target_os = "linux")]
pub mod watcher {
use std::sync::OnceLock;
use parking_lot::Mutex;
use util::ResultExt;
pub struct GlobalWatcher {
// two mutexes because calling inotify.add triggers an inotify.event, which needs watchers.
pub(super) inotify: Mutex<notify::INotifyWatcher>,
pub(super) watchers: Mutex<Vec<Box<dyn Fn(&notify::Event) + Send + Sync>>>,
}
impl GlobalWatcher {
pub(super) fn add(&self, cb: impl Fn(&notify::Event) + Send + Sync + 'static) {
self.watchers.lock().push(Box::new(cb))
}
}
static INOTIFY_INSTANCE: OnceLock<anyhow::Result<GlobalWatcher, notify::Error>> =
OnceLock::new();
fn handle_event(event: Result<notify::Event, notify::Error>) {
let Some(event) = event.log_err() else { return };
global::<()>(move |watcher| {
for f in watcher.watchers.lock().iter() {
f(&event)
}
})
.log_err();
}
pub fn global<T>(f: impl FnOnce(&GlobalWatcher) -> T) -> anyhow::Result<T> {
let result = INOTIFY_INSTANCE.get_or_init(|| {
notify::recommended_watcher(handle_event).map(|file_watcher| GlobalWatcher {
inotify: Mutex::new(file_watcher),
watchers: Default::default(),
})
});
match result {
Ok(g) => Ok(f(g)),
Err(e) => Err(anyhow::anyhow!("{}", e)),
}
}
}

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