Compare commits

..

32 Commits

Author SHA1 Message Date
Joseph T. Lyons
d2997f1051 zed 0.208.3 2025-10-10 13:57:12 -04:00
localcc
2fa54ddbbe Initial layout rounding implementation (#39712)
Release Notes:

- N/A

---------

Co-authored-by: John Tur <john-tur@outlook.com>
2025-10-10 13:52:28 -04:00
Sunli
4769880fb7 gpui: Fix drawing rotated SVGs (#33288)
Fixes: https://github.com/longbridge/gpui-component/issues/994

1. When SVG is rotated, incorrect graphics are drawn.

For example: the original aspect ratio of the SVG is 1:1, if the bounds
used to render the SVG are 400x200 (aspect ratio 2:1),
[here](21f985a018/crates/gpui/src/svg_renderer.rs (L91))
the width is used as the scaling factor, causing the rendered SVG to
only have half the height. This PR ensures the complete SVG image is
always rendered.

2. The clipping region has no transformation applied, I added a function
called `distance_from_clip_rect_transformed` in the shader.

3. Fixed `monochrome_sprite_fragment` in `shader.metal` not applying
clipping region.

### Before:


https://github.com/user-attachments/assets/8f93ac36-281e-4837-96cd-c308bfbf92d1

### After:


https://github.com/user-attachments/assets/f52b67a6-4cb9-4d6c-b759-bbb91b59c1cf

Release Notes:

- N/A

---------

Co-authored-by: Jason Lee <huacnlee@gmail.com>
2025-10-10 13:52:20 -04:00
localcc
bd294bcb69 Fix settings window on Linux/Windows being immovable (#39939) 2025-10-10 13:44:36 -04:00
Alvaro Parker
a70aa4b40a gpui: Add support for floating windows (#39702)
Closes #ISSUE

This allows new windows like the Rules library or the Settings UI window
to appear floating on window managers like hyprland:


https://github.com/user-attachments/assets/628db7f9-4459-4601-85f1-789923831182

Left is with `WindowKind::Floating` and right is with
`WindowKind::Normal`

Release Notes:

- Added support for floating windows on x11 and wayland
2025-10-10 13:44:23 -04:00
Cave Bats Of Ware
f62171c39c Improve GPU selection on Windows (#39264)
Closes #39263

Release Notes:
- N/A 

from
https://github.com/zed-industries/zed/issues/39263#issuecomment-3358220988

> 
> > If you replace that code with
> > 
> > let adapter: IDXGIAdapter1 = unsafe { 
> >    dxgi_factory.EnumAdapters(adapter_index) 
> > }?.cast()?; 
> > 
> > does it not select the right GPU?
>  
> @reflectronic That does seem to select the active gpu for me, meaning
whichever GPU is currently connected. This is a much simpler solution
than the one I have here
(https://github.com/zed-industries/zed/pull/39264 - updated) and while
I'm sure I could imagine someone wanting to choose their GPU to render
Zed on, that may not be something that the application really needs to
support.
> 
> I have a branch with just this as the only change that I can push to
that PR if the simpler solution is preferred.
> 
> ```rust
>         let adapter: IDXGIAdapter1 = unsafe {
>             dxgi_factory.EnumAdapters(adapter_index)?.cast()?
>         };
> ```
2025-10-10 13:34:37 -04:00
localcc
7555a0a06e Change windows asset name to match other platforms (#39936) 2025-10-10 13:34:26 -04:00
Cole Miller
ed4cfb0696 windows: Don't throw an error when the settings file is empty (#39908)
Closes #39585 

Release Notes:

- N/A
2025-10-10 13:33:53 -04:00
Ben Kunkle
2aeb0257e4 settings_ui: Improve search by fuzzy matching on words (#39961)
Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-10 12:40:53 -04:00
Ben Kunkle
f5613e6351 settings_ui: Refactor item renderers to render entire field (#39959)
Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-10 12:36:18 -04:00
Anthony Eid
3a076c4822 settings_ui: Fix page scroll bar lagging behind when jumping to a section (#39897)
The issue was caused by the scroll handle taking a couple of frames to
update its offset correctly after calling
`ScrollHandle::scroll_to_top_of_item`. The fast fix is forcing 3 frames
to render back-to-back.

In the future, we should look into `ScrollHandle` and see if there's any
way to update its state outside of paint.

Release Notes:

- N/A

Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>
Co-authored-by: Katie Geer <katie@zed.dev>
Co-authored-by: Ben Kunkle <ben@zed.dev>
2025-10-09 16:43:25 -04:00
Mikayla Maki
d6a06b6ba3 settings_ui: Restore settings UI keybinding hint (#39896)
Now that the toggle nav focus works well, we can advertise it!

Release Notes:

- N/A
2025-10-09 16:43:09 -04:00
Ben Kunkle
1e579689b3 settings_ui: Fix tab and ID bugs (#39888)
Closes #39883

Release Notes:

- N/A *or* Added/Fixed/Improved ...

---------

Co-authored-by: Anthony <anthony@zed.dev>
2025-10-09 14:27:35 -04:00
Ben Brandt
605aa63e03 Remove codex feature flag (#39878)
Release Notes:

- N/A
2025-10-09 18:30:32 +02:00
Joseph T. Lyons
ecc40a2a55 zed 0.208.2 2025-10-09 11:46:07 -04:00
Ben Brandt
3746518630 Provide codex as an option on remote sessions (#39774)
Release Notes:

- N/A

---------

Co-authored-by: Cole Miller <cole@zed.dev>
2025-10-09 17:31:08 +02:00
Ben Brandt
45f6f8053b acp: Simplify auth check and allow for custom /logout commands (#39867)
- Prefer agent-specific logout handling to allow state reset 
- Treat any auth method as supported; remove provider-specific filter 
- Avoid prompting auth when issuing /logout and agent supports it

Release Notes:

- N/A
2025-10-09 17:30:54 +02:00
Ben Kunkle
90c8da55f3 settings_ui: Add terminal settings (#39874)
Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-09 11:22:55 -04:00
Conrad Irwin
7c3829895a Fix configuring shell in project settings (#39795)
I mistakenly broke this when refactoring settings

Closes #39479

Release Notes:

- Fixed a bug where you could no longer configure `terminal.shell` in
project settings
2025-10-09 11:22:12 -04:00
Matthijs Kok
b78c6f6676 settings_ui: Correct "File Icons" description (#39805)
Align with
cd656485c8/crates/settings/src/settings_content/workspace.rs (L490)

By the way, LOVE the settings UI! <3 Great job so far :)


Release Notes:

- N/A
2025-10-09 08:43:38 -04:00
Ben Kunkle
4122b23cda settings_ui: Improve keyboard nav (#39819)
Closes #ISSUE

From notes:

```markdown
  - [x] Clicking on the disclsoure icon button in the root-level tree view item should steal focus and move it to the root item (not the icon button)
  - [x] [@ben] Allow left/right arrow keys to expand/collapse root tree view items in the nav
    - [x] With this, make enter/space work the same as clicking (activate page, don't expand root items, focus moves to the content and leaves nav — becomes consistent with mouse interaction)
  - [x] Smart cmd-shift-e: toggling focus should take you to the selected item
  - [x] [@ben] pageup + pagedown in nav -> jump between root items
  - [x] [@ben] home + end buttons should work
    - in nav:
      - home always goes to first section header
      - end always goes to last _visible_ item (does not expand)
```

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-08 23:22:33 -04:00
Andrew Farkas
cc3b0d4198 Onboarding refactor (#39724)
<img width="1648" height="976" alt="Screenshot 2025-10-07 at 6 57 20 PM"
src="https://github.com/user-attachments/assets/ae7289c0-8820-4fdf-ae28-84fb6bd64942"
/>

Fixes #39347

Release Notes:

- Improved onboarding UI by collapsing it to a single page

---------

Co-authored-by: dino <dinojoaocosta@gmail.com>
Co-authored-by: Lukas Wirth <me@lukaswirth.dev>
Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>
Co-authored-by: Anthony Eid <hello@anthonyeid.me>
Co-authored-by: Mikayla Maki <mikayla@zed.dev>
2025-10-08 18:57:56 -04:00
Kirill Bulatov
b2e9c6c731 Do not deselect in terminal on copy by default (#39814)
Release Notes:

- Flips `terminal.keep_selection_on_copy` default to `true`
2025-10-08 18:03:11 -04:00
Jakub Konka
3ae971f941 terminal: Clear shell after activating (#39798)
Two tweaks were required to ensure we correctly clear the shell after
running an activate script(s):
1. PowerShell upon receiving `\r\n` input, will enter the continuation
mode (>>). To avoid this, we send an "enter" key press instead `\x0d`.
2. In order to clear the terminal _after_ issuing all activation
commands, we need to take into account the asynchronous nature of the
activation process:
   - We write the command to run the script to PTY
- We send "enter" (It is now being processed by the shell) At this point
we need to wait for the shell to finish executing before we clear the
terminal. Otherwise we will create a race where we might clear the
terminal _before_ the shell finished executing the activation script(s).
   - Write `clear`/`cls` command to PTY
- Send "enter" This way we guarantee that we clear the terminal _after_
all scripts were executed.

Closes #38474 

Release Notes:

- N/A
2025-10-08 17:41:05 -04:00
Jakub Konka
04ff8deb62 terminal: Fix escaping arguments when using CMD as the shell (#39701)
A couple of caveats:
- We should not auto-escape arguments with Alacritty's `escape_args`
option if using CMD otherwise, the generated command will have way too
many escaped characters for CMD to parse correctly.
- When composing a full command for CMD, we need to put it in double
quotes manually: `cmd /C "activate.bat& pwsh.exe -C do_something"` so
that CMD executes the entire string as a sequence of commands.
- CMD requires `&` as a chaining operator for commands (`;` for other
shells).

Release Notes:

- N/A
2025-10-08 17:39:24 -04:00
Joseph T. Lyons
37e7f30539 zed 0.208.1 2025-10-08 16:35:52 -04:00
Piotr Osiewicz
28c9cd2271 windows: Do not exit from app in dev builds when cli is not found (#39768)
Release Notes:

- N/A
2025-10-08 16:26:27 -04:00
Danilo Leal
c0651a8b86 settings ui: Add a handful of design tweaks (#39784)
Release Notes:

- N/A
2025-10-08 16:20:06 -04:00
Danilo Leal
62ac632ccf settings ui: Fix some layout regressions (#39804)
Release Notes:

- N/A
2025-10-08 16:19:25 -04:00
Finn Evers
622bdae9a7 settings_ui: Add fallback for agent_ui_font_size (#39782)
Closes https://github.com/zed-industries/zed/issues/39775

Release Notes:

- N/A
2025-10-08 23:12:39 +03:00
Ben Kunkle
5975c1cae5 settings_ui: Focus content controls when opened from nav bar (#39792)
Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-10-08 12:48:17 -04:00
Joseph T. Lyons
2cee97e359 v0.208.x preview 2025-10-08 10:20:58 -04:00
478 changed files with 9978 additions and 21001 deletions

View File

@@ -1,35 +0,0 @@
name: Bug Report (Windows)
description: Zed Windows Related Bugs
type: "Bug"
labels: ["windows"]
title: "Windows: <a short description of the Windows bug>"
body:
- type: textarea
attributes:
label: Summary
description: Describe the bug with a one-line summary, and provide detailed reproduction steps
value: |
<!-- Please insert a one-line summary of the issue below -->
SUMMARY_SENTENCE_HERE
### Description
<!-- Describe with sufficient detail to reproduce from a clean Zed install. -->
Steps to trigger the problem:
1.
2.
3.
**Expected Behavior**:
**Actual Behavior**:
validations:
required: true
- type: textarea
id: environment
attributes:
label: Zed Version and System Specs
description: 'Open Zed, and in the command palette select "zed: copy system specs into clipboard"'
placeholder: |
Output of "zed: copy system specs into clipboard"
validations:
required: true

2217
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -164,7 +164,6 @@ members = [
"crates/sum_tree",
"crates/supermaven",
"crates/supermaven_api",
"crates/codestral",
"crates/svg_preview",
"crates/system_specs",
"crates/tab_switcher",
@@ -222,7 +221,7 @@ members = [
"tooling/perf",
"tooling/workspace-hack",
"tooling/xtask", "crates/fs_benchmarks", "crates/worktree_benchmarks",
"tooling/xtask",
]
default-members = ["crates/zed"]
@@ -274,7 +273,7 @@ cloud_llm_client = { path = "crates/cloud_llm_client" }
cloud_zeta2_prompt = { path = "crates/cloud_zeta2_prompt" }
collab = { path = "crates/collab" }
collab_ui = { path = "crates/collab_ui" }
collections = { path = "crates/collections", version = "0.1.0" }
collections = { path = "crates/collections", package = "zed-collections", version = "0.1.0" }
command_palette = { path = "crates/command_palette" }
command_palette_hooks = { path = "crates/command_palette_hooks" }
component = { path = "crates/component" }
@@ -290,7 +289,7 @@ debug_adapter_extension = { path = "crates/debug_adapter_extension" }
debugger_tools = { path = "crates/debugger_tools" }
debugger_ui = { path = "crates/debugger_ui" }
deepseek = { path = "crates/deepseek" }
derive_refineable = { path = "crates/refineable/derive_refineable" }
derive_refineable = { path = "crates/refineable/derive_refineable", package = "zed-derive-refineable", version = "0.1.0" }
diagnostics = { path = "crates/diagnostics" }
editor = { path = "crates/editor" }
extension = { path = "crates/extension" }
@@ -309,10 +308,10 @@ git_ui = { path = "crates/git_ui" }
go_to_line = { path = "crates/go_to_line" }
google_ai = { path = "crates/google_ai" }
gpui = { path = "crates/gpui", default-features = false }
gpui_macros = { path = "crates/gpui_macros" }
gpui_macros = { path = "crates/gpui_macros", package = "gpui-macros", version = "0.1.0" }
gpui_tokio = { path = "crates/gpui_tokio" }
html_to_markdown = { path = "crates/html_to_markdown" }
http_client = { path = "crates/http_client" }
http_client = { path = "crates/http_client", package = "zed-http-client", version = "0.1.0" }
http_client_tls = { path = "crates/http_client_tls" }
icons = { path = "crates/icons" }
image_viewer = { path = "crates/image_viewer" }
@@ -341,7 +340,7 @@ lsp = { path = "crates/lsp" }
markdown = { path = "crates/markdown" }
markdown_preview = { path = "crates/markdown_preview" }
svg_preview = { path = "crates/svg_preview" }
media = { path = "crates/media" }
media = { path = "crates/media", package = "zed-media", version = "0.1.0" }
menu = { path = "crates/menu" }
migrator = { path = "crates/migrator" }
mistral = { path = "crates/mistral" }
@@ -358,7 +357,7 @@ outline = { path = "crates/outline" }
outline_panel = { path = "crates/outline_panel" }
panel = { path = "crates/panel" }
paths = { path = "crates/paths" }
perf = { path = "tooling/perf" }
perf = { path = "tooling/perf", package = "zed-perf", version = "0.1.0" }
picker = { path = "crates/picker" }
plugin = { path = "crates/plugin" }
plugin_macros = { path = "crates/plugin_macros" }
@@ -370,7 +369,7 @@ project_symbols = { path = "crates/project_symbols" }
prompt_store = { path = "crates/prompt_store" }
proto = { path = "crates/proto" }
recent_projects = { path = "crates/recent_projects" }
refineable = { path = "crates/refineable" }
refineable = { path = "crates/refineable", package = "zed-refineable", version = "0.1.0" }
release_channel = { path = "crates/release_channel" }
scheduler = { path = "crates/scheduler" }
remote = { path = "crates/remote" }
@@ -383,7 +382,7 @@ rope = { path = "crates/rope" }
rpc = { path = "crates/rpc" }
rules_library = { path = "crates/rules_library" }
search = { path = "crates/search" }
semantic_version = { path = "crates/semantic_version" }
semantic_version = { path = "crates/semantic_version", package = "zed-semantic-version", version = "0.1.0" }
session = { path = "crates/session" }
settings = { path = "crates/settings" }
settings_macros = { path = "crates/settings_macros" }
@@ -396,10 +395,9 @@ sqlez_macros = { path = "crates/sqlez_macros" }
story = { path = "crates/story" }
storybook = { path = "crates/storybook" }
streaming_diff = { path = "crates/streaming_diff" }
sum_tree = { path = "crates/sum_tree" }
sum_tree = { path = "crates/sum_tree", package = "zed-sum-tree", version = "0.1.0" }
supermaven = { path = "crates/supermaven" }
supermaven_api = { path = "crates/supermaven_api" }
codestral = { path = "crates/codestral" }
system_specs = { path = "crates/system_specs" }
tab_switcher = { path = "crates/tab_switcher" }
task = { path = "crates/task" }
@@ -420,8 +418,8 @@ ui = { path = "crates/ui" }
ui_input = { path = "crates/ui_input" }
ui_macros = { path = "crates/ui_macros" }
ui_prompt = { path = "crates/ui_prompt" }
util = { path = "crates/util" }
util_macros = { path = "crates/util_macros" }
util = { path = "crates/util", package = "zed-util", version = "0.1.0" }
util_macros = { path = "crates/util_macros", package = "zed-util-macros", version = "0.1.0" }
vercel = { path = "crates/vercel" }
vim = { path = "crates/vim" }
vim_mode_setting = { path = "crates/vim_mode_setting" }
@@ -455,7 +453,6 @@ async-compat = "0.2.1"
async-compression = { version = "0.4", features = ["gzip", "futures-io"] }
async-dispatcher = "0.1"
async-fs = "2.1"
async-lock = "2.1"
async-pipe = { git = "https://github.com/zed-industries/async-pipe-rs", rev = "82d00a04211cf4e1236029aa03e6b6ce2a74c553" }
async-recursion = "1.0.0"
async-tar = "0.5.0"
@@ -655,7 +652,7 @@ strum = { version = "0.27.0", features = ["derive"] }
subtle = "2.5.0"
syn = { version = "2.0.101", features = ["full", "extra-traits", "visit-mut"] }
sys-locale = "0.3.1"
sysinfo = "0.37.0"
sysinfo = "0.31.0"
take-until = "0.2.0"
tempfile = "3.20.0"
thiserror = "2.0.12"
@@ -694,7 +691,7 @@ tree-sitter-python = "0.25"
tree-sitter-regex = "0.24"
tree-sitter-ruby = "0.23"
tree-sitter-rust = "0.24"
tree-sitter-typescript = { git = "https://github.com/zed-industries/tree-sitter-typescript", rev = "e2c53597d6a5d9cf7bbe8dccde576fe1e46c5899" } # https://github.com/tree-sitter/tree-sitter-typescript/pull/347
tree-sitter-typescript = "0.23"
tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "baff0b51c64ef6a1fb1f8390f3ad6015b83ec13a" }
unicase = "2.6"
unicode-script = "0.5.7"
@@ -806,7 +803,7 @@ wasmtime = { opt-level = 3 }
activity_indicator = { codegen-units = 1 }
assets = { codegen-units = 1 }
breadcrumbs = { codegen-units = 1 }
collections = { codegen-units = 1 }
zed-collections = { codegen-units = 1 }
command_palette = { codegen-units = 1 }
command_palette_hooks = { codegen-units = 1 }
extension_cli = { codegen-units = 1 }
@@ -826,11 +823,11 @@ outline = { codegen-units = 1 }
paths = { codegen-units = 1 }
prettier = { codegen-units = 1 }
project_symbols = { codegen-units = 1 }
refineable = { codegen-units = 1 }
zed-refineable = { codegen-units = 1 }
release_channel = { codegen-units = 1 }
reqwest_client = { codegen-units = 1 }
rich_text = { codegen-units = 1 }
semantic_version = { codegen-units = 1 }
zed-semantic-version = { codegen-units = 1 }
session = { codegen-units = 1 }
snippet = { codegen-units = 1 }
snippets_ui = { codegen-units = 1 }

2
Cross.toml Normal file
View File

@@ -0,0 +1,2 @@
[build]
dockerfile = "Dockerfile-cross"

17
Dockerfile-cross Normal file
View File

@@ -0,0 +1,17 @@
# syntax=docker/dockerfile:1
ARG CROSS_BASE_IMAGE
FROM ${CROSS_BASE_IMAGE}
WORKDIR /app
ARG TZ=Etc/UTC \
LANG=C.UTF-8 \
LC_ALL=C.UTF-8 \
DEBIAN_FRONTEND=noninteractive
ENV CARGO_TERM_COLOR=always
COPY script/install-mold script/
RUN ./script/install-mold "2.34.0"
COPY script/remote-server script/
RUN ./script/remote-server
COPY . .

View File

@@ -1,3 +1,9 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.2806 4.66818L8.26042 1.76982C8.09921 1.67673 7.9003 1.67673 7.73909 1.76982L2.71918 4.66818C2.58367 4.74642 2.5 4.89112 2.5 5.04785V10.8924C2.5 11.0489 2.58367 11.1938 2.71918 11.2721L7.73934 14.1704C7.90054 14.2635 8.09946 14.2635 8.26066 14.1704L13.2808 11.2721C13.4163 11.1938 13.5 11.0491 13.5 10.8924V5.04785C13.5 4.89136 13.4163 4.74642 13.2808 4.66818H13.2806ZM12.9653 5.28212L8.11901 13.676C8.08626 13.7326 7.99977 13.7095 7.99977 13.6439V8.14771C7.99977 8.03788 7.94107 7.9363 7.84586 7.88115L3.08613 5.13317C3.02957 5.10041 3.05266 5.0139 3.11818 5.0139H12.8106C12.9483 5.0139 13.0343 5.1631 12.9655 5.28236H12.9653V5.28212Z" fill="#C4CAD4"/>
<path opacity="0.6" d="M3.5 11V5.5L8.5 8L3.5 11Z" fill="black"/>
<path opacity="0.4" d="M8.5 14L3.5 11L8.5 8V14Z" fill="black"/>
<path opacity="0.6" d="M8.5 5.5H3.5L8.5 2.5L8.5 5.5Z" fill="black"/>
<path opacity="0.8" d="M8.5 5.5V2.5L13.5 5.5H8.5Z" fill="black"/>
<path opacity="0.2" d="M13.5 11L8.5 14L11 9.5L13.5 11Z" fill="black"/>
<path opacity="0.5" d="M13.5 11L11 9.5L13.5 5V11Z" fill="black"/>
<path d="M3.5 11V5L8.5 2.11325L13.5 5V11L8.5 13.8868L3.5 11Z" stroke="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 769 B

After

Width:  |  Height:  |  Size: 583 B

View File

@@ -1,4 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.125 9.25001L3 6.125L6.125 3" stroke="#C4CAD4" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3 6.125H9.56251C10.0139 6.125 10.4609 6.21391 10.878 6.38666C11.295 6.55942 11.674 6.81262 11.9932 7.13182C12.3124 7.45102 12.5656 7.82997 12.7383 8.24703C12.9111 8.66408 13 9.11108 13 9.5625C13 10.0139 12.9111 10.4609 12.7383 10.878C12.5656 11.295 12.3124 11.674 11.9932 11.9932C11.674 12.3124 11.295 12.5656 10.878 12.7383C10.4609 12.9111 10.0139 13 9.56251 13H7.375" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M2.6 5v3.6h3.6"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2" d="M13.4 11A5.4 5.4 0 0 0 8 5.6a5.4 5.4 0 0 0-3.6 1.38L2.6 8.6"/></svg>

Before

Width:  |  Height:  |  Size: 692 B

After

Width:  |  Height:  |  Size: 339 B

View File

@@ -30,8 +30,8 @@
"ctrl-+": ["zed::IncreaseBufferFontSize", { "persist": false }],
"ctrl--": ["zed::DecreaseBufferFontSize", { "persist": false }],
"ctrl-0": ["zed::ResetBufferFontSize", { "persist": false }],
"ctrl-,": "zed::OpenSettings",
"ctrl-alt-,": "zed::OpenSettingsFile",
"ctrl-,": "zed::OpenSettingsEditor",
"ctrl-alt-,": "zed::OpenSettings",
"ctrl-q": "zed::Quit",
"f4": "debugger::Start",
"shift-f5": "debugger::Stop",
@@ -491,8 +491,8 @@
"bindings": {
"ctrl-[": "editor::Outdent",
"ctrl-]": "editor::Indent",
"shift-alt-up": ["editor::AddSelectionAbove", { "skip_soft_wrap": true }], // Insert Cursor Above
"shift-alt-down": ["editor::AddSelectionBelow", { "skip_soft_wrap": true }], // Insert Cursor Below
"shift-alt-up": "editor::AddSelectionAbove", // Insert Cursor Above
"shift-alt-down": "editor::AddSelectionBelow", // Insert Cursor Below
"ctrl-shift-k": "editor::DeleteLine",
"alt-up": "editor::MoveLineUp",
"alt-down": "editor::MoveLineDown",
@@ -527,15 +527,15 @@
"ctrl-k ctrl-l": "editor::ToggleFold",
"ctrl-k ctrl-[": "editor::FoldRecursive",
"ctrl-k ctrl-]": "editor::UnfoldRecursive",
"ctrl-k ctrl-1": "editor::FoldAtLevel_1",
"ctrl-k ctrl-2": "editor::FoldAtLevel_2",
"ctrl-k ctrl-3": "editor::FoldAtLevel_3",
"ctrl-k ctrl-4": "editor::FoldAtLevel_4",
"ctrl-k ctrl-5": "editor::FoldAtLevel_5",
"ctrl-k ctrl-6": "editor::FoldAtLevel_6",
"ctrl-k ctrl-7": "editor::FoldAtLevel_7",
"ctrl-k ctrl-8": "editor::FoldAtLevel_8",
"ctrl-k ctrl-9": "editor::FoldAtLevel_9",
"ctrl-k ctrl-1": ["editor::FoldAtLevel", 1],
"ctrl-k ctrl-2": ["editor::FoldAtLevel", 2],
"ctrl-k ctrl-3": ["editor::FoldAtLevel", 3],
"ctrl-k ctrl-4": ["editor::FoldAtLevel", 4],
"ctrl-k ctrl-5": ["editor::FoldAtLevel", 5],
"ctrl-k ctrl-6": ["editor::FoldAtLevel", 6],
"ctrl-k ctrl-7": ["editor::FoldAtLevel", 7],
"ctrl-k ctrl-8": ["editor::FoldAtLevel", 8],
"ctrl-k ctrl-9": ["editor::FoldAtLevel", 9],
"ctrl-k ctrl-0": "editor::FoldAll",
"ctrl-k ctrl-j": "editor::UnfoldAll",
"ctrl-space": "editor::ShowCompletions",
@@ -621,7 +621,7 @@
"ctrl-shift-f": "pane::DeploySearch",
"ctrl-shift-h": ["pane::DeploySearch", { "replace_enabled": true }],
"ctrl-shift-t": "pane::ReopenClosedItem",
"ctrl-k ctrl-s": "zed::OpenKeymap",
"ctrl-k ctrl-s": "zed::OpenKeymapEditor",
"ctrl-k ctrl-t": "theme_selector::Toggle",
"ctrl-alt-super-p": "settings_profile_selector::Toggle",
"ctrl-t": "project_symbols::Toggle",
@@ -1249,7 +1249,6 @@
"escape": "workspace::CloseWindow",
"ctrl-m": "settings_editor::Minimize",
"ctrl-f": "search::FocusSearch",
"left": "settings_editor::ToggleFocusNav",
"ctrl-shift-e": "settings_editor::ToggleFocusNav",
// todo(settings_ui): cut this down based on the max files and overflow UI
"ctrl-1": ["settings_editor::FocusFile", 0],
@@ -1270,8 +1269,6 @@
"context": "SettingsWindow > NavigationMenu",
"use_key_equivalents": true,
"bindings": {
"up": "settings_editor::FocusPreviousNavEntry",
"down": "settings_editor::FocusNextNavEntry",
"right": "settings_editor::ExpandNavEntry",
"left": "settings_editor::CollapseNavEntry",
"pageup": "settings_editor::FocusPreviousRootNavEntry",

View File

@@ -39,8 +39,8 @@
"cmd-+": ["zed::IncreaseBufferFontSize", { "persist": false }],
"cmd--": ["zed::DecreaseBufferFontSize", { "persist": false }],
"cmd-0": ["zed::ResetBufferFontSize", { "persist": false }],
"cmd-,": "zed::OpenSettings",
"cmd-alt-,": "zed::OpenSettingsFile",
"cmd-,": "zed::OpenSettingsEditor",
"cmd-alt-,": "zed::OpenSettings",
"cmd-q": "zed::Quit",
"cmd-h": "zed::Hide",
"alt-cmd-h": "zed::HideOthers",
@@ -539,10 +539,10 @@
"bindings": {
"cmd-[": "editor::Outdent",
"cmd-]": "editor::Indent",
"cmd-ctrl-p": ["editor::AddSelectionAbove", { "skip_soft_wrap": false }], // Insert cursor above
"cmd-alt-up": ["editor::AddSelectionAbove", { "skip_soft_wrap": true }],
"cmd-ctrl-n": ["editor::AddSelectionBelow", { "skip_soft_wrap": false }], // Insert cursor below
"cmd-alt-down": ["editor::AddSelectionBelow", { "skip_soft_wrap": true }],
"cmd-ctrl-p": "editor::AddSelectionAbove", // Insert cursor above
"cmd-alt-up": "editor::AddSelectionAbove",
"cmd-ctrl-n": "editor::AddSelectionBelow", // Insert cursor below
"cmd-alt-down": "editor::AddSelectionBelow",
"cmd-shift-k": "editor::DeleteLine",
"alt-up": "editor::MoveLineUp",
"alt-down": "editor::MoveLineDown",
@@ -582,15 +582,15 @@
"cmd-k cmd-l": "editor::ToggleFold",
"cmd-k cmd-[": "editor::FoldRecursive",
"cmd-k cmd-]": "editor::UnfoldRecursive",
"cmd-k cmd-1": "editor::FoldAtLevel_1",
"cmd-k cmd-2": "editor::FoldAtLevel_2",
"cmd-k cmd-3": "editor::FoldAtLevel_3",
"cmd-k cmd-4": "editor::FoldAtLevel_4",
"cmd-k cmd-5": "editor::FoldAtLevel_5",
"cmd-k cmd-6": "editor::FoldAtLevel_6",
"cmd-k cmd-7": "editor::FoldAtLevel_7",
"cmd-k cmd-8": "editor::FoldAtLevel_8",
"cmd-k cmd-9": "editor::FoldAtLevel_9",
"cmd-k cmd-1": ["editor::FoldAtLevel", 1],
"cmd-k cmd-2": ["editor::FoldAtLevel", 2],
"cmd-k cmd-3": ["editor::FoldAtLevel", 3],
"cmd-k cmd-4": ["editor::FoldAtLevel", 4],
"cmd-k cmd-5": ["editor::FoldAtLevel", 5],
"cmd-k cmd-6": ["editor::FoldAtLevel", 6],
"cmd-k cmd-7": ["editor::FoldAtLevel", 7],
"cmd-k cmd-8": ["editor::FoldAtLevel", 8],
"cmd-k cmd-9": ["editor::FoldAtLevel", 9],
"cmd-k cmd-0": "editor::FoldAll",
"cmd-k cmd-j": "editor::UnfoldAll",
// Using `ctrl-space` / `ctrl-shift-space` in Zed requires disabling the macOS global shortcut.
@@ -690,7 +690,7 @@
"cmd-shift-f": "pane::DeploySearch",
"cmd-shift-h": ["pane::DeploySearch", { "replace_enabled": true }],
"cmd-shift-t": "pane::ReopenClosedItem",
"cmd-k cmd-s": "zed::OpenKeymap",
"cmd-k cmd-s": "zed::OpenKeymapEditor",
"cmd-k cmd-t": "theme_selector::Toggle",
"ctrl-alt-cmd-p": "settings_profile_selector::Toggle",
"cmd-t": "project_symbols::Toggle",
@@ -1354,7 +1354,6 @@
"escape": "workspace::CloseWindow",
"cmd-m": "settings_editor::Minimize",
"cmd-f": "search::FocusSearch",
"left": "settings_editor::ToggleFocusNav",
"cmd-shift-e": "settings_editor::ToggleFocusNav",
// todo(settings_ui): cut this down based on the max files and overflow UI
"ctrl-1": ["settings_editor::FocusFile", 0],
@@ -1375,8 +1374,6 @@
"context": "SettingsWindow > NavigationMenu",
"use_key_equivalents": true,
"bindings": {
"up": "settings_editor::FocusPreviousNavEntry",
"down": "settings_editor::FocusNextNavEntry",
"right": "settings_editor::ExpandNavEntry",
"left": "settings_editor::CollapseNavEntry",
"pageup": "settings_editor::FocusPreviousRootNavEntry",

View File

@@ -29,8 +29,8 @@
"ctrl-shift-=": ["zed::IncreaseBufferFontSize", { "persist": false }],
"ctrl--": ["zed::DecreaseBufferFontSize", { "persist": false }],
"ctrl-0": ["zed::ResetBufferFontSize", { "persist": false }],
"ctrl-,": "zed::OpenSettings",
"ctrl-alt-,": "zed::OpenSettingsFile",
"ctrl-,": "zed::OpenSettingsEditor",
"ctrl-alt-,": "zed::OpenSettings",
"ctrl-q": "zed::Quit",
"f4": "debugger::Start",
"shift-f5": "debugger::Stop",
@@ -134,7 +134,7 @@
"ctrl-k z": "editor::ToggleSoftWrap",
"ctrl-f": "buffer_search::Deploy",
"ctrl-h": "buffer_search::DeployReplace",
"ctrl-shift-.": "agent::QuoteSelection",
"ctrl-shift-.": "assistant::QuoteSelection",
"ctrl-shift-,": "assistant::InsertIntoEditor",
"shift-alt-e": "editor::SelectEnclosingSymbol",
"ctrl-shift-backspace": "editor::GoToPreviousChange",
@@ -244,7 +244,7 @@
"ctrl-shift-i": "agent::ToggleOptionsMenu",
// "ctrl-shift-alt-n": "agent::ToggleNewThreadMenu",
"shift-alt-escape": "agent::ExpandMessageEditor",
"ctrl-shift-.": "agent::QuoteSelection",
"ctrl-shift-.": "assistant::QuoteSelection",
"shift-alt-e": "agent::RemoveAllContext",
"ctrl-shift-e": "project_panel::ToggleFocus",
"ctrl-shift-enter": "agent::ContinueThread",
@@ -500,8 +500,8 @@
"bindings": {
"ctrl-[": "editor::Outdent",
"ctrl-]": "editor::Indent",
"ctrl-shift-alt-up": ["editor::AddSelectionAbove", { "skip_soft_wrap": true }], // Insert Cursor Above
"ctrl-shift-alt-down": ["editor::AddSelectionBelow", { "skip_soft_wrap": true }], // Insert Cursor Below
"ctrl-shift-alt-up": "editor::AddSelectionAbove", // Insert Cursor Above
"ctrl-shift-alt-down": "editor::AddSelectionBelow", // Insert Cursor Below
"ctrl-shift-k": "editor::DeleteLine",
"alt-up": "editor::MoveLineUp",
"alt-down": "editor::MoveLineDown",
@@ -536,15 +536,15 @@
"ctrl-k ctrl-l": "editor::ToggleFold",
"ctrl-k ctrl-[": "editor::FoldRecursive",
"ctrl-k ctrl-]": "editor::UnfoldRecursive",
"ctrl-k ctrl-1": "editor::FoldAtLevel_1",
"ctrl-k ctrl-2": "editor::FoldAtLevel_2",
"ctrl-k ctrl-3": "editor::FoldAtLevel_3",
"ctrl-k ctrl-4": "editor::FoldAtLevel_4",
"ctrl-k ctrl-5": "editor::FoldAtLevel_5",
"ctrl-k ctrl-6": "editor::FoldAtLevel_6",
"ctrl-k ctrl-7": "editor::FoldAtLevel_7",
"ctrl-k ctrl-8": "editor::FoldAtLevel_8",
"ctrl-k ctrl-9": "editor::FoldAtLevel_9",
"ctrl-k ctrl-1": ["editor::FoldAtLevel", 1],
"ctrl-k ctrl-2": ["editor::FoldAtLevel", 2],
"ctrl-k ctrl-3": ["editor::FoldAtLevel", 3],
"ctrl-k ctrl-4": ["editor::FoldAtLevel", 4],
"ctrl-k ctrl-5": ["editor::FoldAtLevel", 5],
"ctrl-k ctrl-6": ["editor::FoldAtLevel", 6],
"ctrl-k ctrl-7": ["editor::FoldAtLevel", 7],
"ctrl-k ctrl-8": ["editor::FoldAtLevel", 8],
"ctrl-k ctrl-9": ["editor::FoldAtLevel", 9],
"ctrl-k ctrl-0": "editor::FoldAll",
"ctrl-k ctrl-j": "editor::UnfoldAll",
"ctrl-space": "editor::ShowCompletions",
@@ -623,7 +623,7 @@
"ctrl-shift-f": "pane::DeploySearch",
"ctrl-shift-h": ["pane::DeploySearch", { "replace_enabled": true }],
"ctrl-shift-t": "pane::ReopenClosedItem",
"ctrl-k ctrl-s": "zed::OpenKeymap",
"ctrl-k ctrl-s": "zed::OpenKeymapEditor",
"ctrl-k ctrl-t": "theme_selector::Toggle",
"ctrl-alt-super-p": "settings_profile_selector::Toggle",
"ctrl-t": "project_symbols::Toggle",
@@ -1270,7 +1270,6 @@
"escape": "workspace::CloseWindow",
"ctrl-m": "settings_editor::Minimize",
"ctrl-f": "search::FocusSearch",
"left": "settings_editor::ToggleFocusNav",
"ctrl-shift-e": "settings_editor::ToggleFocusNav",
// todo(settings_ui): cut this down based on the max files and overflow UI
"ctrl-1": ["settings_editor::FocusFile", 0],
@@ -1291,8 +1290,6 @@
"context": "SettingsWindow > NavigationMenu",
"use_key_equivalents": true,
"bindings": {
"up": "settings_editor::FocusPreviousNavEntry",
"down": "settings_editor::FocusNextNavEntry",
"right": "settings_editor::ExpandNavEntry",
"left": "settings_editor::CollapseNavEntry",
"pageup": "settings_editor::FocusPreviousRootNavEntry",

View File

@@ -24,8 +24,8 @@
"ctrl-<": "editor::ScrollCursorCenter", // editor:scroll-to-cursor
"f3": ["editor::SelectNext", { "replace_newest": true }], // find-and-replace:find-next
"shift-f3": ["editor::SelectPrevious", { "replace_newest": true }], //find-and-replace:find-previous
"alt-shift-down": ["editor::AddSelectionBelow", { "skip_soft_wrap": true }], // editor:add-selection-below
"alt-shift-up": ["editor::AddSelectionAbove", { "skip_soft_wrap": true }], // editor:add-selection-above
"alt-shift-down": "editor::AddSelectionBelow", // editor:add-selection-below
"alt-shift-up": "editor::AddSelectionAbove", // editor:add-selection-above
"ctrl-j": "editor::JoinLines", // editor:join-lines
"ctrl-shift-d": "editor::DuplicateLineDown", // editor:duplicate-lines
"ctrl-up": "editor::MoveLineUp", // editor:move-line-up

View File

@@ -1,7 +1,7 @@
[
{
"bindings": {
"ctrl-alt-s": "zed::OpenSettingsFile",
"ctrl-alt-s": "zed::OpenSettings",
"ctrl-{": "pane::ActivatePreviousItem",
"ctrl-}": "pane::ActivateNextItem",
"shift-escape": null, // Unmap workspace::zoom

View File

@@ -28,8 +28,8 @@
{
"context": "Editor",
"bindings": {
"ctrl-alt-up": ["editor::AddSelectionAbove", { "skip_soft_wrap": false }],
"ctrl-alt-down": ["editor::AddSelectionBelow", { "skip_soft_wrap": false }],
"ctrl-alt-up": "editor::AddSelectionAbove",
"ctrl-alt-down": "editor::AddSelectionBelow",
"ctrl-shift-up": "editor::MoveLineUp",
"ctrl-shift-down": "editor::MoveLineDown",
"ctrl-shift-m": "editor::SelectLargerSyntaxNode",

View File

@@ -25,8 +25,8 @@
"cmd-<": "editor::ScrollCursorCenter",
"cmd-g": ["editor::SelectNext", { "replace_newest": true }],
"cmd-shift-g": ["editor::SelectPrevious", { "replace_newest": true }],
"ctrl-shift-down": ["editor::AddSelectionBelow", { "skip_soft_wrap": true }],
"ctrl-shift-up": ["editor::AddSelectionAbove", { "skip_soft_wrap": true }],
"ctrl-shift-down": "editor::AddSelectionBelow",
"ctrl-shift-up": "editor::AddSelectionAbove",
"alt-enter": "editor::Newline",
"cmd-shift-d": "editor::DuplicateLineDown",
"ctrl-cmd-up": "editor::MoveLineUp",

View File

@@ -28,8 +28,8 @@
{
"context": "Editor",
"bindings": {
"ctrl-shift-up": ["editor::AddSelectionAbove", { "skip_soft_wrap": false }],
"ctrl-shift-down": ["editor::AddSelectionBelow", { "skip_soft_wrap": false }],
"ctrl-shift-up": "editor::AddSelectionAbove",
"ctrl-shift-down": "editor::AddSelectionBelow",
"cmd-ctrl-up": "editor::MoveLineUp",
"cmd-ctrl-down": "editor::MoveLineDown",
"cmd-shift-space": "editor::SelectAll",

View File

@@ -95,6 +95,8 @@
"g g": "vim::StartOfDocument",
"g h": "editor::Hover",
"g B": "editor::BlameHover",
"g t": "vim::GoToTab",
"g shift-t": "vim::GoToPreviousTab",
"g d": "editor::GoToDefinition",
"g shift-d": "editor::GoToDeclaration",
"g y": "editor::GoToTypeDefinition",
@@ -498,8 +500,8 @@
"ctrl-c": "editor::ToggleComments",
"d": "vim::HelixDelete",
"c": "vim::Substitute",
"shift-c": ["editor::AddSelectionBelow", { "skip_soft_wrap": true }],
"alt-shift-c": ["editor::AddSelectionAbove", { "skip_soft_wrap": true }]
"shift-c": "editor::AddSelectionBelow",
"alt-shift-c": "editor::AddSelectionAbove"
}
},
{
@@ -578,18 +580,18 @@
// "q": "vim::AnyQuotes",
"q": "vim::MiniQuotes",
"|": "vim::VerticalBars",
"(": ["vim::Parentheses", { "opening": true }],
"(": "vim::Parentheses",
")": "vim::Parentheses",
"b": "vim::Parentheses",
// "b": "vim::AnyBrackets",
// "b": "vim::MiniBrackets",
"[": ["vim::SquareBrackets", { "opening": true }],
"[": "vim::SquareBrackets",
"]": "vim::SquareBrackets",
"r": "vim::SquareBrackets",
"{": ["vim::CurlyBrackets", { "opening": true }],
"{": "vim::CurlyBrackets",
"}": "vim::CurlyBrackets",
"shift-b": "vim::CurlyBrackets",
"<": ["vim::AngleBrackets", { "opening": true }],
"<": "vim::AngleBrackets",
">": "vim::AngleBrackets",
"a": "vim::Argument",
"i": "vim::IndentObj",
@@ -809,7 +811,7 @@
}
},
{
"context": "VimControl && !menu || !Editor && !Terminal",
"context": "VimControl || !Editor && !Terminal",
"bindings": {
// window related commands (ctrl-w X)
"ctrl-w": null,
@@ -863,9 +865,7 @@
"ctrl-w ctrl-o": "workspace::CloseInactiveTabsAndPanes",
"ctrl-w o": "workspace::CloseInactiveTabsAndPanes",
"ctrl-w ctrl-n": "workspace::NewFileSplitHorizontal",
"ctrl-w n": "workspace::NewFileSplitHorizontal",
"g t": "vim::GoToTab",
"g shift-t": "vim::GoToPreviousTab"
"ctrl-w n": "workspace::NewFileSplitHorizontal"
}
},
{

View File

@@ -1,5 +1,4 @@
{
"$schema": "zed://schemas/settings",
/// The displayed name of this project. If not set or empty, the root directory name
/// will be displayed.
"project_name": "",
@@ -722,11 +721,7 @@
// Whether to enable drag-and-drop operations in the project panel.
"drag_and_drop": true,
// Whether to hide the root entry when only one folder is open in the window.
"hide_root": false,
// Whether to hide the hidden entries in the project panel.
"hide_hidden": false,
// Whether to automatically open files when pasting them in the project panel.
"open_file_on_paste": true
"hide_root": false
},
"outline_panel": {
// Whether to show the outline panel button in the status bar
@@ -908,7 +903,6 @@
"now": true,
"find_path": true,
"read_file": true,
"open": true,
"grep": true,
"terminal": true,
"thinking": true,
@@ -920,6 +914,7 @@
// We don't know which of the context server tools are safe for the "Ask" profile, so we don't enable them by default.
// "enable_all_context_servers": true,
"tools": {
"contents": true,
"diagnostics": true,
"fetch": true,
"list_directory": true,
@@ -1106,31 +1101,25 @@
// Removes any lines containing only whitespace at the end of the file and
// ensures just one newline at the end.
"ensure_final_newline_on_save": true,
// Whether or not to perform a buffer format before saving: [on, off]
// Whether or not to perform a buffer format before saving: [on, off, prettier, language_server]
// Keep in mind, if the autosave with delay is enabled, format_on_save will be ignored
"format_on_save": "on",
// How to perform a buffer format. This setting can take multiple values:
// How to perform a buffer format. This setting can take 4 values:
//
// 1. Default. Format files using Zed's Prettier integration (if applicable),
// or falling back to formatting via language server:
// "formatter": "auto"
// 2. Format code using the current language server:
// 1. Format code using the current language server:
// "formatter": "language_server"
// 3. Format code using a specific language server:
// "formatter": {"language_server": {"name": "ruff"}}
// 4. Format code using an external command:
// 2. Format code using an external command:
// "formatter": {
// "external": {
// "command": "prettier",
// "arguments": ["--stdin-filepath", "{buffer_path}"]
// }
// }
// 5. Format code using Zed's Prettier integration:
// 3. Format code using Zed's Prettier integration:
// "formatter": "prettier"
// 6. Format code using a code action
// "formatter": {"code_action": "source.fixAll.eslint"}
// 7. An array of any format step specified above to apply in order
// "formatter": [{"code_action": "source.fixAll.eslint"}, "prettier"]
// 4. Default. Format files using Zed's Prettier integration (if applicable),
// or falling back to formatting via language server:
// "formatter": "auto"
"formatter": "auto",
// How to soft-wrap long lines of text.
// Possible values:
@@ -1322,18 +1311,15 @@
// "proxy": "",
// "proxy_no_verify": false
// },
// Whether edit predictions are enabled when editing text threads.
// This setting has no effect if globally disabled.
"enabled_in_text_threads": true,
"copilot": {
"enterprise_uri": null,
"proxy": null,
"proxy_no_verify": null
},
"codestral": {
"model": null,
"max_tokens": null
},
// Whether edit predictions are enabled when editing text threads.
// This setting has no effect if globally disabled.
"enabled_in_text_threads": true
}
},
// Settings specific to journaling
"journal": {
@@ -1529,6 +1515,7 @@
// A value of 45 preserves colorful themes while ensuring legibility.
"minimum_contrast": 45
},
"code_actions_on_format": {},
// Settings related to running tasks.
"tasks": {
"variables": {},
@@ -1698,7 +1685,9 @@
"preferred_line_length": 72
},
"Go": {
"formatter": [{ "code_action": "source.organizeImports" }, "language_server"],
"code_actions_on_format": {
"source.organizeImports": true
},
"debuggers": ["Delve"]
},
"GraphQL": {
@@ -2062,7 +2051,7 @@
// }
// }
// }
"profiles": {},
"profiles": [],
// A map of log scopes to the desired log level.
// Useful for filtering out noisy logs or enabling more verbose logging.

View File

@@ -9,8 +9,6 @@ disallowed-methods = [
{ path = "std::process::Command::spawn", reason = "Spawning `std::process::Command` can block the current thread for an unknown duration", replacement = "smol::process::Command::spawn" },
{ path = "std::process::Command::output", reason = "Spawning `std::process::Command` can block the current thread for an unknown duration", replacement = "smol::process::Command::output" },
{ path = "std::process::Command::status", reason = "Spawning `std::process::Command` can block the current thread for an unknown duration", replacement = "smol::process::Command::status" },
{ path = "serde_json::from_reader", reason = "Parsing from a buffer is much slower than first reading the buffer into a Vec/String, see https://github.com/serde-rs/json/issues/160#issuecomment-253446892. Use `serde_json::from_slice` instead." },
{ path = "serde_json_lenient::from_reader", reason = "Parsing from a buffer is much slower than first reading the buffer into a Vec/String, see https://github.com/serde-rs/json/issues/160#issuecomment-253446892, Use `serde_json_lenient::from_slice` instead." },
]
disallowed-types = [
# { path = "std::collections::HashMap", replacement = "collections::HashMap" },

View File

@@ -2112,7 +2112,6 @@ impl AcpThread {
let project = self.project.clone();
let language_registry = project.read(cx).languages().clone();
let is_windows = project.read(cx).path_style(cx).is_windows();
let terminal_id = acp::TerminalId(Uuid::new_v4().to_string().into());
let terminal_task = cx.spawn({
@@ -2126,10 +2125,9 @@ impl AcpThread {
.and_then(|r| r.read(cx).default_system_shell())
})?
.unwrap_or_else(|| get_default_system_shell_preferring_bash());
let (task_command, task_args) =
ShellBuilder::new(&Shell::Program(shell), is_windows)
.redirect_stdin_to_dev_null()
.build(Some(command.clone()), &args);
let (task_command, task_args) = ShellBuilder::new(&Shell::Program(shell))
.redirect_stdin_to_dev_null()
.build(Some(command.clone()), &args);
let terminal = project
.update(cx, |project, cx| {
project.create_terminal_task(

View File

@@ -4,26 +4,22 @@ use std::{
fmt::Display,
rc::{Rc, Weak},
sync::Arc,
time::Duration,
};
use agent_client_protocol as acp;
use collections::HashMap;
use gpui::{
App, ClipboardItem, Empty, Entity, EventEmitter, FocusHandle, Focusable, Global, ListAlignment,
ListState, StyleRefinement, Subscription, Task, TextStyleRefinement, Window, actions, list,
prelude::*,
App, Empty, Entity, EventEmitter, FocusHandle, Focusable, Global, ListAlignment, ListState,
StyleRefinement, Subscription, Task, TextStyleRefinement, Window, actions, list, prelude::*,
};
use language::LanguageRegistry;
use markdown::{CodeBlockRenderer, Markdown, MarkdownElement, MarkdownStyle};
use project::Project;
use settings::Settings;
use theme::ThemeSettings;
use ui::{Tooltip, prelude::*};
use ui::prelude::*;
use util::ResultExt as _;
use workspace::{
Item, ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
};
use workspace::{Item, Workspace};
actions!(dev, [OpenAcpLogs]);
@@ -231,34 +227,6 @@ impl AcpTools {
cx.notify();
}
fn serialize_observed_messages(&self) -> Option<String> {
let connection = self.watched_connection.as_ref()?;
let messages: Vec<serde_json::Value> = connection
.messages
.iter()
.filter_map(|message| {
let params = match &message.params {
Ok(Some(params)) => params.clone(),
Ok(None) => serde_json::Value::Null,
Err(err) => serde_json::to_value(err).ok()?,
};
Some(serde_json::json!({
"_direction": match message.direction {
acp::StreamMessageDirection::Incoming => "incoming",
acp::StreamMessageDirection::Outgoing => "outgoing",
},
"_type": message.message_type.to_string().to_lowercase(),
"id": message.request_id,
"method": message.name.to_string(),
"params": params,
}))
})
.collect();
serde_json::to_string_pretty(&messages).ok()
}
fn render_message(
&mut self,
index: usize,
@@ -524,92 +492,3 @@ impl Render for AcpTools {
})
}
}
pub struct AcpToolsToolbarItemView {
acp_tools: Option<Entity<AcpTools>>,
just_copied: bool,
}
impl AcpToolsToolbarItemView {
pub fn new() -> Self {
Self {
acp_tools: None,
just_copied: false,
}
}
}
impl Render for AcpToolsToolbarItemView {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let Some(acp_tools) = self.acp_tools.as_ref() else {
return Empty.into_any_element();
};
let acp_tools = acp_tools.clone();
h_flex()
.gap_2()
.child(
IconButton::new(
"copy_all_messages",
if self.just_copied {
IconName::Check
} else {
IconName::Copy
},
)
.icon_size(IconSize::Small)
.tooltip(Tooltip::text(if self.just_copied {
"Copied!"
} else {
"Copy All Messages"
}))
.disabled(
acp_tools
.read(cx)
.watched_connection
.as_ref()
.is_none_or(|connection| connection.messages.is_empty()),
)
.on_click(cx.listener(move |this, _, _window, cx| {
if let Some(content) = acp_tools.read(cx).serialize_observed_messages() {
cx.write_to_clipboard(ClipboardItem::new_string(content));
this.just_copied = true;
cx.spawn(async move |this, cx| {
cx.background_executor().timer(Duration::from_secs(2)).await;
this.update(cx, |this, cx| {
this.just_copied = false;
cx.notify();
})
})
.detach();
}
})),
)
.into_any()
}
}
impl EventEmitter<ToolbarItemEvent> for AcpToolsToolbarItemView {}
impl ToolbarItemView for AcpToolsToolbarItemView {
fn set_active_pane_item(
&mut self,
active_pane_item: Option<&dyn ItemHandle>,
_window: &mut Window,
cx: &mut Context<Self>,
) -> ToolbarItemLocation {
if let Some(item) = active_pane_item
&& let Some(acp_tools) = item.downcast::<AcpTools>()
{
self.acp_tools = Some(acp_tools);
cx.notify();
return ToolbarItemLocation::PrimaryRight;
}
if self.acp_tools.take().is_some() {
cx.notify();
}
ToolbarItemLocation::Hidden
}
}

View File

@@ -20,6 +20,7 @@ use std::{
cmp::Reverse,
collections::HashSet,
fmt::Write,
path::Path,
sync::Arc,
time::{Duration, Instant},
};
@@ -327,13 +328,17 @@ impl ActivityIndicator {
.flatten()
}
fn pending_environment_error<'a>(&'a self, cx: &'a App) -> Option<&'a EnvironmentErrorMessage> {
self.project.read(cx).peek_environment_error(cx)
fn pending_environment_errors<'a>(
&'a self,
cx: &'a App,
) -> impl Iterator<Item = (&'a Arc<Path>, &'a EnvironmentErrorMessage)> {
self.project.read(cx).shell_environment_errors(cx)
}
fn content_to_render(&mut self, cx: &mut Context<Self>) -> Option<Content> {
// Show if any direnv calls failed
if let Some(error) = self.pending_environment_error(cx) {
if let Some((abs_path, error)) = self.pending_environment_errors(cx).next() {
let abs_path = abs_path.clone();
return Some(Content {
icon: Some(
Icon::new(IconName::Warning)
@@ -343,7 +348,7 @@ impl ActivityIndicator {
message: error.0.clone(),
on_click: Some(Arc::new(move |this, window, cx| {
this.project.update(cx, |project, cx| {
project.pop_environment_error(cx);
project.remove_environment_error(&abs_path, cx);
});
window.dispatch_action(Box::new(workspace::OpenLog), cx);
})),

View File

@@ -39,6 +39,7 @@ heed.workspace = true
http_client.workspace = true
icons.workspace = true
indoc.workspace = true
itertools.workspace = true
language.workspace = true
language_model.workspace = true
log.workspace = true

View File

@@ -2,6 +2,7 @@ pub mod agent_profile;
pub mod context;
pub mod context_server_tool;
pub mod context_store;
pub mod history_store;
pub mod thread;
pub mod thread_store;
pub mod tool_use;

View File

@@ -0,0 +1,253 @@
use crate::{ThreadId, thread_store::SerializedThreadMetadata};
use anyhow::{Context as _, Result};
use assistant_context::SavedContextMetadata;
use chrono::{DateTime, Utc};
use gpui::{App, AsyncApp, Entity, SharedString, Task, prelude::*};
use itertools::Itertools;
use paths::contexts_dir;
use serde::{Deserialize, Serialize};
use std::{collections::VecDeque, path::Path, sync::Arc, time::Duration};
use util::ResultExt as _;
const MAX_RECENTLY_OPENED_ENTRIES: usize = 6;
const NAVIGATION_HISTORY_PATH: &str = "agent-navigation-history.json";
const SAVE_RECENTLY_OPENED_ENTRIES_DEBOUNCE: Duration = Duration::from_millis(50);
#[derive(Clone, Debug)]
pub enum HistoryEntry {
Thread(SerializedThreadMetadata),
Context(SavedContextMetadata),
}
impl HistoryEntry {
pub fn updated_at(&self) -> DateTime<Utc> {
match self {
HistoryEntry::Thread(thread) => thread.updated_at,
HistoryEntry::Context(context) => context.mtime.to_utc(),
}
}
pub fn id(&self) -> HistoryEntryId {
match self {
HistoryEntry::Thread(thread) => HistoryEntryId::Thread(thread.id.clone()),
HistoryEntry::Context(context) => HistoryEntryId::Context(context.path.clone()),
}
}
pub fn title(&self) -> &SharedString {
match self {
HistoryEntry::Thread(thread) => &thread.summary,
HistoryEntry::Context(context) => &context.title,
}
}
}
/// Generic identifier for a history entry.
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum HistoryEntryId {
Thread(ThreadId),
Context(Arc<Path>),
}
#[derive(Serialize, Deserialize)]
enum SerializedRecentOpen {
Thread(String),
ContextName(String),
/// Old format which stores the full path
Context(String),
}
pub struct HistoryStore {
context_store: Entity<assistant_context::ContextStore>,
recently_opened_entries: VecDeque<HistoryEntryId>,
_subscriptions: Vec<gpui::Subscription>,
_save_recently_opened_entries_task: Task<()>,
}
impl HistoryStore {
pub fn new(
context_store: Entity<assistant_context::ContextStore>,
initial_recent_entries: impl IntoIterator<Item = HistoryEntryId>,
cx: &mut Context<Self>,
) -> Self {
let subscriptions = vec![cx.observe(&context_store, |_, _, cx| cx.notify())];
cx.spawn(async move |this, cx| {
let entries = Self::load_recently_opened_entries(cx).await.log_err()?;
this.update(cx, |this, _| {
this.recently_opened_entries
.extend(
entries.into_iter().take(
MAX_RECENTLY_OPENED_ENTRIES
.saturating_sub(this.recently_opened_entries.len()),
),
);
})
.ok()
})
.detach();
Self {
context_store,
recently_opened_entries: initial_recent_entries.into_iter().collect(),
_subscriptions: subscriptions,
_save_recently_opened_entries_task: Task::ready(()),
}
}
pub fn entries(&self, cx: &mut Context<Self>) -> Vec<HistoryEntry> {
let mut history_entries = Vec::new();
#[cfg(debug_assertions)]
if std::env::var("ZED_SIMULATE_NO_THREAD_HISTORY").is_ok() {
return history_entries;
}
history_entries.extend(
self.context_store
.read(cx)
.unordered_contexts()
.cloned()
.map(HistoryEntry::Context),
);
history_entries.sort_unstable_by_key(|entry| std::cmp::Reverse(entry.updated_at()));
history_entries
}
pub fn recent_entries(&self, limit: usize, cx: &mut Context<Self>) -> Vec<HistoryEntry> {
self.entries(cx).into_iter().take(limit).collect()
}
pub fn recently_opened_entries(&self, cx: &App) -> Vec<HistoryEntry> {
#[cfg(debug_assertions)]
if std::env::var("ZED_SIMULATE_NO_THREAD_HISTORY").is_ok() {
return Vec::new();
}
let context_entries =
self.context_store
.read(cx)
.unordered_contexts()
.flat_map(|context| {
self.recently_opened_entries
.iter()
.enumerate()
.flat_map(|(index, entry)| match entry {
HistoryEntryId::Context(path) if &context.path == path => {
Some((index, HistoryEntry::Context(context.clone())))
}
_ => None,
})
});
context_entries
// optimization to halt iteration early
.take(self.recently_opened_entries.len())
.sorted_unstable_by_key(|(index, _)| *index)
.map(|(_, entry)| entry)
.collect()
}
fn save_recently_opened_entries(&mut self, cx: &mut Context<Self>) {
let serialized_entries = self
.recently_opened_entries
.iter()
.filter_map(|entry| match entry {
HistoryEntryId::Context(path) => path.file_name().map(|file| {
SerializedRecentOpen::ContextName(file.to_string_lossy().into_owned())
}),
HistoryEntryId::Thread(id) => Some(SerializedRecentOpen::Thread(id.to_string())),
})
.collect::<Vec<_>>();
self._save_recently_opened_entries_task = cx.spawn(async move |_, cx| {
cx.background_executor()
.timer(SAVE_RECENTLY_OPENED_ENTRIES_DEBOUNCE)
.await;
cx.background_spawn(async move {
let path = paths::data_dir().join(NAVIGATION_HISTORY_PATH);
let content = serde_json::to_string(&serialized_entries)?;
std::fs::write(path, content)?;
anyhow::Ok(())
})
.await
.log_err();
});
}
fn load_recently_opened_entries(cx: &AsyncApp) -> Task<Result<Vec<HistoryEntryId>>> {
cx.background_spawn(async move {
let path = paths::data_dir().join(NAVIGATION_HISTORY_PATH);
let contents = match smol::fs::read_to_string(path).await {
Ok(it) => it,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
return Ok(Vec::new());
}
Err(e) => {
return Err(e)
.context("deserializing persisted agent panel navigation history");
}
};
let entries = serde_json::from_str::<Vec<SerializedRecentOpen>>(&contents)
.context("deserializing persisted agent panel navigation history")?
.into_iter()
.take(MAX_RECENTLY_OPENED_ENTRIES)
.flat_map(|entry| match entry {
SerializedRecentOpen::Thread(id) => {
Some(HistoryEntryId::Thread(id.as_str().into()))
}
SerializedRecentOpen::ContextName(file_name) => Some(HistoryEntryId::Context(
contexts_dir().join(file_name).into(),
)),
SerializedRecentOpen::Context(path) => {
Path::new(&path).file_name().map(|file_name| {
HistoryEntryId::Context(contexts_dir().join(file_name).into())
})
}
})
.collect::<Vec<_>>();
Ok(entries)
})
}
pub fn push_recently_opened_entry(&mut self, entry: HistoryEntryId, cx: &mut Context<Self>) {
self.recently_opened_entries
.retain(|old_entry| old_entry != &entry);
self.recently_opened_entries.push_front(entry);
self.recently_opened_entries
.truncate(MAX_RECENTLY_OPENED_ENTRIES);
self.save_recently_opened_entries(cx);
}
pub fn remove_recently_opened_thread(&mut self, id: ThreadId, cx: &mut Context<Self>) {
self.recently_opened_entries.retain(
|entry| !matches!(entry, HistoryEntryId::Thread(thread_id) if thread_id == &id),
);
self.save_recently_opened_entries(cx);
}
pub fn replace_recently_opened_text_thread(
&mut self,
old_path: &Path,
new_path: &Arc<Path>,
cx: &mut Context<Self>,
) {
for entry in &mut self.recently_opened_entries {
match entry {
HistoryEntryId::Context(path) if path.as_ref() == old_path => {
*entry = HistoryEntryId::Context(new_path.clone());
break;
}
_ => {}
}
}
self.save_recently_opened_entries(cx);
}
pub fn remove_recently_opened_entry(&mut self, entry: &HistoryEntryId, cx: &mut Context<Self>) {
self.recently_opened_entries
.retain(|old_entry| old_entry != entry);
self.save_recently_opened_entries(cx);
}
}

View File

@@ -3220,6 +3220,7 @@ mod tests {
use settings::{LanguageModelParameters, Settings, SettingsStore};
use std::sync::Arc;
use std::time::Duration;
use theme::ThemeSettings;
use util::path;
use workspace::Workspace;
@@ -5280,7 +5281,7 @@ fn main() {{
thread_store::init(fs.clone(), cx);
workspace::init_settings(cx);
language_model::init_settings(cx);
theme::init(theme::LoadThemes::JustBase, cx);
ThemeSettings::register(cx);
ToolRegistry::default_global(cx);
assistant_tool::init(cx);

View File

@@ -15,11 +15,10 @@ use agent_settings::{
use anyhow::{Context as _, Result, anyhow};
use assistant_tool::adapt_schema_to_format;
use chrono::{DateTime, Utc};
use client::{ModelRequestUsage, RequestUsage, UserStore};
use cloud_llm_client::{CompletionIntent, CompletionRequestStatus, Plan, UsageLimit};
use client::{ModelRequestUsage, RequestUsage};
use cloud_llm_client::{CompletionIntent, CompletionRequestStatus, UsageLimit};
use collections::{HashMap, HashSet, IndexMap};
use fs::Fs;
use futures::stream;
use futures::{
FutureExt,
channel::{mpsc, oneshot},
@@ -35,7 +34,7 @@ use language_model::{
LanguageModelImage, LanguageModelProviderId, LanguageModelRegistry, LanguageModelRequest,
LanguageModelRequestMessage, LanguageModelRequestTool, LanguageModelToolResult,
LanguageModelToolResultContent, LanguageModelToolSchemaFormat, LanguageModelToolUse,
LanguageModelToolUseId, Role, SelectedModel, StopReason, TokenUsage, ZED_CLOUD_PROVIDER_ID,
LanguageModelToolUseId, Role, SelectedModel, StopReason, TokenUsage,
};
use project::{
Project,
@@ -586,7 +585,6 @@ pub struct Thread {
pending_title_generation: Option<Task<()>>,
summary: Option<SharedString>,
messages: Vec<Message>,
user_store: Entity<UserStore>,
completion_mode: CompletionMode,
/// Holds the task that handles agent interaction until the end of the turn.
/// Survives across multiple requests as the model performs tool calls and
@@ -643,7 +641,6 @@ impl Thread {
pending_title_generation: None,
summary: None,
messages: Vec::new(),
user_store: project.read(cx).user_store(),
completion_mode: AgentSettings::get_global(cx).preferred_completion_mode,
running_turn: None,
pending_message: None,
@@ -823,7 +820,6 @@ impl Thread {
pending_title_generation: None,
summary: db_thread.detailed_summary,
messages: db_thread.messages,
user_store: project.read(cx).user_store(),
completion_mode: db_thread.completion_mode.unwrap_or_default(),
running_turn: None,
pending_message: None,
@@ -1253,12 +1249,12 @@ impl Thread {
);
log::debug!("Calling model.stream_completion, attempt {}", attempt);
let (mut events, mut error) = match model.stream_completion(request, cx).await {
Ok(events) => (events, None),
Err(err) => (stream::empty().boxed(), Some(err)),
};
let mut events = model
.stream_completion(request, cx)
.await
.map_err(|error| anyhow!(error))?;
let mut tool_results = FuturesUnordered::new();
let mut error = None;
while let Some(event) = events.next().await {
log::trace!("Received completion event: {:?}", event);
match event {
@@ -1306,10 +1302,8 @@ impl Thread {
if let Some(error) = error {
attempt += 1;
let retry = this.update(cx, |this, cx| {
let user_store = this.user_store.read(cx);
this.handle_completion_error(error, attempt, user_store.plan())
})??;
let retry =
this.update(cx, |this, _| this.handle_completion_error(error, attempt))??;
let timer = cx.background_executor().timer(retry.duration);
event_stream.send_retry(retry);
timer.await;
@@ -1336,23 +1330,8 @@ impl Thread {
&mut self,
error: LanguageModelCompletionError,
attempt: u8,
plan: Option<Plan>,
) -> Result<acp_thread::RetryStatus> {
let Some(model) = self.model.as_ref() else {
return Err(anyhow!(error));
};
let auto_retry = if model.provider_id() == ZED_CLOUD_PROVIDER_ID {
match plan {
Some(Plan::V2(_)) => true,
Some(Plan::V1(_)) => self.completion_mode == CompletionMode::Burn,
None => false,
}
} else {
true
};
if !auto_retry {
if self.completion_mode == CompletionMode::Normal {
return Err(anyhow!(error));
}

View File

@@ -790,7 +790,7 @@ mod tests {
store.update_user_settings(cx, |settings| {
settings.project.all_languages.defaults.format_on_save = Some(FormatOnSave::On);
settings.project.all_languages.defaults.formatter =
Some(language::language_settings::FormatterList::default());
Some(language::language_settings::SelectedFormatter::Auto);
});
});
});

View File

@@ -9,9 +9,8 @@ use futures::io::BufReader;
use project::Project;
use project::agent_server_store::AgentServerCommand;
use serde::Deserialize;
use settings::{Settings as _, SettingsLocation};
use task::Shell;
use util::{ResultExt as _, get_default_system_shell_preferring_bash};
use util::ResultExt as _;
use std::path::PathBuf;
use std::{any::Any, cell::RefCell};
@@ -23,7 +22,7 @@ use gpui::{App, AppContext as _, AsyncApp, Entity, SharedString, Task, WeakEntit
use acp_thread::{AcpThread, AuthRequired, LoadError, TerminalProviderEvent};
use terminal::TerminalBuilder;
use terminal::terminal_settings::{AlternateScroll, CursorShape, TerminalSettings};
use terminal::terminal_settings::{AlternateScroll, CursorShape};
#[derive(Debug, Error)]
#[error("Unsupported version")]
@@ -169,10 +168,7 @@ impl AcpConnection {
meta: None,
},
terminal: true,
meta: Some(serde_json::json!({
// Experimental: Allow for rendering terminal output from the agents
"terminal_output": true,
})),
meta: None,
},
meta: None,
})
@@ -819,25 +815,13 @@ impl acp::Client for ClientDelegate {
let mut env = if let Some(dir) = &args.cwd {
project
.update(&mut self.cx.clone(), |project, cx| {
let worktree = project.find_worktree(dir.as_path(), cx);
let shell = TerminalSettings::get(
worktree.as_ref().map(|(worktree, path)| SettingsLocation {
worktree_id: worktree.read(cx).id(),
path: &path,
}),
cx,
)
.shell
.clone();
project.directory_environment(&shell, dir.clone().into(), cx)
project.directory_environment(&task::Shell::System, dir.clone().into(), cx)
})?
.await
.unwrap_or_default()
} else {
Default::default()
};
// Disables paging for `git` and hopefully other commands
env.insert("PAGER".into(), "".into());
for var in args.env {
env.insert(var.name, var.value);
}
@@ -850,11 +834,8 @@ impl acp::Client for ClientDelegate {
.and_then(|r| r.read(cx).default_system_shell())
.map(Shell::Program)
})?
.unwrap_or_else(|| Shell::Program(get_default_system_shell_preferring_bash()));
let is_windows = project
.read_with(&self.cx, |project, cx| project.path_style(cx).is_windows())
.unwrap_or(cfg!(windows));
let (task_command, task_args) = task::ShellBuilder::new(&shell, is_windows)
.unwrap_or(task::Shell::System);
let (task_command, task_args) = task::ShellBuilder::new(&shell)
.redirect_stdin_to_dev_null()
.build(Some(args.command.clone()), &args.args);

View File

@@ -1,16 +1,11 @@
use std::rc::Rc;
use std::sync::Arc;
use std::{any::Any, path::Path};
use acp_thread::AgentConnection;
use agent_client_protocol as acp;
use anyhow::{Context as _, Result};
use fs::Fs;
use gpui::{App, AppContext as _, SharedString, Task};
use project::agent_server_store::{AllAgentServersSettings, CODEX_NAME};
use settings::{SettingsStore, update_settings_file};
use crate::{AgentServer, AgentServerDelegate, load_proxy_env};
use acp_thread::AgentConnection;
use anyhow::{Context as _, Result};
use gpui::{App, SharedString, Task};
use project::agent_server_store::CODEX_NAME;
#[derive(Clone)]
pub struct Codex;
@@ -35,27 +30,6 @@ impl AgentServer for Codex {
ui::IconName::AiOpenAi
}
fn default_mode(&self, cx: &mut App) -> Option<acp::SessionModeId> {
let settings = cx.read_global(|settings: &SettingsStore, _| {
settings.get::<AllAgentServersSettings>(None).codex.clone()
});
settings
.as_ref()
.and_then(|s| s.default_mode.clone().map(|m| acp::SessionModeId(m.into())))
}
fn set_default_mode(&self, mode_id: Option<acp::SessionModeId>, fs: Arc<dyn Fs>, cx: &mut App) {
update_settings_file(fs, cx, |settings, _| {
settings
.agent_servers
.get_or_insert_default()
.codex
.get_or_insert_default()
.default_mode = mode_id.map(|m| m.to_string())
});
}
fn connect(
&self,
root_dir: Option<&Path>,

View File

@@ -151,7 +151,7 @@ impl Default for AgentProfileId {
}
impl Settings for AgentSettings {
fn from_settings(content: &settings::SettingsContent) -> Self {
fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self {
let agent = content.agent.clone().unwrap();
Self {
enabled: agent.enabled.unwrap(),

View File

@@ -12,7 +12,7 @@ use anyhow::Result;
use editor::{CompletionProvider, Editor, ExcerptId};
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{App, Entity, Task, WeakEntity};
use language::{Buffer, CodeLabel, CodeLabelBuilder, HighlightId};
use language::{Buffer, CodeLabel, HighlightId};
use lsp::CompletionContext;
use project::lsp_store::{CompletionDocumentation, SymbolLocation};
use project::{
@@ -27,7 +27,7 @@ use util::rel_path::RelPath;
use workspace::Workspace;
use crate::AgentPanel;
use crate::acp::message_editor::MessageEditor;
use crate::acp::message_editor::{MessageEditor, MessageEditorEvent};
use crate::context_picker::file_context_picker::{FileMatch, search_files};
use crate::context_picker::rules_context_picker::{RulesContextEntry, search_rules};
use crate::context_picker::symbol_context_picker::SymbolMatch;
@@ -673,7 +673,7 @@ impl ContextPickerCompletionProvider {
fn build_code_label_for_full_path(file_name: &str, directory: Option<&str>, cx: &App) -> CodeLabel {
let comment_id = cx.theme().syntax().highlight_id("comment").map(HighlightId);
let mut label = CodeLabelBuilder::default();
let mut label = CodeLabel::default();
label.push_str(file_name, None);
label.push_str(" ", None);
@@ -682,7 +682,9 @@ fn build_code_label_for_full_path(file_name: &str, directory: Option<&str>, cx:
label.push_str(directory, comment_id);
}
label.build()
label.filter_range = 0..label.text().len();
label
}
impl CompletionProvider for ContextPickerCompletionProvider {
@@ -757,13 +759,13 @@ impl CompletionProvider for ContextPickerCompletionProvider {
let editor = editor.clone();
move |cx| {
editor
.update(cx, |editor, cx| {
.update(cx, |_editor, cx| {
match intent {
CompletionIntent::Complete
| CompletionIntent::CompleteWithInsert
| CompletionIntent::CompleteWithReplace => {
if !is_missing_argument {
editor.send(cx);
cx.emit(MessageEditorEvent::Send);
}
}
CompletionIntent::Compose => {}
@@ -773,7 +775,7 @@ impl CompletionProvider for ContextPickerCompletionProvider {
}
});
}
false
is_missing_argument
}
})),
}
@@ -908,17 +910,6 @@ impl CompletionProvider for ContextPickerCompletionProvider {
offset_to_line,
self.prompt_capabilities.borrow().embedded_context,
)
.filter(|completion| {
// Right now we don't support completing arguments of slash commands
let is_slash_command_with_argument = matches!(
completion,
ContextCompletion::SlashCommand(SlashCommandCompletion {
argument: Some(_),
..
})
);
!is_slash_command_with_argument
})
.map(|completion| {
completion.source_range().start <= offset_to_line + position.column as usize
&& completion.source_range().end >= offset_to_line + position.column as usize

View File

@@ -414,6 +414,7 @@ mod tests {
use project::Project;
use serde_json::json;
use settings::{Settings as _, SettingsStore};
use theme::ThemeSettings;
use util::path;
use workspace::Workspace;
@@ -543,7 +544,7 @@ mod tests {
Project::init_settings(cx);
AgentSettings::register(cx);
workspace::init_settings(cx);
theme::init(theme::LoadThemes::JustBase, cx);
ThemeSettings::register(cx);
release_channel::init(SemanticVersion::default(), cx);
EditorSettings::register(cx);
});

View File

@@ -141,9 +141,7 @@ impl MessageEditor {
subscriptions.push(cx.subscribe_in(&editor, window, {
move |this, editor, event, window, cx| {
if let EditorEvent::Edited { .. } = event
&& !editor.read(cx).read_only(cx)
{
if let EditorEvent::Edited { .. } = event {
let snapshot = editor.update(cx, |editor, cx| {
let new_hints = this
.command_hint(editor.buffer(), cx)
@@ -825,20 +823,13 @@ impl MessageEditor {
});
}
pub fn send(&mut self, cx: &mut Context<Self>) {
fn send(&mut self, _: &Chat, _: &mut Window, cx: &mut Context<Self>) {
if self.is_empty(cx) {
return;
}
self.editor.update(cx, |editor, cx| {
editor.clear_inlay_hints(cx);
});
cx.emit(MessageEditorEvent::Send)
}
fn chat(&mut self, _: &Chat, _: &mut Window, cx: &mut Context<Self>) {
self.send(cx);
}
fn cancel(&mut self, _: &editor::actions::Cancel, _: &mut Window, cx: &mut Context<Self>) {
cx.emit(MessageEditorEvent::Cancel)
}
@@ -1039,7 +1030,6 @@ impl MessageEditor {
) else {
return;
};
self.editor.update(cx, |message_editor, cx| {
message_editor.edit([(cursor_anchor..cursor_anchor, completion.new_text)], cx);
});
@@ -1297,7 +1287,7 @@ impl Render for MessageEditor {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
div()
.key_context("MessageEditor")
.on_action(cx.listener(Self::chat))
.on_action(cx.listener(Self::send))
.on_action(cx.listener(Self::cancel))
.capture_action(cx.listener(Self::paste))
.flex_1()
@@ -2021,11 +2011,21 @@ mod tests {
editor.update_in(&mut cx, |editor, _window, cx| {
assert_eq!(editor.text(cx), "/say-hello ");
assert_eq!(editor.display_text(cx), "/say-hello <name>");
assert!(!editor.has_visible_completions_menu());
assert!(editor.has_visible_completions_menu());
assert_eq!(
current_completion_labels_with_documentation(editor),
&[("say-hello".into(), "Say hello to whoever you want".into())]
);
});
cx.simulate_input("GPT5");
editor.update_in(&mut cx, |editor, window, cx| {
assert!(editor.has_visible_completions_menu());
editor.confirm_completion(&editor::actions::ConfirmCompletion::default(), window, cx);
});
cx.run_until_parked();
editor.update_in(&mut cx, |editor, window, cx| {
@@ -2034,7 +2034,7 @@ mod tests {
assert!(!editor.has_visible_completions_menu());
// Delete argument
for _ in 0..5 {
for _ in 0..4 {
editor.backspace(&editor::actions::Backspace, window, cx);
}
});
@@ -2042,12 +2042,13 @@ mod tests {
cx.run_until_parked();
editor.update_in(&mut cx, |editor, window, cx| {
assert_eq!(editor.text(cx), "/say-hello");
assert_eq!(editor.text(cx), "/say-hello ");
// Hint is visible because argument was deleted
assert_eq!(editor.display_text(cx), "/say-hello <name>");
// Delete last command letter
editor.backspace(&editor::actions::Backspace, window, cx);
editor.backspace(&editor::actions::Backspace, window, cx);
});
cx.run_until_parked();

View File

@@ -174,16 +174,11 @@ impl Render for ModeSelector {
let this = cx.entity();
let icon = if self.menu_handle.is_deployed() {
IconName::ChevronUp
} else {
IconName::ChevronDown
};
let trigger_button = Button::new("mode-selector-trigger", current_mode_name)
.label_size(LabelSize::Small)
.style(ButtonStyle::Subtle)
.color(Color::Muted)
.icon(icon)
.icon(IconName::ChevronDown)
.icon_size(IconSize::XSmall)
.icon_position(IconPosition::End)
.icon_color(Color::Muted)

View File

@@ -5,12 +5,12 @@ use anyhow::Result;
use collections::IndexMap;
use futures::FutureExt;
use fuzzy::{StringMatchCandidate, match_strings};
use gpui::{AsyncWindowContext, BackgroundExecutor, DismissEvent, Task, WeakEntity};
use gpui::{Action, AsyncWindowContext, BackgroundExecutor, DismissEvent, Task, WeakEntity};
use ordered_float::OrderedFloat;
use picker::{Picker, PickerDelegate};
use ui::{
DocumentationAside, DocumentationEdge, DocumentationSide, IntoElement, ListItem,
ListItemSpacing, prelude::*,
AnyElement, App, Context, DocumentationAside, DocumentationEdge, DocumentationSide,
IntoElement, ListItem, ListItemSpacing, SharedString, Window, prelude::*, rems,
};
use util::ResultExt;
@@ -278,6 +278,36 @@ impl PickerDelegate for AcpModelPickerDelegate {
}
}
fn render_footer(
&self,
_: &mut Window,
cx: &mut Context<Picker<Self>>,
) -> Option<gpui::AnyElement> {
Some(
h_flex()
.w_full()
.border_t_1()
.border_color(cx.theme().colors().border_variant)
.p_1()
.gap_4()
.justify_between()
.child(
Button::new("configure", "Configure")
.icon(IconName::Settings)
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
.icon_position(IconPosition::Start)
.on_click(|_, window, cx| {
window.dispatch_action(
zed_actions::agent::OpenSettings.boxed_clone(),
cx,
);
}),
)
.into_any(),
)
}
fn documentation_aside(
&self,
_window: &mut Window,
@@ -287,7 +317,7 @@ impl PickerDelegate for AcpModelPickerDelegate {
let description = description.clone();
DocumentationAside::new(
DocumentationSide::Left,
DocumentationEdge::Top,
DocumentationEdge::Bottom,
Rc::new(move |_| Label::new(description.clone()).into_any_element()),
)
})

View File

@@ -57,26 +57,30 @@ impl Render for AcpModelSelectorPopover {
let focus_handle = self.focus_handle.clone();
let (color, icon) = if self.menu_handle.is_deployed() {
(Color::Accent, IconName::ChevronUp)
let color = if self.menu_handle.is_deployed() {
Color::Accent
} else {
(Color::Muted, IconName::ChevronDown)
Color::Muted
};
PickerPopoverMenu::new(
self.selector.clone(),
ButtonLike::new("active-model")
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
.when_some(model_icon, |this, icon| {
this.child(Icon::new(icon).color(color).size(IconSize::XSmall))
})
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
.child(
Label::new(model_name)
.color(color)
.size(LabelSize::Small)
.ml_0p5(),
)
.child(Icon::new(icon).color(Color::Muted).size(IconSize::XSmall)),
.child(
Icon::new(IconName::ChevronDown)
.color(Color::Muted)
.size(IconSize::XSmall),
),
move |window, cx| {
Tooltip::for_action_in(
"Change Model",

View File

@@ -278,7 +278,7 @@ pub struct AcpThreadView {
thread_feedback: ThreadFeedbackState,
list_state: ListState,
auth_task: Option<Task<()>>,
collapsed_tool_calls: HashSet<acp::ToolCallId>,
expanded_tool_calls: HashSet<acp::ToolCallId>,
expanded_thinking_blocks: HashSet<(usize, usize)>,
edits_expanded: bool,
plan_expanded: bool,
@@ -292,8 +292,6 @@ pub struct AcpThreadView {
resume_thread_metadata: Option<DbThreadMetadata>,
_cancel_task: Option<Task<()>>,
_subscriptions: [Subscription; 5],
#[cfg(target_os = "windows")]
show_codex_windows_warning: bool,
}
enum ThreadState {
@@ -337,10 +335,7 @@ impl AcpThreadView {
let placeholder = if agent.name() == "Zed Agent" {
format!("Message the {} — @ to include context", agent.name())
} else if agent.name() == "Claude Code"
|| agent.name() == "Codex"
|| !available_commands.borrow().is_empty()
{
} else if agent.name() == "Claude Code" || !available_commands.borrow().is_empty() {
format!(
"Message {} — @ to include context, / for commands",
agent.name()
@@ -399,10 +394,6 @@ impl AcpThreadView {
),
];
#[cfg(target_os = "windows")]
let show_codex_windows_warning = crate::ExternalAgent::parse_built_in(agent.as_ref())
== Some(crate::ExternalAgent::Codex);
Self {
agent: agent.clone(),
workspace: workspace.clone(),
@@ -428,7 +419,7 @@ impl AcpThreadView {
thread_error: None,
thread_feedback: Default::default(),
auth_task: None,
collapsed_tool_calls: HashSet::default(),
expanded_tool_calls: HashSet::default(),
expanded_thinking_blocks: HashSet::default(),
editing_message: None,
edits_expanded: false,
@@ -445,8 +436,6 @@ impl AcpThreadView {
focus_handle: cx.focus_handle(),
new_server_version_available: None,
resume_thread_metadata: resume_thread,
#[cfg(target_os = "windows")]
show_codex_windows_warning,
}
}
@@ -965,17 +954,17 @@ impl AcpThreadView {
) {
match &event.view_event {
ViewEvent::NewDiff(tool_call_id) => {
if !AgentSettings::get_global(cx).expand_edit_card {
self.collapsed_tool_calls.insert(tool_call_id.clone());
if AgentSettings::get_global(cx).expand_edit_card {
self.expanded_tool_calls.insert(tool_call_id.clone());
}
}
ViewEvent::NewTerminal(tool_call_id) => {
if !AgentSettings::get_global(cx).expand_terminal_card {
self.collapsed_tool_calls.insert(tool_call_id.clone());
if AgentSettings::get_global(cx).expand_terminal_card {
self.expanded_tool_calls.insert(tool_call_id.clone());
}
}
ViewEvent::TerminalMovedToBackground(tool_call_id) => {
self.collapsed_tool_calls.insert(tool_call_id.clone());
self.expanded_tool_calls.remove(tool_call_id);
}
ViewEvent::MessageEditorEvent(_editor, MessageEditorEvent::Focus) => {
if let Some(thread) = self.thread()
@@ -1066,9 +1055,6 @@ impl AcpThreadView {
.iter()
.any(|command| command.name == "logout");
if can_login && !logout_supported {
self.message_editor
.update(cx, |editor, cx| editor.clear(window, cx));
let this = cx.weak_entity();
let agent = self.agent.clone();
window.defer(cx, |window, cx| {
@@ -1265,6 +1251,12 @@ impl AcpThreadView {
.detach();
}
fn open_agent_diff(&mut self, _: &OpenAgentDiff, window: &mut Window, cx: &mut Context<Self>) {
if let Some(thread) = self.thread() {
AgentDiffPane::deploy(thread.clone(), self.workspace.clone(), window, cx).log_err();
}
}
fn open_edited_buffer(
&mut self,
buffer: &Entity<Buffer>,
@@ -2130,7 +2122,7 @@ impl AcpThreadView {
let is_collapsible = !tool_call.content.is_empty() && !needs_confirmation;
let is_open = needs_confirmation || !self.collapsed_tool_calls.contains(&tool_call.id);
let is_open = needs_confirmation || self.expanded_tool_calls.contains(&tool_call.id);
let tool_output_display =
if is_open {
@@ -2280,9 +2272,9 @@ impl AcpThreadView {
let id = tool_call.id.clone();
move |this: &mut Self, _, _, cx: &mut Context<Self>| {
if is_open {
this.collapsed_tool_calls.insert(id.clone());
this.expanded_tool_calls.remove(&id);
} else {
this.collapsed_tool_calls.remove(&id);
this.expanded_tool_calls.insert(id.clone());
}
cx.notify();
}
@@ -2484,7 +2476,7 @@ impl AcpThreadView {
.icon_color(Color::Muted)
.on_click(cx.listener({
move |this: &mut Self, _, _, cx: &mut Context<Self>| {
this.collapsed_tool_calls.insert(tool_call_id.clone());
this.expanded_tool_calls.remove(&tool_call_id);
cx.notify();
}
})),
@@ -2762,7 +2754,7 @@ impl AcpThreadView {
.map(|path| path.display().to_string())
.unwrap_or_else(|| "current directory".to_string());
let is_expanded = !self.collapsed_tool_calls.contains(&tool_call.id);
let is_expanded = self.expanded_tool_calls.contains(&tool_call.id);
let header = h_flex()
.id(header_id)
@@ -2897,9 +2889,9 @@ impl AcpThreadView {
let id = tool_call.id.clone();
move |this, _event, _window, _cx| {
if is_expanded {
this.collapsed_tool_calls.insert(id.clone());
this.expanded_tool_calls.remove(&id);
} else {
this.collapsed_tool_calls.remove(&id);
this.expanded_tool_calls.insert(id.clone());
}
}
})),
@@ -3291,12 +3283,6 @@ impl AcpThreadView {
this.style(ButtonStyle::Outlined)
}
})
.when_some(
method.description.clone(),
|this, description| {
this.tooltip(Tooltip::text(description))
},
)
.on_click({
cx.listener(move |this, _, window, cx| {
telemetry::event!(
@@ -4986,12 +4972,10 @@ impl AcpThreadView {
})
}
/// Inserts the selected text into the message editor or the message being
/// edited, if any.
pub(crate) fn insert_selections(&self, window: &mut Window, cx: &mut Context<Self>) {
self.active_editor(cx).update(cx, |editor, cx| {
editor.insert_selections(window, cx);
});
self.message_editor.update(cx, |message_editor, cx| {
message_editor.insert_selections(window, cx);
})
}
fn render_thread_retry_status_callout(
@@ -5036,49 +5020,6 @@ impl AcpThreadView {
)
}
#[cfg(target_os = "windows")]
fn render_codex_windows_warning(&self, cx: &mut Context<Self>) -> Option<Callout> {
if self.show_codex_windows_warning {
Some(
Callout::new()
.icon(IconName::Warning)
.severity(Severity::Warning)
.title("Codex on Windows")
.description(
"For best performance, run Codex in Windows Subsystem for Linux (WSL2)",
)
.actions_slot(
Button::new("open-wsl-modal", "Open in WSL")
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
.on_click(cx.listener({
move |_, _, window, cx| {
window.dispatch_action(
zed_actions::wsl_actions::OpenWsl::default().boxed_clone(),
cx,
);
cx.notify();
}
})),
)
.dismiss_action(
IconButton::new("dismiss", IconName::Close)
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
.tooltip(Tooltip::text("Dismiss Warning"))
.on_click(cx.listener({
move |this, _, _, cx| {
this.show_codex_windows_warning = false;
cx.notify();
}
})),
),
)
} else {
None
}
}
fn render_thread_error(&self, window: &mut Window, cx: &mut Context<Self>) -> Option<Div> {
let content = match self.thread_error.as_ref()? {
ThreadError::Other(error) => self.render_any_thread_error(error.clone(), cx),
@@ -5445,23 +5386,6 @@ impl AcpThreadView {
};
task.detach_and_log_err(cx);
}
/// Returns the currently active editor, either for a message that is being
/// edited or the editor for a new message.
fn active_editor(&self, cx: &App) -> Entity<MessageEditor> {
if let Some(index) = self.editing_message
&& let Some(editor) = self
.entry_view_state
.read(cx)
.entry(index)
.and_then(|e| e.message_editor())
.cloned()
{
editor
} else {
self.message_editor.clone()
}
}
}
fn loading_contents_spinner(size: IconSize) -> AnyElement {
@@ -5476,7 +5400,7 @@ impl Focusable for AcpThreadView {
fn focus_handle(&self, cx: &App) -> FocusHandle {
match self.thread_state {
ThreadState::Loading { .. } | ThreadState::Ready { .. } => {
self.active_editor(cx).focus_handle(cx)
self.message_editor.focus_handle(cx)
}
ThreadState::LoadError(_) | ThreadState::Unauthenticated { .. } => {
self.focus_handle.clone()
@@ -5493,6 +5417,7 @@ impl Render for AcpThreadView {
v_flex()
.size_full()
.key_context("AcpThread")
.on_action(cx.listener(Self::open_agent_diff))
.on_action(cx.listener(Self::toggle_burn_mode))
.on_action(cx.listener(Self::keep_all))
.on_action(cx.listener(Self::reject_all))
@@ -5566,16 +5491,6 @@ impl Render for AcpThreadView {
_ => this,
})
.children(self.render_thread_retry_status_callout(window, cx))
.children({
#[cfg(target_os = "windows")]
{
self.render_codex_windows_warning(cx)
}
#[cfg(not(target_os = "windows"))]
{
Vec::<Empty>::new()
}
})
.children(self.render_thread_error(window, cx))
.when_some(
self.new_server_version_available.as_ref().filter(|_| {
@@ -6172,7 +6087,7 @@ pub(crate) mod tests {
Project::init_settings(cx);
AgentSettings::register(cx);
workspace::init_settings(cx);
theme::init(theme::LoadThemes::JustBase, cx);
ThemeSettings::register(cx);
release_channel::init(SemanticVersion::default(), cx);
EditorSettings::register(cx);
prompt_store::init(cx)
@@ -6746,146 +6661,4 @@ pub(crate) mod tests {
)
});
}
#[gpui::test]
async fn test_message_editing_insert_selections(cx: &mut TestAppContext) {
init_test(cx);
let connection = StubAgentConnection::new();
connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk {
content: acp::ContentBlock::Text(acp::TextContent {
text: "Response".into(),
annotations: None,
meta: None,
}),
}]);
let (thread_view, cx) = setup_thread_view(StubAgentServer::new(connection), cx).await;
add_to_workspace(thread_view.clone(), cx);
let message_editor = cx.read(|cx| thread_view.read(cx).message_editor.clone());
message_editor.update_in(cx, |editor, window, cx| {
editor.set_text("Original message to edit", window, cx)
});
thread_view.update_in(cx, |thread_view, window, cx| thread_view.send(window, cx));
cx.run_until_parked();
let user_message_editor = thread_view.read_with(cx, |thread_view, cx| {
thread_view
.entry_view_state
.read(cx)
.entry(0)
.expect("Should have at least one entry")
.message_editor()
.expect("Should have message editor")
.clone()
});
cx.focus(&user_message_editor);
thread_view.read_with(cx, |thread_view, _cx| {
assert_eq!(thread_view.editing_message, Some(0));
});
// Ensure to edit the focused message before proceeding otherwise, since
// its content is not different from what was sent, focus will be lost.
user_message_editor.update_in(cx, |editor, window, cx| {
editor.set_text("Original message to edit with ", window, cx)
});
// Create a simple buffer with some text so we can create a selection
// that will then be added to the message being edited.
let (workspace, project) = thread_view.read_with(cx, |thread_view, _cx| {
(thread_view.workspace.clone(), thread_view.project.clone())
});
let buffer = project.update(cx, |project, cx| {
project.create_local_buffer("let a = 10 + 10;", None, false, cx)
});
workspace
.update_in(cx, |workspace, window, cx| {
let editor = cx.new(|cx| {
let mut editor =
Editor::for_buffer(buffer.clone(), Some(project.clone()), window, cx);
editor.change_selections(Default::default(), window, cx, |selections| {
selections.select_ranges([8..15]);
});
editor
});
workspace.add_item_to_active_pane(Box::new(editor), None, false, window, cx);
})
.unwrap();
thread_view.update_in(cx, |thread_view, window, cx| {
assert_eq!(thread_view.editing_message, Some(0));
thread_view.insert_selections(window, cx);
});
user_message_editor.read_with(cx, |editor, cx| {
let text = editor.editor().read(cx).text(cx);
let expected_text = String::from("Original message to edit with selection ");
assert_eq!(text, expected_text);
});
}
#[gpui::test]
async fn test_insert_selections(cx: &mut TestAppContext) {
init_test(cx);
let connection = StubAgentConnection::new();
connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk {
content: acp::ContentBlock::Text(acp::TextContent {
text: "Response".into(),
annotations: None,
meta: None,
}),
}]);
let (thread_view, cx) = setup_thread_view(StubAgentServer::new(connection), cx).await;
add_to_workspace(thread_view.clone(), cx);
let message_editor = cx.read(|cx| thread_view.read(cx).message_editor.clone());
message_editor.update_in(cx, |editor, window, cx| {
editor.set_text("Can you review this snippet ", window, cx)
});
// Create a simple buffer with some text so we can create a selection
// that will then be added to the message being edited.
let (workspace, project) = thread_view.read_with(cx, |thread_view, _cx| {
(thread_view.workspace.clone(), thread_view.project.clone())
});
let buffer = project.update(cx, |project, cx| {
project.create_local_buffer("let a = 10 + 10;", None, false, cx)
});
workspace
.update_in(cx, |workspace, window, cx| {
let editor = cx.new(|cx| {
let mut editor =
Editor::for_buffer(buffer.clone(), Some(project.clone()), window, cx);
editor.change_selections(Default::default(), window, cx, |selections| {
selections.select_ranges([8..15]);
});
editor
});
workspace.add_item_to_active_pane(Box::new(editor), None, false, window, cx);
})
.unwrap();
thread_view.update_in(cx, |thread_view, window, cx| {
assert_eq!(thread_view.editing_message, None);
thread_view.insert_selections(window, cx);
});
thread_view.read_with(cx, |thread_view, cx| {
let text = thread_view.message_editor.read(cx).text(cx);
let expected_txt = String::from("Can you review this snippet selection ");
assert_eq!(text, expected_txt);
})
}
}

View File

@@ -6,6 +6,7 @@ mod tool_picker;
use std::{ops::Range, sync::Arc};
use agent_settings::AgentSettings;
use anyhow::Result;
use assistant_tool::{ToolSource, ToolWorkingSet};
use cloud_llm_client::{Plan, PlanV1, PlanV2};
@@ -28,10 +29,10 @@ use project::{
agent_server_store::{AgentServerStore, CLAUDE_CODE_NAME, CODEX_NAME, GEMINI_NAME},
context_server_store::{ContextServerConfiguration, ContextServerStatus, ContextServerStore},
};
use settings::{SettingsStore, update_settings_file};
use settings::{Settings, SettingsStore, update_settings_file};
use ui::{
Chip, CommonAnimationExt, ContextMenu, Disclosure, Divider, DividerColor, ElevationIndex,
Indicator, PopoverMenu, Switch, SwitchColor, Tooltip, WithScrollbar, prelude::*,
Indicator, PopoverMenu, Switch, SwitchColor, SwitchField, Tooltip, WithScrollbar, prelude::*,
};
use util::ResultExt as _;
use workspace::{Workspace, create_and_open_local_file};
@@ -401,6 +402,101 @@ impl AgentConfiguration {
)
}
fn render_command_permission(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
let always_allow_tool_actions = AgentSettings::get_global(cx).always_allow_tool_actions;
let fs = self.fs.clone();
SwitchField::new(
"always-allow-tool-actions-switch",
Some("Allow running commands without asking for confirmation"),
Some(
"The agent can perform potentially destructive actions without asking for your confirmation.".into(),
),
always_allow_tool_actions,
move |state, _window, cx| {
let allow = state == &ToggleState::Selected;
update_settings_file(fs.clone(), cx, move |settings, _| {
settings.agent.get_or_insert_default().set_always_allow_tool_actions(allow);
});
},
)
}
fn render_single_file_review(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
let single_file_review = AgentSettings::get_global(cx).single_file_review;
let fs = self.fs.clone();
SwitchField::new(
"single-file-review",
Some("Enable single-file agent reviews"),
Some("Agent edits are also displayed in single-file editors for review.".into()),
single_file_review,
move |state, _window, cx| {
let allow = state == &ToggleState::Selected;
update_settings_file(fs.clone(), cx, move |settings, _| {
settings
.agent
.get_or_insert_default()
.set_single_file_review(allow);
});
},
)
}
fn render_sound_notification(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
let play_sound_when_agent_done = AgentSettings::get_global(cx).play_sound_when_agent_done;
let fs = self.fs.clone();
SwitchField::new(
"sound-notification",
Some("Play sound when finished generating"),
Some(
"Hear a notification sound when the agent is done generating changes or needs your input.".into(),
),
play_sound_when_agent_done,
move |state, _window, cx| {
let allow = state == &ToggleState::Selected;
update_settings_file(fs.clone(), cx, move |settings, _| {
settings.agent.get_or_insert_default().set_play_sound_when_agent_done(allow);
});
},
)
}
fn render_modifier_to_send(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
let use_modifier_to_send = AgentSettings::get_global(cx).use_modifier_to_send;
let fs = self.fs.clone();
SwitchField::new(
"modifier-send",
Some("Use modifier to submit a message"),
Some(
"Make a modifier (cmd-enter on macOS, ctrl-enter on Linux or Windows) required to send messages.".into(),
),
use_modifier_to_send,
move |state, _window, cx| {
let allow = state == &ToggleState::Selected;
update_settings_file(fs.clone(), cx, move |settings, _| {
settings.agent.get_or_insert_default().set_use_modifier_to_send(allow);
});
},
)
}
fn render_general_settings_section(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
v_flex()
.p(DynamicSpacing::Base16.rems(cx))
.pr(DynamicSpacing::Base20.rems(cx))
.gap_2p5()
.border_b_1()
.border_color(cx.theme().colors().border)
.child(Headline::new("General Settings"))
.child(self.render_command_permission(cx))
.child(self.render_single_file_review(cx))
.child(self.render_sound_notification(cx))
.child(self.render_modifier_to_send(cx))
}
fn render_zed_plan_info(&self, plan: Option<Plan>, cx: &mut Context<Self>) -> impl IntoElement {
if let Some(plan) = plan {
let free_chip_bg = cx
@@ -1045,6 +1141,7 @@ impl Render for AgentConfiguration {
.track_scroll(&self.scroll_handle)
.size_full()
.overflow_y_scroll()
.child(self.render_general_settings_section(cx))
.child(self.render_agent_servers_section(cx))
.child(self.render_context_servers_section(window, cx))
.child(self.render_provider_configuration_section(cx)),

View File

@@ -619,10 +619,10 @@ mod tests {
cx.update(|_window, cx| {
LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
registry.register_provider(
Arc::new(FakeLanguageModelProvider::new(
FakeLanguageModelProvider::new(
LanguageModelProviderId::new("someprovider"),
LanguageModelProviderName::new("Some Provider"),
)),
),
cx,
);
});

View File

@@ -1814,6 +1814,7 @@ mod tests {
use serde_json::json;
use settings::{Settings, SettingsStore};
use std::{path::Path, rc::Rc};
use theme::ThemeSettings;
use util::path;
#[gpui::test]
@@ -1826,7 +1827,7 @@ mod tests {
AgentSettings::register(cx);
prompt_store::init(cx);
workspace::init_settings(cx);
theme::init(theme::LoadThemes::JustBase, cx);
ThemeSettings::register(cx);
EditorSettings::register(cx);
language_model::init_settings(cx);
});
@@ -1978,7 +1979,7 @@ mod tests {
AgentSettings::register(cx);
prompt_store::init(cx);
workspace::init_settings(cx);
theme::init(theme::LoadThemes::JustBase, cx);
ThemeSettings::register(cx);
EditorSettings::register(cx);
language_model::init_settings(cx);
workspace::register_project_item::<Editor>(cx);

View File

@@ -7,7 +7,7 @@ use gpui::{Entity, FocusHandle, SharedString};
use picker::popover_menu::PickerPopoverMenu;
use settings::update_settings_file;
use std::sync::Arc;
use ui::{ButtonLike, PopoverMenuHandle, TintColor, Tooltip, prelude::*};
use ui::{ButtonLike, PopoverMenuHandle, Tooltip, prelude::*};
use zed_actions::agent::ToggleModelSelector;
pub struct AgentModelSelector {
@@ -70,11 +70,6 @@ impl Render for AgentModelSelector {
.unwrap_or_else(|| SharedString::from("Select a Model"));
let provider_icon = model.as_ref().map(|model| model.provider.icon());
let color = if self.menu_handle.is_deployed() {
Color::Accent
} else {
Color::Muted
};
let focus_handle = self.focus_handle.clone();
@@ -82,18 +77,17 @@ impl Render for AgentModelSelector {
self.selector.clone(),
ButtonLike::new("active-model")
.when_some(provider_icon, |this, icon| {
this.child(Icon::new(icon).color(color).size(IconSize::XSmall))
this.child(Icon::new(icon).color(Color::Muted).size(IconSize::XSmall))
})
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
.child(
Label::new(model_name)
.color(color)
.color(Color::Muted)
.size(LabelSize::Small)
.ml_0p5(),
)
.child(
Icon::new(IconName::ChevronDown)
.color(color)
.color(Color::Muted)
.size(IconSize::XSmall),
),
move |window, cx| {
@@ -105,14 +99,10 @@ impl Render for AgentModelSelector {
cx,
)
},
gpui::Corner::TopRight,
gpui::Corner::BottomRight,
cx,
)
.with_handle(self.menu_handle.clone())
.offset(gpui::Point {
x: px(0.0),
y: px(2.0),
})
.render(window, cx)
}
}

View File

@@ -19,10 +19,9 @@ use zed_actions::agent::{OpenClaudeCodeOnboardingModal, ReauthenticateAgent};
use crate::acp::{AcpThreadHistory, ThreadHistoryEvent};
use crate::ui::{AcpOnboardingModal, ClaudeCodeOnboardingModal};
use crate::{
AddContextServer, AgentDiffPane, DeleteRecentlyOpenThread, Follow, InlineAssistant,
NewTextThread, NewThread, OpenActiveThreadAsMarkdown, OpenAgentDiff, OpenHistory,
ResetTrialEndUpsell, ResetTrialUpsell, ToggleNavigationMenu, ToggleNewThreadMenu,
ToggleOptionsMenu,
AddContextServer, DeleteRecentlyOpenThread, Follow, InlineAssistant, NewTextThread, NewThread,
OpenActiveThreadAsMarkdown, OpenHistory, ResetTrialEndUpsell, ResetTrialUpsell,
ToggleNavigationMenu, ToggleNewThreadMenu, ToggleOptionsMenu,
acp::AcpThreadView,
agent_configuration::{AgentConfiguration, AssistantConfigurationEvent},
slash_command::SlashCommandCompletionProvider,
@@ -34,6 +33,7 @@ use crate::{
};
use agent::{
context_store::ContextStore,
history_store::{HistoryEntryId, HistoryStore},
thread_store::{TextThreadStore, ThreadStore},
};
use agent_settings::AgentSettings;
@@ -140,16 +140,6 @@ pub fn init(cx: &mut App) {
.register_action(|workspace, _: &Follow, window, cx| {
workspace.follow(CollaboratorId::Agent, window, cx);
})
.register_action(|workspace, _: &OpenAgentDiff, window, cx| {
let thread = workspace
.panel::<AgentPanel>(cx)
.and_then(|panel| panel.read(cx).active_thread_view().cloned())
.and_then(|thread_view| thread_view.read(cx).thread().cloned());
if let Some(thread) = thread {
AgentDiffPane::deploy_in_workspace(thread, workspace, window, cx);
}
})
.register_action(|workspace, _: &ToggleNavigationMenu, window, cx| {
if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
workspace.focus_panel::<AgentPanel>(window, cx);
@@ -222,11 +212,12 @@ enum WhichFontSize {
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
pub enum AgentType {
#[default]
NativeAgent,
Zed,
TextThread,
Gemini,
ClaudeCode,
Codex,
NativeAgent,
Custom {
name: SharedString,
command: AgentServerCommand,
@@ -236,7 +227,8 @@ pub enum AgentType {
impl AgentType {
fn label(&self) -> SharedString {
match self {
Self::NativeAgent | Self::TextThread => "Zed Agent".into(),
Self::Zed | Self::TextThread => "Zed Agent".into(),
Self::NativeAgent => "Agent 2".into(),
Self::Gemini => "Gemini CLI".into(),
Self::ClaudeCode => "Claude Code".into(),
Self::Codex => "Codex".into(),
@@ -246,7 +238,7 @@ impl AgentType {
fn icon(&self) -> Option<IconName> {
match self {
Self::NativeAgent | Self::TextThread => None,
Self::Zed | Self::NativeAgent | Self::TextThread => None,
Self::Gemini => Some(IconName::AiGemini),
Self::ClaudeCode => Some(IconName::AiClaude),
Self::Codex => Some(IconName::AiOpenAi),
@@ -306,6 +298,7 @@ impl ActiveView {
pub fn prompt_editor(
context_editor: Entity<TextThreadEditor>,
history_store: Entity<HistoryStore>,
acp_history_store: Entity<agent2::HistoryStore>,
language_registry: Arc<LanguageRegistry>,
window: &mut Window,
@@ -373,6 +366,18 @@ impl ActiveView {
})
}
ContextEvent::PathChanged { old_path, new_path } => {
history_store.update(cx, |history_store, cx| {
if let Some(old_path) = old_path {
history_store
.replace_recently_opened_text_thread(old_path, new_path, cx);
} else {
history_store.push_recently_opened_entry(
HistoryEntryId::Context(new_path.clone()),
cx,
);
}
});
acp_history_store.update(cx, |history_store, cx| {
if let Some(old_path) = old_path {
history_store
@@ -414,7 +419,7 @@ pub struct AgentPanel {
language_registry: Arc<LanguageRegistry>,
thread_store: Entity<ThreadStore>,
acp_history: Entity<AcpThreadHistory>,
history_store: Entity<agent2::HistoryStore>,
acp_history_store: Entity<agent2::HistoryStore>,
context_store: Entity<TextThreadStore>,
prompt_store: Option<Entity<PromptStore>>,
inline_assist_context_store: Entity<ContextStore>,
@@ -422,6 +427,7 @@ pub struct AgentPanel {
configuration_subscription: Option<Subscription>,
active_view: ActiveView,
previous_view: Option<ActiveView>,
history_store: Entity<HistoryStore>,
new_thread_menu_handle: PopoverMenuHandle<ContextMenu>,
agent_panel_menu_handle: PopoverMenuHandle<ContextMenu>,
assistant_navigation_menu_handle: PopoverMenuHandle<ContextMenu>,
@@ -554,8 +560,10 @@ impl AgentPanel {
let inline_assist_context_store =
cx.new(|_cx| ContextStore::new(project.downgrade(), Some(thread_store.downgrade())));
let history_store = cx.new(|cx| agent2::HistoryStore::new(context_store.clone(), cx));
let acp_history = cx.new(|cx| AcpThreadHistory::new(history_store.clone(), window, cx));
let history_store = cx.new(|cx| HistoryStore::new(context_store.clone(), [], cx));
let acp_history_store = cx.new(|cx| agent2::HistoryStore::new(context_store.clone(), cx));
let acp_history = cx.new(|cx| AcpThreadHistory::new(acp_history_store.clone(), window, cx));
cx.subscribe_in(
&acp_history,
window,
@@ -577,12 +585,14 @@ impl AgentPanel {
)
.detach();
cx.observe(&history_store, |_, _, cx| cx.notify()).detach();
let panel_type = AgentSettings::get_global(cx).default_view;
let active_view = match panel_type {
DefaultView::Thread => ActiveView::native_agent(
fs.clone(),
prompt_store.clone(),
history_store.clone(),
acp_history_store.clone(),
project.clone(),
workspace.clone(),
window,
@@ -608,6 +618,7 @@ impl AgentPanel {
ActiveView::prompt_editor(
context_editor,
history_store.clone(),
acp_history_store.clone(),
language_registry.clone(),
window,
cx,
@@ -673,6 +684,7 @@ impl AgentPanel {
configuration_subscription: None,
inline_assist_context_store,
previous_view: None,
history_store: history_store.clone(),
new_thread_menu_handle: PopoverMenuHandle::default(),
agent_panel_menu_handle: PopoverMenuHandle::default(),
assistant_navigation_menu_handle: PopoverMenuHandle::default(),
@@ -683,7 +695,7 @@ impl AgentPanel {
pending_serialization: None,
onboarding,
acp_history,
history_store,
acp_history_store,
selected_agent: AgentType::default(),
loading: false,
}
@@ -737,7 +749,7 @@ impl AgentPanel {
cx: &mut Context<Self>,
) {
let Some(thread) = self
.history_store
.acp_history_store
.read(cx)
.thread_from_session_id(&action.from_session_id)
else {
@@ -786,6 +798,7 @@ impl AgentPanel {
ActiveView::prompt_editor(
context_editor.clone(),
self.history_store.clone(),
self.acp_history_store.clone(),
self.language_registry.clone(),
window,
cx,
@@ -811,13 +824,13 @@ impl AgentPanel {
const LAST_USED_EXTERNAL_AGENT_KEY: &str = "agent_panel__last_used_external_agent";
#[derive(Serialize, Deserialize)]
#[derive(Default, Serialize, Deserialize)]
struct LastUsedExternalAgent {
agent: crate::ExternalAgent,
}
let loading = self.loading;
let history = self.history_store.clone();
let history = self.acp_history_store.clone();
cx.spawn_in(window, async move |this, cx| {
let ext_agent = match agent_choice {
@@ -852,18 +865,18 @@ impl AgentPanel {
.and_then(|value| {
serde_json::from_str::<LastUsedExternalAgent>(&value).log_err()
})
.map(|agent| agent.agent)
.unwrap_or(ExternalAgent::NativeAgent)
.unwrap_or_default()
.agent
}
}
};
let server = ext_agent.server(fs, history);
if !loading {
telemetry::event!("Agent Thread Started", agent = server.telemetry_id());
telemetry::event!("Agent Thread Started", agent = ext_agent.name());
}
let server = ext_agent.server(fs, history);
this.update_in(cx, |this, window, cx| {
let selected_agent = ext_agent.into();
if this.selected_agent != selected_agent {
@@ -878,7 +891,7 @@ impl AgentPanel {
summarize_thread,
workspace.clone(),
project,
this.history_store.clone(),
this.acp_history_store.clone(),
this.prompt_store.clone(),
window,
cx,
@@ -976,6 +989,7 @@ impl AgentPanel {
ActiveView::prompt_editor(
editor,
self.history_store.clone(),
self.acp_history_store.clone(),
self.language_registry.clone(),
window,
cx,
@@ -1239,6 +1253,11 @@ impl AgentPanel {
match &new_view {
ActiveView::TextThread { context_editor, .. } => {
self.history_store.update(cx, |store, cx| {
if let Some(path) = context_editor.read(cx).context().read(cx).path() {
store.push_recently_opened_entry(HistoryEntryId::Context(path.clone()), cx)
}
});
self.acp_history_store.update(cx, |store, cx| {
if let Some(path) = context_editor.read(cx).context().read(cx).path() {
store.push_recently_opened_entry(
agent2::HistoryEntryId::TextThread(path.clone()),
@@ -1272,7 +1291,7 @@ impl AgentPanel {
) -> ContextMenu {
let entries = panel
.read(cx)
.history_store
.acp_history_store
.read(cx)
.recently_opened_entries(cx);
@@ -1317,7 +1336,7 @@ impl AgentPanel {
move |_window, cx| {
panel
.update(cx, |this, cx| {
this.history_store.update(cx, |history_store, cx| {
this.acp_history_store.update(cx, |history_store, cx| {
history_store.remove_recently_opened_entry(&id, cx);
});
})
@@ -1343,6 +1362,15 @@ impl AgentPanel {
cx: &mut Context<Self>,
) {
match agent {
AgentType::Zed => {
window.dispatch_action(
NewThread {
from_thread_id: None,
}
.boxed_clone(),
cx,
);
}
AgentType::TextThread => {
window.dispatch_action(NewTextThread.boxed_clone(), cx);
}
@@ -2139,7 +2167,10 @@ impl AgentPanel {
false
}
_ => {
let history_is_empty = self.history_store.read(cx).is_empty(cx);
let history_is_empty = self.acp_history_store.read(cx).is_empty(cx)
&& self
.history_store
.update(cx, |store, cx| store.recent_entries(1, cx).is_empty());
let has_configured_non_zed_providers = LanguageModelRegistry::read_global(cx)
.providers()

View File

@@ -161,9 +161,10 @@ pub struct NewNativeAgentThreadFromSummary {
}
// TODO unify this with AgentType
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExternalAgent {
enum ExternalAgent {
#[default]
Gemini,
ClaudeCode,
Codex,
@@ -183,13 +184,13 @@ fn placeholder_command() -> AgentServerCommand {
}
impl ExternalAgent {
pub fn parse_built_in(server: &dyn agent_servers::AgentServer) -> Option<Self> {
match server.telemetry_id() {
"gemini-cli" => Some(Self::Gemini),
"claude-code" => Some(Self::ClaudeCode),
"codex" => Some(Self::Codex),
"zed" => Some(Self::NativeAgent),
_ => None,
fn name(&self) -> &'static str {
match self {
Self::NativeAgent => "zed",
Self::Gemini => "gemini-cli",
Self::ClaudeCode => "claude-code",
Self::Codex => "codex",
Self::Custom { .. } => "custom",
}
}

View File

@@ -11,7 +11,7 @@ use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{App, Entity, Task, WeakEntity};
use http_client::HttpClientWithUrl;
use itertools::Itertools;
use language::{Buffer, CodeLabel, CodeLabelBuilder, HighlightId};
use language::{Buffer, CodeLabel, HighlightId};
use lsp::CompletionContext;
use project::lsp_store::SymbolLocation;
use project::{
@@ -686,8 +686,7 @@ impl ContextPickerCompletionProvider {
};
let comment_id = cx.theme().syntax().highlight_id("comment").map(HighlightId);
let mut label = CodeLabelBuilder::default();
label.push_str(&symbol.name, None);
let mut label = CodeLabel::plain(symbol.name.clone(), None);
label.push_str(" ", None);
label.push_str(&file_name, comment_id);
label.push_str(&format!(" L{}", symbol.range.start.0.row + 1), comment_id);
@@ -697,7 +696,7 @@ impl ContextPickerCompletionProvider {
Some(Completion {
replace_range: source_range.clone(),
new_text,
label: label.build(),
label,
documentation: None,
source: project::CompletionSource::Custom,
icon_path: Some(IconName::Code.path().into()),
@@ -730,7 +729,7 @@ impl ContextPickerCompletionProvider {
fn build_code_label_for_full_path(file_name: &str, directory: Option<&str>, cx: &App) -> CodeLabel {
let comment_id = cx.theme().syntax().highlight_id("comment").map(HighlightId);
let mut label = CodeLabelBuilder::default();
let mut label = CodeLabel::default();
label.push_str(file_name, None);
label.push_str(" ", None);
@@ -739,7 +738,9 @@ fn build_code_label_for_full_path(file_name: &str, directory: Option<&str>, cx:
label.push_str(directory, comment_id);
}
label.build()
label.filter_range = 0..label.text().len();
label
}
impl CompletionProvider for ContextPickerCompletionProvider {

View File

@@ -144,16 +144,10 @@ impl Render for ProfileSelector {
.unwrap_or_else(|| "Unknown".into());
let focus_handle = self.focus_handle.clone();
let icon = if self.picker_handle.is_deployed() {
IconName::ChevronUp
} else {
IconName::ChevronDown
};
let trigger_button = Button::new("profile-selector", selected_profile)
.label_size(LabelSize::Small)
.color(Color::Muted)
.icon(icon)
.icon(IconName::ChevronDown)
.icon_size(IconSize::XSmall)
.icon_position(IconPosition::End)
.icon_color(Color::Muted)

View File

@@ -1977,9 +1977,7 @@ impl TextThreadEditor {
cx.entity().downgrade(),
IconButton::new("trigger", IconName::Plus)
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
.selected_icon_color(Color::Accent)
.selected_style(ButtonStyle::Filled),
.icon_color(Color::Muted),
move |window, cx| {
Tooltip::with_meta(
"Add Context",
@@ -2054,27 +2052,30 @@ impl TextThreadEditor {
};
let focus_handle = self.editor().focus_handle(cx);
let (color, icon) = if self.language_model_selector_menu_handle.is_deployed() {
(Color::Accent, IconName::ChevronUp)
} else {
(Color::Muted, IconName::ChevronDown)
};
PickerPopoverMenu::new(
self.language_model_selector.clone(),
ButtonLike::new("active-model")
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
.style(ButtonStyle::Subtle)
.child(
h_flex()
.gap_0p5()
.child(Icon::new(provider_icon).color(color).size(IconSize::XSmall))
.child(
Icon::new(provider_icon)
.color(Color::Muted)
.size(IconSize::XSmall),
)
.child(
Label::new(model_name)
.color(color)
.color(Color::Muted)
.size(LabelSize::Small)
.ml_0p5(),
)
.child(Icon::new(icon).color(color).size(IconSize::XSmall)),
.child(
Icon::new(IconName::ChevronDown)
.color(Color::Muted)
.size(IconSize::XSmall),
),
),
move |window, cx| {
Tooltip::for_action_in(
@@ -2085,7 +2086,7 @@ impl TextThreadEditor {
cx,
)
},
gpui::Corner::BottomRight,
gpui::Corner::BottomLeft,
cx,
)
.with_handle(self.language_model_selector_menu_handle.clone())

View File

@@ -9,7 +9,6 @@ use anyhow::Result;
use futures::StreamExt;
use futures::stream::{self, BoxStream};
use gpui::{App, SharedString, Task, WeakEntity, Window};
use language::CodeLabelBuilder;
use language::HighlightId;
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate, OffsetRangeExt};
pub use language_model::Role;
@@ -329,15 +328,15 @@ impl SlashCommandLine {
}
pub fn create_label_for_command(command_name: &str, arguments: &[&str], cx: &App) -> CodeLabel {
let mut label = CodeLabelBuilder::default();
let mut label = CodeLabel::default();
label.push_str(command_name, None);
label.respan_filter_range(None);
label.push_str(" ", None);
label.push_str(
&arguments.join(" "),
cx.theme().syntax().highlight_id("comment").map(HighlightId),
);
label.build()
label.filter_range = 0..command_name.len();
label
}
#[cfg(test)]

View File

@@ -7,7 +7,7 @@ use futures::Stream;
use futures::channel::mpsc;
use fuzzy::PathMatch;
use gpui::{App, Entity, Task, WeakEntity};
use language::{BufferSnapshot, CodeLabelBuilder, HighlightId, LineEnding, LspAdapterDelegate};
use language::{BufferSnapshot, CodeLabel, HighlightId, LineEnding, LspAdapterDelegate};
use project::{PathMatchCandidateSet, Project};
use serde::{Deserialize, Serialize};
use smol::stream::StreamExt;
@@ -168,7 +168,7 @@ impl SlashCommand for FileSlashCommand {
.display(path_style)
.to_string();
let mut label = CodeLabelBuilder::default();
let mut label = CodeLabel::default();
let file_name = path_match.path.file_name()?;
let label_text = if path_match.is_dir {
format!("{}/ ", file_name)
@@ -178,10 +178,10 @@ impl SlashCommand for FileSlashCommand {
label.push_str(label_text.as_str(), None);
label.push_str(&text, comment_id);
label.respan_filter_range(Some(file_name));
label.filter_range = 0..file_name.len();
Some(ArgumentCompletion {
label: label.build(),
label,
new_text: text,
after_completion: AfterCompletion::Compose,
replace_previous_arguments: false,

View File

@@ -7,7 +7,7 @@ use collections::{HashMap, HashSet};
use editor::Editor;
use futures::future::join_all;
use gpui::{Task, WeakEntity};
use language::{BufferSnapshot, CodeLabel, CodeLabelBuilder, HighlightId, LspAdapterDelegate};
use language::{BufferSnapshot, CodeLabel, HighlightId, LspAdapterDelegate};
use std::sync::{Arc, atomic::AtomicBool};
use ui::{ActiveTheme, App, Window, prelude::*};
use util::{ResultExt, paths::PathStyle};
@@ -308,10 +308,10 @@ fn create_tab_completion_label(
comment_id: Option<HighlightId>,
) -> CodeLabel {
let (parent_path, file_name) = path_style.split(path);
let mut label = CodeLabelBuilder::default();
let mut label = CodeLabel::default();
label.push_str(file_name, None);
label.push_str(" ", None);
label.push_str(parent_path.unwrap_or_default(), comment_id);
label.respan_filter_range(Some(file_name));
label.build()
label.filter_range = 0..file_name.len();
label
}

View File

@@ -1538,7 +1538,7 @@ mod tests {
store.update_user_settings(cx, |settings| {
settings.project.all_languages.defaults.format_on_save = Some(FormatOnSave::On);
settings.project.all_languages.defaults.formatter =
Some(language::language_settings::FormatterList::default());
Some(language::language_settings::SelectedFormatter::Auto);
});
});
});

View File

@@ -136,7 +136,6 @@ impl Tool for TerminalTool {
}),
None => Task::ready(None).shared(),
};
let is_windows = project.read(cx).path_style(cx).is_windows();
let shell = project
.update(cx, |project, cx| {
project
@@ -156,7 +155,7 @@ impl Tool for TerminalTool {
let build_cmd = {
let input_command = input.command.clone();
move || {
ShellBuilder::new(&Shell::Program(shell), is_windows)
ShellBuilder::new(&Shell::Program(shell))
.redirect_stdin_to_dev_null()
.build(Some(input_command), &[])
}
@@ -705,6 +704,7 @@ mod tests {
use serde_json::json;
use settings::{Settings, SettingsStore};
use terminal::terminal_settings::TerminalSettings;
use theme::ThemeSettings;
use util::{ResultExt as _, test::TempTree};
use super::*;
@@ -719,7 +719,7 @@ mod tests {
language::init(cx);
Project::init_settings(cx);
workspace::init_settings(cx);
theme::init(theme::LoadThemes::JustBase, cx);
ThemeSettings::register(cx);
TerminalSettings::register(cx);
EditorSettings::register(cx);
});

View File

@@ -1,12 +1,12 @@
use anyhow::{Context as _, Result};
use collections::HashMap;
use gpui::{App, BackgroundExecutor, BorrowAppContext, Global};
use log::info;
#[cfg(not(any(all(target_os = "windows", target_env = "gnu"), target_os = "freebsd")))]
mod non_windows_and_freebsd_deps {
pub(super) use gpui::AsyncApp;
pub(super) use libwebrtc::native::apm;
pub(super) use log::info;
pub(super) use parking_lot::Mutex;
pub(super) use rodio::cpal::Sample;
pub(super) use rodio::source::LimitSettings;

View File

@@ -42,7 +42,7 @@ pub struct AudioSettings {
/// Configuration of audio in Zed
impl Settings for AudioSettings {
fn from_settings(content: &settings::SettingsContent) -> Self {
fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self {
let audio = &content.audio.as_ref().unwrap();
AudioSettings {
rodio_audio: audio.rodio_audio.unwrap(),

View File

@@ -127,7 +127,7 @@ struct AutoUpdateSetting(bool);
///
/// Default: true
impl Settings for AutoUpdateSetting {
fn from_settings(content: &settings::SettingsContent) -> Self {
fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self {
Self(content.auto_update.unwrap())
}
}
@@ -649,7 +649,7 @@ impl AutoUpdater {
#[cfg(not(target_os = "windows"))]
anyhow::ensure!(
which::which("rsync").is_ok(),
"Could not auto-update because the required rsync utility was not found."
"Aborting. Could not find rsync which is required for auto-updates."
);
Ok(())
}

View File

@@ -1,3 +1,4 @@
use gpui::App;
use settings::Settings;
#[derive(Debug)]
@@ -7,11 +8,17 @@ pub struct CallSettings {
}
impl Settings for CallSettings {
fn from_settings(content: &settings::SettingsContent) -> Self {
fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self {
let call = content.calls.clone().unwrap();
CallSettings {
mute_on_join: call.mute_on_join.unwrap(),
share_on_join: call.share_on_join.unwrap(),
}
}
fn import_from_vscode(
_vscode: &settings::VsCodeSettings,
_current: &mut settings::SettingsContent,
) {
}
}

View File

@@ -68,13 +68,10 @@ struct Args {
#[arg(short, long, overrides_with = "add")]
new: bool,
/// Sets a custom directory for all user data (e.g., database, extensions, logs).
/// This overrides the default platform-specific data directory location:
#[cfg_attr(target_os = "macos", doc = "`~/Library/Application Support/Zed`.")]
#[cfg_attr(target_os = "windows", doc = "`%LOCALAPPDATA%\\Zed`.")]
#[cfg_attr(
not(any(target_os = "windows", target_os = "macos")),
doc = "`$XDG_DATA_HOME/zed`."
)]
/// This overrides the default platform-specific data directory location.
/// On macOS, the default is `~/Library/Application Support/Zed`.
/// On Linux/FreeBSD, the default is `$XDG_DATA_HOME/zed`.
/// On Windows, the default is `%LOCALAPPDATA%\Zed`.
#[arg(long, value_name = "DIR")]
user_data_dir: Option<String>,
/// The paths to open in Zed (space-separated).

View File

@@ -101,7 +101,7 @@ pub struct ClientSettings {
}
impl Settings for ClientSettings {
fn from_settings(content: &settings::SettingsContent) -> Self {
fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self {
if let Some(server_url) = &*ZED_SERVER_URL {
return Self {
server_url: server_url.clone(),
@@ -133,7 +133,7 @@ impl ProxySettings {
}
impl Settings for ProxySettings {
fn from_settings(content: &settings::SettingsContent) -> Self {
fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self {
Self {
proxy: content.proxy.clone(),
}
@@ -519,7 +519,7 @@ pub struct TelemetrySettings {
}
impl settings::Settings for TelemetrySettings {
fn from_settings(content: &SettingsContent) -> Self {
fn from_settings(content: &SettingsContent, _cx: &mut App) -> Self {
Self {
diagnostics: content.telemetry.as_ref().unwrap().diagnostics.unwrap(),
metrics: content.telemetry.as_ref().unwrap().metrics.unwrap(),

View File

@@ -322,9 +322,6 @@ pub struct LanguageModel {
pub supports_images: bool,
pub supports_thinking: bool,
pub supports_max_mode: bool,
// only used by OpenAI and xAI
#[serde(default)]
pub supports_parallel_tool_calls: bool,
}
#[derive(Debug, Serialize, Deserialize)]

View File

@@ -1,7 +1,7 @@
use chrono::Duration;
use serde::{Deserialize, Serialize};
use std::{
ops::{Add, Range, Sub},
ops::Range,
path::{Path, PathBuf},
sync::Arc,
};
@@ -18,8 +18,8 @@ pub struct PredictEditsRequest {
pub excerpt_path: Arc<Path>,
/// Within file
pub excerpt_range: Range<usize>,
pub excerpt_line_range: Range<Line>,
pub cursor_point: Point,
/// Within `excerpt`
pub cursor_offset: usize,
/// Within `signatures`
pub excerpt_parent: Option<usize>,
pub signatures: Vec<Signature>,
@@ -47,13 +47,12 @@ pub struct PredictEditsRequest {
pub enum PromptFormat {
MarkedExcerpt,
LabeledSections,
NumLinesUniDiff,
/// Prompt format intended for use via zeta_cli
OnlySnippets,
}
impl PromptFormat {
pub const DEFAULT: PromptFormat = PromptFormat::NumLinesUniDiff;
pub const DEFAULT: PromptFormat = PromptFormat::LabeledSections;
}
impl Default for PromptFormat {
@@ -74,7 +73,6 @@ impl std::fmt::Display for PromptFormat {
PromptFormat::MarkedExcerpt => write!(f, "Marked Excerpt"),
PromptFormat::LabeledSections => write!(f, "Labeled Sections"),
PromptFormat::OnlySnippets => write!(f, "Only Snippets"),
PromptFormat::NumLinesUniDiff => write!(f, "Numbered Lines / Unified Diff"),
}
}
}
@@ -99,7 +97,7 @@ pub struct Signature {
pub parent_index: Option<usize>,
/// Range of `text` within the file, possibly truncated according to `text_is_truncated`. The
/// file is implicitly the file that contains the descendant declaration or excerpt.
pub range: Range<Line>,
pub range: Range<usize>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -108,7 +106,7 @@ pub struct ReferencedDeclaration {
pub text: String,
pub text_is_truncated: bool,
/// Range of `text` within file, possibly truncated according to `text_is_truncated`
pub range: Range<Line>,
pub range: Range<usize>,
/// Range within `text`
pub signature_range: Range<usize>,
/// Index within `signatures`.
@@ -129,6 +127,7 @@ pub struct DeclarationScoreComponents {
pub declaration_count: usize,
pub reference_line_distance: u32,
pub declaration_line_distance: u32,
pub declaration_line_distance_rank: usize,
pub excerpt_vs_item_jaccard: f32,
pub excerpt_vs_signature_jaccard: f32,
pub adjacent_vs_item_jaccard: f32,
@@ -137,15 +136,6 @@ pub struct DeclarationScoreComponents {
pub excerpt_vs_signature_weighted_overlap: f32,
pub adjacent_vs_item_weighted_overlap: f32,
pub adjacent_vs_signature_weighted_overlap: f32,
pub path_import_match_count: usize,
pub wildcard_path_import_match_count: usize,
pub import_similarity: f32,
pub max_import_similarity: f32,
pub normalized_import_similarity: f32,
pub wildcard_import_similarity: f32,
pub normalized_wildcard_import_similarity: f32,
pub included_by_others: usize,
pub includes_others: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -171,36 +161,10 @@ pub struct DebugInfo {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Edit {
pub path: Arc<Path>,
pub range: Range<Line>,
pub range: Range<usize>,
pub content: String,
}
fn is_default<T: Default + PartialEq>(value: &T) -> bool {
*value == T::default()
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, PartialOrd, Eq, Ord)]
pub struct Point {
pub line: Line,
pub column: u32,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, PartialOrd, Eq, Ord)]
#[serde(transparent)]
pub struct Line(pub u32);
impl Add for Line {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self(self.0 + rhs.0)
}
}
impl Sub for Line {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
Self(self.0 - rhs.0)
}
}

View File

@@ -1,9 +1,7 @@
//! Zeta2 prompt planning and generation code shared with cloud.
use anyhow::{Context as _, Result, anyhow};
use cloud_llm_client::predict_edits_v3::{
self, Event, Line, Point, PromptFormat, ReferencedDeclaration,
};
use cloud_llm_client::predict_edits_v3::{self, Event, PromptFormat, ReferencedDeclaration};
use indoc::indoc;
use ordered_float::OrderedFloat;
use rustc_hash::{FxHashMap, FxHashSet};
@@ -15,30 +13,27 @@ use strum::{EnumIter, IntoEnumIterator};
pub const DEFAULT_MAX_PROMPT_BYTES: usize = 10 * 1024;
pub const CURSOR_MARKER: &str = "<|user_cursor|>";
pub const CURSOR_MARKER: &str = "<|cursor_position|>";
/// NOTE: Differs from zed version of constant - includes a newline
pub const EDITABLE_REGION_START_MARKER_WITH_NEWLINE: &str = "<|editable_region_start|>\n";
/// NOTE: Differs from zed version of constant - includes a newline
pub const EDITABLE_REGION_END_MARKER_WITH_NEWLINE: &str = "<|editable_region_end|>\n";
// TODO: use constants for markers?
const MARKED_EXCERPT_INSTRUCTIONS: &str = indoc! {"
const MARKED_EXCERPT_SYSTEM_PROMPT: &str = indoc! {"
You are a code completion assistant and your task is to analyze user edits and then rewrite an excerpt that the user provides, suggesting the appropriate edits within the excerpt, taking into account the cursor location.
The excerpt to edit will be wrapped in markers <|editable_region_start|> and <|editable_region_end|>. The cursor position is marked with <|user_cursor|>. Please respond with edited code for that region.
The excerpt to edit will be wrapped in markers <|editable_region_start|> and <|editable_region_end|>. The cursor position is marked with <|cursor_position|>. Please respond with edited code for that region.
Other code is provided for context, and `…` indicates when code has been skipped.
# Edit History:
"};
const LABELED_SECTIONS_INSTRUCTIONS: &str = indoc! {r#"
const LABELED_SECTIONS_SYSTEM_PROMPT: &str = indoc! {r#"
You are a code completion assistant and your task is to analyze user edits, and suggest an edit to one of the provided sections of code.
Sections of code are grouped by file and then labeled by `<|section_N|>` (e.g `<|section_8|>`).
The cursor position is marked with `<|user_cursor|>` and it will appear within a special section labeled `<|current_section|>`. Prefer editing the current section until no more changes are needed within it.
The cursor position is marked with `<|cursor_position|>` and it will appear within a special section labeled `<|current_section|>`. Prefer editing the current section until no more changes are needed within it.
Respond ONLY with the name of the section to edit on a single line, followed by all of the code that should replace that section. For example:
@@ -46,58 +41,8 @@ const LABELED_SECTIONS_INSTRUCTIONS: &str = indoc! {r#"
for i in 0..16 {
println!("{i}");
}
# Edit History:
"#};
const NUMBERED_LINES_INSTRUCTIONS: &str = indoc! {r#"
# Instructions
You are a code completion assistant helping a programmer finish their work. Your task is to:
1. Analyze the edit history to understand what the programmer is trying to achieve
2. Identify any incomplete refactoring or changes that need to be finished
3. Make the remaining edits that a human programmer would logically make next
4. Apply systematic changes consistently across the entire codebase - if you see a pattern starting, complete it everywhere.
Focus on:
- Understanding the intent behind the changes (e.g., improving error handling, refactoring APIs, fixing bugs)
- Completing any partially-applied changes across the codebase
- Ensuring consistency with the programming style and patterns already established
- Making edits that maintain or improve code quality
- If the programmer started refactoring one instance of a pattern, find and update ALL similar instances
- Don't write a lot of code if you're not sure what to do
Rules:
- Do not just mechanically apply patterns - reason about what changes make sense given the context and the programmer's apparent goals.
- Do not just fix syntax errors - look for the broader refactoring pattern and apply it systematically throughout the code.
- Write the edits in the unified diff format as shown in the example.
# Example output:
```
--- a/src/myapp/cli.py
+++ b/src/myapp/cli.py
@@ -1,3 +1,3 @@
-
-
-import sys
+import json
```
# Edit History:
"#};
const UNIFIED_DIFF_REMINDER: &str = indoc! {"
---
Please analyze the edit history and the files, then provide the unified diff for your predicted edits.
Do not include the cursor marker in your output.
If you're editing multiple files, be sure to reflect filename in the hunk's header.
"};
pub struct PlannedPrompt<'a> {
request: &'a predict_edits_v3::PredictEditsRequest,
/// Snippets to include in the prompt. These may overlap - they are merged / deduplicated in
@@ -106,10 +51,19 @@ pub struct PlannedPrompt<'a> {
budget_used: usize,
}
pub fn system_prompt(format: PromptFormat) -> &'static str {
match format {
PromptFormat::MarkedExcerpt => MARKED_EXCERPT_SYSTEM_PROMPT,
PromptFormat::LabeledSections => LABELED_SECTIONS_SYSTEM_PROMPT,
// only intended for use via zeta_cli
PromptFormat::OnlySnippets => "",
}
}
#[derive(Clone, Debug)]
pub struct PlannedSnippet<'a> {
path: Arc<Path>,
range: Range<Line>,
range: Range<usize>,
text: &'a str,
// TODO: Indicate this in the output
#[allow(dead_code)]
@@ -125,7 +79,7 @@ pub enum DeclarationStyle {
#[derive(Clone, Debug, Serialize)]
pub struct SectionLabels {
pub excerpt_index: usize,
pub section_ranges: Vec<(Arc<Path>, Range<Line>)>,
pub section_ranges: Vec<(Arc<Path>, Range<usize>)>,
}
impl<'a> PlannedPrompt<'a> {
@@ -242,24 +196,10 @@ impl<'a> PlannedPrompt<'a> {
declaration.text.len()
));
};
let signature_start_line = declaration.range.start
+ Line(
declaration.text[..declaration.signature_range.start]
.lines()
.count() as u32,
);
let signature_end_line = signature_start_line
+ Line(
declaration.text
[declaration.signature_range.start..declaration.signature_range.end]
.lines()
.count() as u32,
);
let range = signature_start_line..signature_end_line;
PlannedSnippet {
path: declaration.path.clone(),
range,
range: (declaration.signature_range.start + declaration.range.start)
..(declaration.signature_range.end + declaration.range.start),
text,
text_is_truncated: declaration.text_is_truncated,
}
@@ -378,7 +318,7 @@ impl<'a> PlannedPrompt<'a> {
}
let excerpt_snippet = PlannedSnippet {
path: self.request.excerpt_path.clone(),
range: self.request.excerpt_line_range.clone(),
range: self.request.excerpt_range.clone(),
text: &self.request.excerpt,
text_is_truncated: false,
};
@@ -388,79 +328,36 @@ impl<'a> PlannedPrompt<'a> {
let mut excerpt_file_insertions = match self.request.prompt_format {
PromptFormat::MarkedExcerpt => vec![
(
Point {
line: self.request.excerpt_line_range.start,
column: 0,
},
self.request.excerpt_range.start,
EDITABLE_REGION_START_MARKER_WITH_NEWLINE,
),
(self.request.cursor_point, CURSOR_MARKER),
(
Point {
line: self.request.excerpt_line_range.end,
column: 0,
},
self.request.excerpt_range.start + self.request.cursor_offset,
CURSOR_MARKER,
),
(
self.request
.excerpt_range
.end
.saturating_sub(0)
.max(self.request.excerpt_range.start),
EDITABLE_REGION_END_MARKER_WITH_NEWLINE,
),
],
PromptFormat::LabeledSections => vec![(self.request.cursor_point, CURSOR_MARKER)],
PromptFormat::NumLinesUniDiff => {
vec![(self.request.cursor_point, CURSOR_MARKER)]
}
PromptFormat::LabeledSections => vec![(
self.request.excerpt_range.start + self.request.cursor_offset,
CURSOR_MARKER,
)],
PromptFormat::OnlySnippets => vec![],
};
let mut prompt = match self.request.prompt_format {
PromptFormat::MarkedExcerpt => MARKED_EXCERPT_INSTRUCTIONS.to_string(),
PromptFormat::LabeledSections => LABELED_SECTIONS_INSTRUCTIONS.to_string(),
PromptFormat::NumLinesUniDiff => NUMBERED_LINES_INSTRUCTIONS.to_string(),
// only intended for use via zeta_cli
PromptFormat::OnlySnippets => String::new(),
};
if self.request.events.is_empty() {
prompt.push_str("No edits yet.\n\n");
} else {
prompt.push_str(
"The following are the latest edits made by the user, from earlier to later.\n\n",
);
Self::push_events(&mut prompt, &self.request.events);
}
if self.request.prompt_format == PromptFormat::NumLinesUniDiff {
if self.request.referenced_declarations.is_empty() {
prompt.push_str(indoc! {"
# File under the cursor:
The cursor marker <|user_cursor|> indicates the current user cursor position.
The file is in current state, edits from edit history have been applied.
We prepend line numbers (e.g., `123|<actual line>`); they are not part of the file.
"});
} else {
// Note: This hasn't been trained on yet
prompt.push_str(indoc! {"
# Code Excerpts:
The cursor marker <|user_cursor|> indicates the current user cursor position.
Other excerpts of code from the project have been included as context based on their similarity to the code under the cursor.
Context excerpts are not guaranteed to be relevant, so use your own judgement.
Files are in their current state, edits from edit history have been applied.
We prepend line numbers (e.g., `123|<actual line>`); they are not part of the file.
"});
}
} else {
prompt.push_str("\n## Code\n\n");
}
let mut prompt = String::new();
prompt.push_str("## User Edits\n\n");
Self::push_events(&mut prompt, &self.request.events);
prompt.push_str("\n## Code\n\n");
let section_labels =
self.push_file_snippets(&mut prompt, &mut excerpt_file_insertions, file_snippets)?;
if self.request.prompt_format == PromptFormat::NumLinesUniDiff {
prompt.push_str(UNIFIED_DIFF_REMINDER);
}
Ok((prompt, section_labels))
}
@@ -494,17 +391,13 @@ impl<'a> PlannedPrompt<'a> {
if *predicted {
writeln!(
output,
"User accepted prediction {:?}:\n`````diff\n{}\n`````\n",
"User accepted prediction {:?}:\n```diff\n{}\n```\n",
path, diff
)
.unwrap();
} else {
writeln!(
output,
"User edited {:?}:\n`````diff\n{}\n`````\n",
path, diff
)
.unwrap();
writeln!(output, "User edited {:?}:\n```diff\n{}\n```\n", path, diff)
.unwrap();
}
}
}
@@ -514,7 +407,7 @@ impl<'a> PlannedPrompt<'a> {
fn push_file_snippets(
&self,
output: &mut String,
excerpt_file_insertions: &mut Vec<(Point, &'static str)>,
excerpt_file_insertions: &mut Vec<(usize, &'static str)>,
file_snippets: Vec<(&'a Path, Vec<&'a PlannedSnippet>, bool)>,
) -> Result<SectionLabels> {
let mut section_ranges = Vec::new();
@@ -524,13 +417,15 @@ impl<'a> PlannedPrompt<'a> {
snippets.sort_by_key(|s| (s.range.start, Reverse(s.range.end)));
// TODO: What if the snippets get expanded too large to be editable?
let mut current_snippet: Option<(&PlannedSnippet, Range<Line>)> = None;
let mut disjoint_snippets: Vec<(&PlannedSnippet, Range<Line>)> = Vec::new();
let mut current_snippet: Option<(&PlannedSnippet, Range<usize>)> = None;
let mut disjoint_snippets: Vec<(&PlannedSnippet, Range<usize>)> = Vec::new();
for snippet in snippets {
if let Some((_, current_snippet_range)) = current_snippet.as_mut()
&& snippet.range.start <= current_snippet_range.end
&& snippet.range.start < current_snippet_range.end
{
current_snippet_range.end = current_snippet_range.end.max(snippet.range.end);
if snippet.range.end > current_snippet_range.end {
current_snippet_range.end = snippet.range.end;
}
continue;
}
if let Some(current_snippet) = current_snippet.take() {
@@ -542,24 +437,21 @@ impl<'a> PlannedPrompt<'a> {
disjoint_snippets.push(current_snippet);
}
// TODO: remove filename=?
writeln!(output, "`````filename={}", file_path.display()).ok();
writeln!(output, "```{}", file_path.display()).ok();
let mut skipped_last_snippet = false;
for (snippet, range) in disjoint_snippets {
let section_index = section_ranges.len();
match self.request.prompt_format {
PromptFormat::MarkedExcerpt
| PromptFormat::OnlySnippets
| PromptFormat::NumLinesUniDiff => {
if range.start.0 > 0 && !skipped_last_snippet {
PromptFormat::MarkedExcerpt | PromptFormat::OnlySnippets => {
if range.start > 0 && !skipped_last_snippet {
output.push_str("\n");
}
}
PromptFormat::LabeledSections => {
if is_excerpt_file
&& range.start <= self.request.excerpt_line_range.start
&& range.end >= self.request.excerpt_line_range.end
&& range.start <= self.request.excerpt_range.start
&& range.end >= self.request.excerpt_range.end
{
writeln!(output, "<|current_section|>").ok();
} else {
@@ -568,83 +460,46 @@ impl<'a> PlannedPrompt<'a> {
}
}
let push_full_snippet = |output: &mut String| {
if self.request.prompt_format == PromptFormat::NumLinesUniDiff {
for (i, line) in snippet.text.lines().enumerate() {
writeln!(output, "{}|{}", i as u32 + range.start.0 + 1, line)?;
}
} else {
output.push_str(&snippet.text);
}
anyhow::Ok(())
};
if is_excerpt_file {
if self.request.prompt_format == PromptFormat::OnlySnippets {
if range.start >= self.request.excerpt_line_range.start
&& range.end <= self.request.excerpt_line_range.end
if range.start >= self.request.excerpt_range.start
&& range.end <= self.request.excerpt_range.end
{
skipped_last_snippet = true;
} else {
skipped_last_snippet = false;
output.push_str(snippet.text);
}
} else if !excerpt_file_insertions.is_empty() {
let lines = snippet.text.lines().collect::<Vec<_>>();
let push_line = |output: &mut String, line_ix: usize| {
if self.request.prompt_format == PromptFormat::NumLinesUniDiff {
write!(output, "{}|", line_ix as u32 + range.start.0 + 1)?;
}
anyhow::Ok(writeln!(output, "{}", lines[line_ix])?)
};
let mut last_line_ix = 0;
let mut insertion_ix = 0;
while insertion_ix < excerpt_file_insertions.len() {
let (point, insertion) = &excerpt_file_insertions[insertion_ix];
let found = point.line >= range.start && point.line <= range.end;
} else {
let mut last_offset = range.start;
let mut i = 0;
while i < excerpt_file_insertions.len() {
let (offset, insertion) = &excerpt_file_insertions[i];
let found = *offset >= range.start && *offset <= range.end;
if found {
excerpt_index = Some(section_index);
let insertion_line_ix = (point.line.0 - range.start.0) as usize;
for line_ix in last_line_ix..insertion_line_ix {
push_line(output, line_ix)?;
}
if let Some(next_line) = lines.get(insertion_line_ix) {
if self.request.prompt_format == PromptFormat::NumLinesUniDiff {
write!(
output,
"{}|",
insertion_line_ix as u32 + range.start.0 + 1
)?
}
output.push_str(&next_line[..point.column as usize]);
output.push_str(insertion);
writeln!(output, "{}", &next_line[point.column as usize..])?;
} else {
writeln!(output, "{}", insertion)?;
}
last_line_ix = insertion_line_ix + 1;
excerpt_file_insertions.remove(insertion_ix);
output.push_str(
&snippet.text[last_offset - range.start..offset - range.start],
);
output.push_str(insertion);
last_offset = *offset;
excerpt_file_insertions.remove(i);
continue;
}
insertion_ix += 1;
i += 1;
}
skipped_last_snippet = false;
for line_ix in last_line_ix..lines.len() {
push_line(output, line_ix)?;
}
} else {
skipped_last_snippet = false;
push_full_snippet(output)?;
output.push_str(&snippet.text[last_offset - range.start..]);
}
} else {
skipped_last_snippet = false;
push_full_snippet(output)?;
output.push_str(snippet.text);
}
section_ranges.push((snippet.path.clone(), range));
}
output.push_str("`````\n\n");
output.push_str("```\n\n");
}
Ok(SectionLabels {

View File

@@ -1,28 +0,0 @@
[package]
name = "codestral"
version = "0.1.0"
edition = "2021"
publish = false
license = "GPL-3.0-or-later"
[lib]
path = "src/codestral.rs"
[dependencies]
anyhow.workspace = true
edit_prediction.workspace = true
edit_prediction_context.workspace = true
futures.workspace = true
gpui.workspace = true
http_client.workspace = true
language.workspace = true
language_models.workspace = true
log.workspace = true
mistral.workspace = true
serde.workspace = true
serde_json.workspace = true
smol.workspace = true
text.workspace = true
workspace-hack.workspace = true
[dev-dependencies]

View File

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

View File

@@ -1,381 +0,0 @@
use anyhow::{Context as _, Result};
use edit_prediction::{Direction, EditPrediction, EditPredictionProvider};
use edit_prediction_context::{EditPredictionExcerpt, EditPredictionExcerptOptions};
use futures::AsyncReadExt;
use gpui::{App, Context, Entity, Task};
use http_client::HttpClient;
use language::{
language_settings::all_language_settings, Anchor, Buffer, BufferSnapshot, EditPreview, ToPoint,
};
use language_models::MistralLanguageModelProvider;
use mistral::CODESTRAL_API_URL;
use serde::{Deserialize, Serialize};
use std::{
ops::Range,
sync::Arc,
time::{Duration, Instant},
};
use text::ToOffset;
pub const DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(150);
const EXCERPT_OPTIONS: EditPredictionExcerptOptions = EditPredictionExcerptOptions {
max_bytes: 1050,
min_bytes: 525,
target_before_cursor_over_total_bytes: 0.66,
};
/// Represents a completion that has been received and processed from Codestral.
/// This struct maintains the state needed to interpolate the completion as the user types.
#[derive(Clone)]
struct CurrentCompletion {
/// The buffer snapshot at the time the completion was generated.
/// Used to detect changes and interpolate edits.
snapshot: BufferSnapshot,
/// The edits that should be applied to transform the original text into the predicted text.
/// Each edit is a range in the buffer and the text to replace it with.
edits: Arc<[(Range<Anchor>, String)]>,
/// Preview of how the buffer will look after applying the edits.
edit_preview: EditPreview,
}
impl CurrentCompletion {
/// Attempts to adjust the edits based on changes made to the buffer since the completion was generated.
/// Returns None if the user's edits conflict with the predicted edits.
fn interpolate(&self, new_snapshot: &BufferSnapshot) -> Option<Vec<(Range<Anchor>, String)>> {
edit_prediction::interpolate_edits(&self.snapshot, new_snapshot, &self.edits)
}
}
pub struct CodestralCompletionProvider {
http_client: Arc<dyn HttpClient>,
pending_request: Option<Task<Result<()>>>,
current_completion: Option<CurrentCompletion>,
}
impl CodestralCompletionProvider {
pub fn new(http_client: Arc<dyn HttpClient>) -> Self {
Self {
http_client,
pending_request: None,
current_completion: None,
}
}
pub fn has_api_key(cx: &App) -> bool {
Self::api_key(cx).is_some()
}
fn api_key(cx: &App) -> Option<Arc<str>> {
MistralLanguageModelProvider::try_global(cx)
.and_then(|provider| provider.codestral_api_key(CODESTRAL_API_URL, cx))
}
/// Uses Codestral's Fill-in-the-Middle API for code completion.
async fn fetch_completion(
http_client: Arc<dyn HttpClient>,
api_key: &str,
prompt: String,
suffix: String,
model: String,
max_tokens: Option<u32>,
) -> Result<String> {
let start_time = Instant::now();
log::debug!(
"Codestral: Requesting completion (model: {}, max_tokens: {:?})",
model,
max_tokens
);
let request = CodestralRequest {
model,
prompt,
suffix: if suffix.is_empty() {
None
} else {
Some(suffix)
},
max_tokens: max_tokens.or(Some(350)),
temperature: Some(0.2),
top_p: Some(1.0),
stream: Some(false),
stop: None,
random_seed: None,
min_tokens: None,
};
let request_body = serde_json::to_string(&request)?;
log::debug!("Codestral: Sending FIM request");
let http_request = http_client::Request::builder()
.method(http_client::Method::POST)
.uri(format!("{}/v1/fim/completions", CODESTRAL_API_URL))
.header("Content-Type", "application/json")
.header("Authorization", format!("Bearer {}", api_key))
.body(http_client::AsyncBody::from(request_body))?;
let mut response = http_client.send(http_request).await?;
let status = response.status();
log::debug!("Codestral: Response status: {}", status);
if !status.is_success() {
let mut body = String::new();
response.body_mut().read_to_string(&mut body).await?;
return Err(anyhow::anyhow!(
"Codestral API error: {} - {}",
status,
body
));
}
let mut body = String::new();
response.body_mut().read_to_string(&mut body).await?;
let codestral_response: CodestralResponse = serde_json::from_str(&body)?;
let elapsed = start_time.elapsed();
if let Some(choice) = codestral_response.choices.first() {
let completion = &choice.message.content;
log::debug!(
"Codestral: Completion received ({} tokens, {:.2}s)",
codestral_response.usage.completion_tokens,
elapsed.as_secs_f64()
);
// Return just the completion text for insertion at cursor
Ok(completion.clone())
} else {
log::error!("Codestral: No completion returned in response");
Err(anyhow::anyhow!("No completion returned from Codestral"))
}
}
}
impl EditPredictionProvider for CodestralCompletionProvider {
fn name() -> &'static str {
"codestral"
}
fn display_name() -> &'static str {
"Codestral"
}
fn show_completions_in_menu() -> bool {
true
}
fn is_enabled(&self, _buffer: &Entity<Buffer>, _cursor_position: Anchor, cx: &App) -> bool {
Self::api_key(cx).is_some()
}
fn is_refreshing(&self) -> bool {
self.pending_request.is_some()
}
fn refresh(
&mut self,
buffer: Entity<Buffer>,
cursor_position: language::Anchor,
debounce: bool,
cx: &mut Context<Self>,
) {
log::debug!("Codestral: Refresh called (debounce: {})", debounce);
let Some(api_key) = Self::api_key(cx) else {
log::warn!("Codestral: No API key configured, skipping refresh");
return;
};
let snapshot = buffer.read(cx).snapshot();
// Check if current completion is still valid
if let Some(current_completion) = self.current_completion.as_ref() {
if current_completion.interpolate(&snapshot).is_some() {
return;
}
}
let http_client = self.http_client.clone();
// Get settings
let settings = all_language_settings(None, cx);
let model = settings
.edit_predictions
.codestral
.model
.clone()
.unwrap_or_else(|| "codestral-latest".to_string());
let max_tokens = settings.edit_predictions.codestral.max_tokens;
self.pending_request = Some(cx.spawn(async move |this, cx| {
if debounce {
log::debug!("Codestral: Debouncing for {:?}", DEBOUNCE_TIMEOUT);
smol::Timer::after(DEBOUNCE_TIMEOUT).await;
}
let cursor_offset = cursor_position.to_offset(&snapshot);
let cursor_point = cursor_offset.to_point(&snapshot);
let excerpt = EditPredictionExcerpt::select_from_buffer(
cursor_point,
&snapshot,
&EXCERPT_OPTIONS,
None,
)
.context("Line containing cursor doesn't fit in excerpt max bytes")?;
let excerpt_text = excerpt.text(&snapshot);
let cursor_within_excerpt = cursor_offset
.saturating_sub(excerpt.range.start)
.min(excerpt_text.body.len());
let prompt = excerpt_text.body[..cursor_within_excerpt].to_string();
let suffix = excerpt_text.body[cursor_within_excerpt..].to_string();
let completion_text = match Self::fetch_completion(
http_client,
&api_key,
prompt,
suffix,
model,
max_tokens,
)
.await
{
Ok(completion) => completion,
Err(e) => {
log::error!("Codestral: Failed to fetch completion: {}", e);
this.update(cx, |this, cx| {
this.pending_request = None;
cx.notify();
})?;
return Err(e);
}
};
if completion_text.trim().is_empty() {
log::debug!("Codestral: Completion was empty after trimming; ignoring");
this.update(cx, |this, cx| {
this.pending_request = None;
cx.notify();
})?;
return Ok(());
}
let edits: Arc<[(Range<Anchor>, String)]> =
vec![(cursor_position..cursor_position, completion_text)].into();
let edit_preview = buffer
.read_with(cx, |buffer, cx| buffer.preview_edits(edits.clone(), cx))?
.await;
this.update(cx, |this, cx| {
this.current_completion = Some(CurrentCompletion {
snapshot,
edits,
edit_preview,
});
this.pending_request = None;
cx.notify();
})?;
Ok(())
}));
}
fn cycle(
&mut self,
_buffer: Entity<Buffer>,
_cursor_position: Anchor,
_direction: Direction,
_cx: &mut Context<Self>,
) {
// Codestral doesn't support multiple completions, so cycling does nothing
}
fn accept(&mut self, _cx: &mut Context<Self>) {
log::debug!("Codestral: Completion accepted");
self.pending_request = None;
self.current_completion = None;
}
fn discard(&mut self, _cx: &mut Context<Self>) {
log::debug!("Codestral: Completion discarded");
self.pending_request = None;
self.current_completion = None;
}
/// Returns the completion suggestion, adjusted or invalidated based on user edits
fn suggest(
&mut self,
buffer: &Entity<Buffer>,
_cursor_position: Anchor,
cx: &mut Context<Self>,
) -> Option<EditPrediction> {
let current_completion = self.current_completion.as_ref()?;
let buffer = buffer.read(cx);
let edits = current_completion.interpolate(&buffer.snapshot())?;
if edits.is_empty() {
return None;
}
Some(EditPrediction::Local {
id: None,
edits,
edit_preview: Some(current_completion.edit_preview.clone()),
})
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct CodestralRequest {
pub model: String,
pub prompt: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub suffix: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_tokens: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub temperature: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub top_p: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stream: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stop: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub random_seed: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub min_tokens: Option<u32>,
}
#[derive(Debug, Deserialize)]
pub struct CodestralResponse {
pub id: String,
pub object: String,
pub model: String,
pub usage: Usage,
pub created: u64,
pub choices: Vec<Choice>,
}
#[derive(Debug, Deserialize)]
pub struct Usage {
pub prompt_tokens: u32,
pub completion_tokens: u32,
pub total_tokens: u32,
}
#[derive(Debug, Deserialize)]
pub struct Choice {
pub index: u32,
pub message: Message,
pub finish_reason: String,
}
#[derive(Debug, Deserialize)]
pub struct Message {
pub content: String,
pub role: String,
}

View File

@@ -97,7 +97,6 @@ CREATE TABLE "worktree_entries" (
"is_external" BOOL NOT NULL,
"is_ignored" BOOL NOT NULL,
"is_deleted" BOOL NOT NULL,
"is_hidden" BOOL NOT NULL,
"git_status" INTEGER,
"is_fifo" BOOL NOT NULL,
PRIMARY KEY (project_id, worktree_id, id),

View File

@@ -1,2 +0,0 @@
ALTER TABLE "worktree_entries"
ADD "is_hidden" BOOL NOT NULL DEFAULT FALSE;

View File

@@ -282,7 +282,6 @@ impl Database {
git_status: ActiveValue::set(None),
is_external: ActiveValue::set(entry.is_external),
is_deleted: ActiveValue::set(false),
is_hidden: ActiveValue::set(entry.is_hidden),
scan_id: ActiveValue::set(update.scan_id as i64),
is_fifo: ActiveValue::set(entry.is_fifo),
}
@@ -301,7 +300,6 @@ impl Database {
worktree_entry::Column::MtimeNanos,
worktree_entry::Column::CanonicalPath,
worktree_entry::Column::IsIgnored,
worktree_entry::Column::IsHidden,
worktree_entry::Column::ScanId,
])
.to_owned(),
@@ -907,7 +905,6 @@ impl Database {
canonical_path: db_entry.canonical_path,
is_ignored: db_entry.is_ignored,
is_external: db_entry.is_external,
is_hidden: db_entry.is_hidden,
// This is only used in the summarization backlog, so if it's None,
// that just means we won't be able to detect when to resummarize
// based on total number of backlogged bytes - instead, we'd go

View File

@@ -671,7 +671,6 @@ impl Database {
canonical_path: db_entry.canonical_path,
is_ignored: db_entry.is_ignored,
is_external: db_entry.is_external,
is_hidden: db_entry.is_hidden,
// This is only used in the summarization backlog, so if it's None,
// that just means we won't be able to detect when to resummarize
// based on total number of backlogged bytes - instead, we'd go

View File

@@ -19,7 +19,6 @@ pub struct Model {
pub is_ignored: bool,
pub is_external: bool,
pub is_deleted: bool,
pub is_hidden: bool,
pub scan_id: i64,
pub is_fifo: bool,
pub canonical_path: Option<String>,

View File

@@ -30,9 +30,9 @@ impl fmt::Display for ZedVersion {
impl ZedVersion {
pub fn can_collaborate(&self) -> bool {
// v0.204.1 was the first version after the auto-update bug.
// We reject any clients older than that to hope we can persuade them to upgrade.
if self.0 < SemanticVersion::new(0, 204, 1) {
// v0.198.4 is the first version where we no longer connect to Collab automatically.
// We reject any clients older than that to prevent them from connecting to Collab just for authentication.
if self.0 < SemanticVersion::new(0, 198, 4) {
return false;
}

View File

@@ -4,7 +4,7 @@ use crate::{
};
use call::ActiveCall;
use editor::{
DocumentColorsRenderMode, Editor, FETCH_COLORS_DEBOUNCE_TIMEOUT, RowInfo, SelectionEffects,
DocumentColorsRenderMode, Editor, RowInfo, SelectionEffects,
actions::{
ConfirmCodeAction, ConfirmCompletion, ConfirmRename, ContextMenuFirst,
ExpandMacroRecursively, MoveToEnd, Redo, Rename, SelectAll, ToggleCodeActions, Undo,
@@ -1272,7 +1272,7 @@ async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut Tes
fake_language_server.start_progress("the-token").await;
executor.advance_clock(SERVER_PROGRESS_THROTTLE_TIMEOUT);
fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
fake_language_server.notify::<lsp::notification::Progress>(&lsp::ProgressParams {
token: lsp::NumberOrString::String("the-token".to_string()),
value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
lsp::WorkDoneProgressReport {
@@ -1306,7 +1306,7 @@ async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut Tes
});
executor.advance_clock(SERVER_PROGRESS_THROTTLE_TIMEOUT);
fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
fake_language_server.notify::<lsp::notification::Progress>(&lsp::ProgressParams {
token: lsp::NumberOrString::String("the-token".to_string()),
value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
lsp::WorkDoneProgressReport {
@@ -2041,10 +2041,6 @@ async fn test_mutual_editor_inlay_hint_cache_update(
});
}
// This test started hanging on seed 2 after the theme settings
// PR. The hypothesis is that it's been buggy for a while, but got lucky
// on seeds.
#[ignore]
#[gpui::test(iterations = 10)]
async fn test_inlay_hint_refresh_is_forwarded(
cx_a: &mut TestAppContext,
@@ -2409,7 +2405,6 @@ async fn test_lsp_document_color(cx_a: &mut TestAppContext, cx_b: &mut TestAppCo
.unwrap();
color_request_handle.next().await.unwrap();
executor.advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
executor.run_until_parked();
assert_eq!(
@@ -2849,7 +2844,7 @@ async fn test_lsp_pull_diagnostics(
});
fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
lsp::PublishDiagnosticsParams {
&lsp::PublishDiagnosticsParams {
uri: lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
diagnostics: vec![lsp::Diagnostic {
range: lsp::Range {
@@ -2870,7 +2865,7 @@ async fn test_lsp_pull_diagnostics(
},
);
fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
lsp::PublishDiagnosticsParams {
&lsp::PublishDiagnosticsParams {
uri: lsp::Uri::from_file_path(path!("/a/lib.rs")).unwrap(),
diagnostics: vec![lsp::Diagnostic {
range: lsp::Range {
@@ -2892,7 +2887,7 @@ async fn test_lsp_pull_diagnostics(
);
if should_stream_workspace_diagnostic {
fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
fake_language_server.notify::<lsp::notification::Progress>(&lsp::ProgressParams {
token: expected_workspace_diagnostic_token.clone(),
value: lsp::ProgressParamsValue::WorkspaceDiagnostic(
lsp::WorkspaceDiagnosticReportResult::Report(lsp::WorkspaceDiagnosticReport {
@@ -3074,7 +3069,7 @@ async fn test_lsp_pull_diagnostics(
});
if should_stream_workspace_diagnostic {
fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
fake_language_server.notify::<lsp::notification::Progress>(&lsp::ProgressParams {
token: expected_workspace_diagnostic_token.clone(),
value: lsp::ProgressParamsValue::WorkspaceDiagnostic(
lsp::WorkspaceDiagnosticReportResult::Report(lsp::WorkspaceDiagnosticReport {

View File

@@ -25,7 +25,7 @@ use gpui::{
use language::{
Diagnostic, DiagnosticEntry, DiagnosticSourceKind, FakeLspAdapter, Language, LanguageConfig,
LanguageMatcher, LineEnding, OffsetRangeExt, Point, Rope,
language_settings::{Formatter, FormatterList},
language_settings::{Formatter, FormatterList, SelectedFormatter},
tree_sitter_rust, tree_sitter_typescript,
};
use lsp::{LanguageServerId, OneOf};
@@ -39,7 +39,7 @@ use project::{
use prompt_store::PromptBuilder;
use rand::prelude::*;
use serde_json::json;
use settings::{LanguageServerFormatterSpecifier, PrettierSettingsContent, SettingsStore};
use settings::{PrettierSettingsContent, SettingsStore};
use std::{
cell::{Cell, RefCell},
env, future, mem,
@@ -4077,7 +4077,7 @@ async fn test_collaborating_with_diagnostics(
.receive_notification::<lsp::notification::DidOpenTextDocument>()
.await;
fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
lsp::PublishDiagnosticsParams {
&lsp::PublishDiagnosticsParams {
uri: lsp::Uri::from_file_path(path!("/a/a.rs")).unwrap(),
version: None,
diagnostics: vec![lsp::Diagnostic {
@@ -4097,7 +4097,7 @@ async fn test_collaborating_with_diagnostics(
.await
.unwrap();
fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
lsp::PublishDiagnosticsParams {
&lsp::PublishDiagnosticsParams {
uri: lsp::Uri::from_file_path(path!("/a/a.rs")).unwrap(),
version: None,
diagnostics: vec![lsp::Diagnostic {
@@ -4171,7 +4171,7 @@ async fn test_collaborating_with_diagnostics(
// Simulate a language server reporting more errors for a file.
fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
lsp::PublishDiagnosticsParams {
&lsp::PublishDiagnosticsParams {
uri: lsp::Uri::from_file_path(path!("/a/a.rs")).unwrap(),
version: None,
diagnostics: vec![
@@ -4269,7 +4269,7 @@ async fn test_collaborating_with_diagnostics(
// Simulate a language server reporting no errors for a file.
fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
lsp::PublishDiagnosticsParams {
&lsp::PublishDiagnosticsParams {
uri: lsp::Uri::from_file_path(path!("/a/a.rs")).unwrap(),
version: None,
diagnostics: Vec::new(),
@@ -4365,7 +4365,7 @@ async fn test_collaborating_with_lsp_progress_updates_and_diagnostics_ordering(
.await
.into_response()
.unwrap();
fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
fake_language_server.notify::<lsp::notification::Progress>(&lsp::ProgressParams {
token: lsp::NumberOrString::String("the-disk-based-token".to_string()),
value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Begin(
lsp::WorkDoneProgressBegin {
@@ -4376,7 +4376,7 @@ async fn test_collaborating_with_lsp_progress_updates_and_diagnostics_ordering(
});
for file_name in file_names {
fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
lsp::PublishDiagnosticsParams {
&lsp::PublishDiagnosticsParams {
uri: lsp::Uri::from_file_path(Path::new(path!("/test")).join(file_name)).unwrap(),
version: None,
diagnostics: vec![lsp::Diagnostic {
@@ -4389,7 +4389,7 @@ async fn test_collaborating_with_lsp_progress_updates_and_diagnostics_ordering(
},
);
}
fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
fake_language_server.notify::<lsp::notification::Progress>(&lsp::ProgressParams {
token: lsp::NumberOrString::String("the-disk-based-token".to_string()),
value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::End(
lsp::WorkDoneProgressEnd { message: None },
@@ -4610,13 +4610,14 @@ async fn test_formatting_buffer(
cx_a.update(|cx| {
SettingsStore::update_global(cx, |store, cx| {
store.update_user_settings(cx, |file| {
file.project.all_languages.defaults.formatter =
Some(FormatterList::Single(Formatter::External {
file.project.all_languages.defaults.formatter = Some(SelectedFormatter::List(
FormatterList::Single(Formatter::External {
command: "awk".into(),
arguments: Some(
vec!["{sub(/two/,\"{buffer_path}\")}1".to_string()].into(),
),
}));
}),
));
});
});
});
@@ -4707,7 +4708,7 @@ async fn test_prettier_formatting_buffer(
cx_a.update(|cx| {
SettingsStore::update_global(cx, |store, cx| {
store.update_user_settings(cx, |file| {
file.project.all_languages.defaults.formatter = Some(FormatterList::default());
file.project.all_languages.defaults.formatter = Some(SelectedFormatter::Auto);
file.project.all_languages.defaults.prettier = Some(PrettierSettingsContent {
allowed: Some(true),
..Default::default()
@@ -4718,8 +4719,8 @@ async fn test_prettier_formatting_buffer(
cx_b.update(|cx| {
SettingsStore::update_global(cx, |store, cx| {
store.update_user_settings(cx, |file| {
file.project.all_languages.defaults.formatter = Some(FormatterList::Single(
Formatter::LanguageServer(LanguageServerFormatterSpecifier::Current),
file.project.all_languages.defaults.formatter = Some(SelectedFormatter::List(
FormatterList::Single(Formatter::LanguageServer { name: None }),
));
file.project.all_languages.defaults.prettier = Some(PrettierSettingsContent {
allowed: Some(true),

View File

@@ -183,10 +183,9 @@ pub async fn run_randomized_test<T: RandomizedTest>(
for (client, cx) in clients {
cx.update(|cx| {
let settings = cx.remove_global::<SettingsStore>();
let store = cx.remove_global::<SettingsStore>();
cx.clear_globals();
cx.set_global(settings);
theme::init(theme::LoadThemes::JustBase, cx);
cx.set_global(store);
drop(client);
});
}

View File

@@ -14,7 +14,7 @@ use gpui::{
use http_client::BlockedHttpClient;
use language::{
FakeLspAdapter, Language, LanguageConfig, LanguageMatcher, LanguageRegistry,
language_settings::{Formatter, FormatterList, language_settings},
language_settings::{Formatter, FormatterList, SelectedFormatter, language_settings},
tree_sitter_typescript,
};
use node_runtime::NodeRuntime;
@@ -27,7 +27,7 @@ use remote::RemoteClient;
use remote_server::{HeadlessAppState, HeadlessProject};
use rpc::proto;
use serde_json::json;
use settings::{LanguageServerFormatterSpecifier, PrettierSettingsContent, SettingsStore};
use settings::{PrettierSettingsContent, SettingsStore};
use std::{
path::Path,
sync::{Arc, atomic::AtomicUsize},
@@ -491,7 +491,7 @@ async fn test_ssh_collaboration_formatting_with_prettier(
cx_a.update(|cx| {
SettingsStore::update_global(cx, |store, cx| {
store.update_user_settings(cx, |file| {
file.project.all_languages.defaults.formatter = Some(FormatterList::default());
file.project.all_languages.defaults.formatter = Some(SelectedFormatter::Auto);
file.project.all_languages.defaults.prettier = Some(PrettierSettingsContent {
allowed: Some(true),
..Default::default()
@@ -502,8 +502,8 @@ async fn test_ssh_collaboration_formatting_with_prettier(
cx_b.update(|cx| {
SettingsStore::update_global(cx, |store, cx| {
store.update_user_settings(cx, |file| {
file.project.all_languages.defaults.formatter = Some(FormatterList::Single(
Formatter::LanguageServer(LanguageServerFormatterSpecifier::Current),
file.project.all_languages.defaults.formatter = Some(SelectedFormatter::List(
FormatterList::Single(Formatter::LanguageServer { name: None }),
));
file.project.all_languages.defaults.prettier = Some(PrettierSettingsContent {
allowed: Some(true),
@@ -550,7 +550,7 @@ async fn test_ssh_collaboration_formatting_with_prettier(
cx_a.update(|cx| {
SettingsStore::update_global(cx, |store, cx| {
store.update_user_settings(cx, |file| {
file.project.all_languages.defaults.formatter = Some(FormatterList::default());
file.project.all_languages.defaults.formatter = Some(SelectedFormatter::Auto);
file.project.all_languages.defaults.prettier = Some(PrettierSettingsContent {
allowed: Some(true),
..Default::default()

View File

@@ -172,7 +172,6 @@ impl TestServer {
}
let settings = SettingsStore::test(cx);
cx.set_global(settings);
theme::init(theme::LoadThemes::JustBase, cx);
release_channel::init(SemanticVersion::default(), cx);
client::init_settings(cx);
});

View File

@@ -2250,7 +2250,7 @@ impl CollabPanel {
})),
)
.child(
v_flex().w_full().items_center().child(
div().flex().w_full().items_center().child(
Label::new("Sign in to enable collaboration.")
.color(Color::Muted)
.size(LabelSize::Small),

View File

@@ -18,7 +18,7 @@ pub struct NotificationPanelSettings {
}
impl Settings for CollaborationPanelSettings {
fn from_settings(content: &settings::SettingsContent) -> Self {
fn from_settings(content: &settings::SettingsContent, _cx: &mut ui::App) -> Self {
let panel = content.collaboration_panel.as_ref().unwrap();
Self {
@@ -30,7 +30,7 @@ impl Settings for CollaborationPanelSettings {
}
impl Settings for NotificationPanelSettings {
fn from_settings(content: &settings::SettingsContent) -> Self {
fn from_settings(content: &settings::SettingsContent, _cx: &mut ui::App) -> Self {
let panel = content.notification_panel.as_ref().unwrap();
return Self {
button: panel.button.unwrap(),

View File

@@ -1,8 +1,8 @@
[package]
name = "collections"
name = "zed-collections"
version = "0.1.0"
edition.workspace = true
publish = false
publish = true
license = "Apache-2.0"
description = "Standard collection type re-exports used by Zed and GPUI"

View File

@@ -97,10 +97,11 @@ impl CommandPaletteFilter {
pub struct CommandInterceptResult {
/// The action produced as a result of the interception.
pub action: Box<dyn Action>,
/// The display string to show in the command palette for this result.
// TODO: Document this field.
#[allow(missing_docs)]
pub string: String,
/// The character positions in the string that match the query.
/// Used for highlighting matched characters in the command palette UI.
// TODO: Document this field.
#[allow(missing_docs)]
pub positions: Vec<usize>,
}

View File

@@ -270,7 +270,7 @@ impl RegisteredBuffer {
server
.lsp
.notify::<lsp::notification::DidChangeTextDocument>(
lsp::DidChangeTextDocumentParams {
&lsp::DidChangeTextDocumentParams {
text_document: lsp::VersionedTextDocumentIdentifier::new(
buffer.uri.clone(),
buffer.snapshot_version,
@@ -744,7 +744,7 @@ impl Copilot {
let snapshot = buffer.read(cx).snapshot();
server
.notify::<lsp::notification::DidOpenTextDocument>(
lsp::DidOpenTextDocumentParams {
&lsp::DidOpenTextDocumentParams {
text_document: lsp::TextDocumentItem {
uri: uri.clone(),
language_id: language_id.clone(),
@@ -792,14 +792,13 @@ impl Copilot {
server
.lsp
.notify::<lsp::notification::DidSaveTextDocument>(
lsp::DidSaveTextDocumentParams {
&lsp::DidSaveTextDocumentParams {
text_document: lsp::TextDocumentIdentifier::new(
registered_buffer.uri.clone(),
),
text: None,
},
)
.ok();
)?;
}
language::BufferEvent::FileHandleChanged
| language::BufferEvent::LanguageChanged => {
@@ -815,15 +814,14 @@ impl Copilot {
server
.lsp
.notify::<lsp::notification::DidCloseTextDocument>(
lsp::DidCloseTextDocumentParams {
&lsp::DidCloseTextDocumentParams {
text_document: lsp::TextDocumentIdentifier::new(old_uri),
},
)
.ok();
)?;
server
.lsp
.notify::<lsp::notification::DidOpenTextDocument>(
lsp::DidOpenTextDocumentParams {
&lsp::DidOpenTextDocumentParams {
text_document: lsp::TextDocumentItem::new(
registered_buffer.uri.clone(),
registered_buffer.language_id.clone(),
@@ -831,8 +829,7 @@ impl Copilot {
registered_buffer.snapshot.text(),
),
},
)
.ok();
)?;
}
}
_ => {}
@@ -849,7 +846,7 @@ impl Copilot {
server
.lsp
.notify::<lsp::notification::DidCloseTextDocument>(
lsp::DidCloseTextDocumentParams {
&lsp::DidCloseTextDocumentParams {
text_document: lsp::TextDocumentIdentifier::new(buffer.uri),
},
)
@@ -1154,12 +1151,9 @@ fn notify_did_change_config_to_server(
}
});
server
.notify::<lsp::notification::DidChangeConfiguration>(lsp::DidChangeConfigurationParams {
settings,
})
.ok();
Ok(())
server.notify::<lsp::notification::DidChangeConfiguration>(&lsp::DidChangeConfigurationParams {
settings,
})
}
async fn clear_copilot_dir() {

View File

@@ -92,10 +92,7 @@ pub async fn init(crash_init: InitCrashHandler) {
#[cfg(target_os = "macos")]
suspend_all_other_threads();
// on macos this "ping" is needed to ensure that all our
// `client.send_message` calls have been processed before we trigger the
// minidump request.
client.ping().ok();
client.ping().unwrap();
client.request_dump(crash_context).is_ok()
} else {
true

View File

@@ -46,7 +46,6 @@ pub trait DapDelegate: Send + Sync + 'static {
async fn which(&self, command: &OsStr) -> Option<PathBuf>;
async fn read_text_file(&self, path: &RelPath) -> Result<String>;
async fn shell_env(&self) -> collections::HashMap<String, String>;
fn is_headless(&self) -> bool;
}
#[derive(

View File

@@ -1,4 +1,5 @@
use dap_types::SteppingGranularity;
use gpui::App;
use settings::{Settings, SettingsContent};
pub struct DebuggerSettings {
@@ -33,7 +34,7 @@ pub struct DebuggerSettings {
}
impl Settings for DebuggerSettings {
fn from_settings(content: &SettingsContent) -> Self {
fn from_settings(content: &SettingsContent, _cx: &mut App) -> Self {
let content = content.debugger.clone().unwrap();
Self {
stepping_granularity: dap_granularity_from_settings(

View File

@@ -262,15 +262,11 @@ impl TransportDelegate {
break;
}
}
// Clean up logs by trimming unnecessary whitespace/newlines before inserting into log.
let line = line.trim();
log::debug!("stderr: {line}");
for (kind, handler) in log_handlers.lock().iter_mut() {
if matches!(kind, LogKind::Adapter) {
handler(iokind, None, line);
handler(iokind, None, line.as_str());
}
}
}
@@ -653,7 +649,7 @@ impl Drop for TcpTransport {
}
pub struct StdioTransport {
process: Mutex<Child>,
process: Mutex<Option<Child>>,
_stderr_task: Option<Task<()>>,
}
@@ -680,7 +676,7 @@ impl StdioTransport {
let mut process = Child::spawn(command, Stdio::piped())?;
let _stderr_task = process.stderr.take().map(|stderr| {
let err_task = process.stderr.take().map(|stderr| {
cx.background_spawn(TransportDelegate::handle_adapter_log(
stderr,
IoKind::StdErr,
@@ -688,22 +684,24 @@ impl StdioTransport {
))
});
let process = Mutex::new(process);
let process = Mutex::new(Some(process));
Ok(Self {
process,
_stderr_task,
_stderr_task: err_task,
})
}
}
impl Transport for StdioTransport {
fn has_adapter_logs(&self) -> bool {
true
false
}
fn kill(&mut self) {
self.process.lock().kill();
if let Some(process) = &mut *self.process.lock() {
process.kill();
}
}
fn connect(
@@ -715,7 +713,8 @@ impl Transport for StdioTransport {
)>,
> {
let result = util::maybe!({
let mut process = self.process.lock();
let mut guard = self.process.lock();
let process = guard.as_mut().context("oops")?;
Ok((
Box::new(process.stdin.take().context("Cannot reconnect")?) as _,
Box::new(process.stdout.take().context("Cannot reconnect")?) as _,
@@ -731,7 +730,9 @@ impl Transport for StdioTransport {
impl Drop for StdioTransport {
fn drop(&mut self) {
self.process.lock().kill();
if let Some(process) = &mut *self.process.lock() {
process.kill();
}
}
}

View File

@@ -1,4 +1,4 @@
use std::{path::PathBuf, sync::OnceLock};
use std::{collections::HashMap, path::PathBuf, sync::OnceLock};
use anyhow::{Context as _, Result};
use async_trait::async_trait;
@@ -377,12 +377,6 @@ impl DebugAdapter for CodeLldbDebugAdapter {
command = Some(path);
};
let mut json_config = config.config.clone();
// Enable info level for CodeLLDB by default.
// Logs can then be viewed in our DAP logs.
let mut envs = collections::HashMap::default();
envs.insert("RUST_LOG".to_string(), "info".to_string());
Ok(DebugAdapterBinary {
command: Some(command.unwrap()),
cwd: Some(delegate.worktree_root_path().to_path_buf()),
@@ -407,7 +401,7 @@ impl DebugAdapter for CodeLldbDebugAdapter {
request_args: self
.request_args(delegate, json_config, &config.label)
.await?,
envs,
envs: HashMap::default(),
connection: None,
})
}

View File

@@ -120,13 +120,6 @@ impl JsDebugAdapter {
configuration
.entry("sourceMapRenames")
.or_insert(true.into());
// Set up remote browser debugging
if delegate.is_headless() {
configuration
.entry("browserLaunchLocation")
.or_insert("ui".into());
}
}
let adapter_path = if let Some(user_installed_path) = user_installed_path {

View File

@@ -1,12 +1,12 @@
use crate::*;
use anyhow::{Context as _, bail};
use anyhow::Context as _;
use dap::{DebugRequest, StartDebuggingRequestArguments, adapters::DebugTaskDefinition};
use fs::RemoveOptions;
use futures::{StreamExt, TryStreamExt};
use gpui::http_client::AsyncBody;
use gpui::{AsyncApp, SharedString};
use json_dotpath::DotPaths;
use language::{LanguageName, Toolchain};
use language::LanguageName;
use paths::debug_adapters_dir;
use serde_json::Value;
use smol::fs::File;
@@ -20,8 +20,7 @@ use std::{
ffi::OsStr,
path::{Path, PathBuf},
};
use util::command::new_smol_command;
use util::{ResultExt, paths::PathStyle, rel_path::RelPath};
use util::{ResultExt, maybe, paths::PathStyle, rel_path::RelPath};
#[derive(Default)]
pub(crate) struct PythonDebugAdapter {
@@ -93,16 +92,12 @@ impl PythonDebugAdapter {
})
}
async fn fetch_wheel(
&self,
toolchain: Option<Toolchain>,
delegate: &Arc<dyn DapDelegate>,
) -> Result<Arc<Path>> {
async fn fetch_wheel(&self, delegate: &Arc<dyn DapDelegate>) -> Result<Arc<Path>, String> {
let download_dir = debug_adapters_dir().join(Self::ADAPTER_NAME).join("wheels");
std::fs::create_dir_all(&download_dir)?;
let venv_python = self.base_venv_path(toolchain, delegate).await?;
std::fs::create_dir_all(&download_dir).map_err(|e| e.to_string())?;
let system_python = self.base_venv_path(delegate).await?;
let installation_succeeded = util::command::new_smol_command(venv_python.as_ref())
let installation_succeeded = util::command::new_smol_command(system_python.as_ref())
.args([
"-m",
"pip",
@@ -114,36 +109,36 @@ impl PythonDebugAdapter {
])
.output()
.await
.context("spawn system python")?
.map_err(|e| format!("{e}"))?
.status
.success();
if !installation_succeeded {
bail!("debugpy installation failed (could not fetch Debugpy's wheel)");
return Err("debugpy installation failed (could not fetch Debugpy's wheel)".into());
}
let wheel_path = std::fs::read_dir(&download_dir)?
let wheel_path = std::fs::read_dir(&download_dir)
.map_err(|e| e.to_string())?
.find_map(|entry| {
entry.ok().filter(|e| {
e.file_type().is_ok_and(|typ| typ.is_file())
&& Path::new(&e.file_name()).extension() == Some("whl".as_ref())
})
})
.with_context(|| format!("Did not find a .whl in {download_dir:?}"))?;
.ok_or_else(|| String::from("Did not find a .whl in {download_dir}"))?;
util::archive::extract_zip(
&debug_adapters_dir().join(Self::ADAPTER_NAME),
File::open(&wheel_path.path()).await?,
File::open(&wheel_path.path())
.await
.map_err(|e| e.to_string())?,
)
.await?;
.await
.map_err(|e| e.to_string())?;
Ok(Arc::from(wheel_path.path()))
}
async fn maybe_fetch_new_wheel(
&self,
toolchain: Option<Toolchain>,
delegate: &Arc<dyn DapDelegate>,
) -> Result<()> {
async fn maybe_fetch_new_wheel(&self, delegate: &Arc<dyn DapDelegate>) {
let latest_release = delegate
.http_client()
.get(
@@ -153,61 +148,62 @@ impl PythonDebugAdapter {
)
.await
.log_err();
let response = latest_release
.filter(|response| response.status().is_success())
.context("getting latest release")?;
maybe!(async move {
let response = latest_release.filter(|response| response.status().is_success())?;
let download_dir = debug_adapters_dir().join(Self::ADAPTER_NAME);
std::fs::create_dir_all(&download_dir)?;
let download_dir = debug_adapters_dir().join(Self::ADAPTER_NAME);
std::fs::create_dir_all(&download_dir).ok()?;
let mut output = String::new();
response.into_body().read_to_string(&mut output).await?;
let as_json = serde_json::Value::from_str(&output)?;
let latest_version = as_json
.get("info")
.and_then(|info| {
let mut output = String::new();
response
.into_body()
.read_to_string(&mut output)
.await
.ok()?;
let as_json = serde_json::Value::from_str(&output).ok()?;
let latest_version = as_json.get("info").and_then(|info| {
info.get("version")
.and_then(|version| version.as_str())
.map(ToOwned::to_owned)
})
.context("parsing latest release information")?;
let dist_info_dirname: OsString = format!("debugpy-{latest_version}.dist-info").into();
let is_up_to_date = delegate
.fs()
.read_dir(&debug_adapters_dir().join(Self::ADAPTER_NAME))
.await?
.into_stream()
.any(async |entry| {
entry.is_ok_and(|e| e.file_name().is_some_and(|name| name == dist_info_dirname))
})
.await;
if !is_up_to_date {
delegate
})?;
let dist_info_dirname: OsString = format!("debugpy-{latest_version}.dist-info").into();
let is_up_to_date = delegate
.fs()
.remove_dir(
&debug_adapters_dir().join(Self::ADAPTER_NAME),
RemoveOptions {
recursive: true,
ignore_if_not_exists: true,
},
)
.await?;
self.fetch_wheel(toolchain, delegate).await?;
}
anyhow::Ok(())
.read_dir(&debug_adapters_dir().join(Self::ADAPTER_NAME))
.await
.ok()?
.into_stream()
.any(async |entry| {
entry.is_ok_and(|e| e.file_name().is_some_and(|name| name == dist_info_dirname))
})
.await;
if !is_up_to_date {
delegate
.fs()
.remove_dir(
&debug_adapters_dir().join(Self::ADAPTER_NAME),
RemoveOptions {
recursive: true,
ignore_if_not_exists: true,
},
)
.await
.ok()?;
self.fetch_wheel(delegate).await.ok()?;
}
Some(())
})
.await;
}
async fn fetch_debugpy_whl(
&self,
toolchain: Option<Toolchain>,
delegate: &Arc<dyn DapDelegate>,
) -> Result<Arc<Path>, String> {
self.debugpy_whl_base_path
.get_or_init(|| async move {
self.maybe_fetch_new_wheel(toolchain, delegate)
.await
.map_err(|e| format!("{e}"))?;
self.maybe_fetch_new_wheel(delegate).await;
Ok(Arc::from(
debug_adapters_dir()
.join(Self::ADAPTER_NAME)
@@ -220,24 +216,12 @@ impl PythonDebugAdapter {
.clone()
}
async fn base_venv_path(
&self,
toolchain: Option<Toolchain>,
delegate: &Arc<dyn DapDelegate>,
) -> Result<Arc<Path>> {
let result = self.base_venv_path
async fn base_venv_path(&self, delegate: &Arc<dyn DapDelegate>) -> Result<Arc<Path>, String> {
self.base_venv_path
.get_or_init(|| async {
let base_python = if let Some(toolchain) = toolchain {
toolchain.path.to_string()
} else {
Self::system_python_name(delegate).await.ok_or_else(|| {
let mut message = "Could not find a Python installation".to_owned();
if cfg!(windows){
message.push_str(". Install Python from the Microsoft Store, or manually from https://www.python.org/downloads/windows.")
}
message
})?
};
let base_python = Self::system_python_name(delegate)
.await
.ok_or_else(|| String::from("Could not find a Python installation"))?;
let did_succeed = util::command::new_smol_command(base_python)
.args(["-m", "venv", "zed_base_venv"])
@@ -255,50 +239,35 @@ impl PythonDebugAdapter {
return Err("Failed to create base virtual environment".into());
}
const PYTHON_PATH: &str = if cfg!(target_os = "windows") {
"Scripts/python.exe"
const DIR: &str = if cfg!(target_os = "windows") {
"Scripts"
} else {
"bin/python3"
"bin"
};
Ok(Arc::from(
paths::debug_adapters_dir()
.join(Self::DEBUG_ADAPTER_NAME.as_ref())
.join("zed_base_venv")
.join(PYTHON_PATH)
.join(DIR)
.join("python3")
.as_ref(),
))
})
.await
.clone();
match result {
Ok(path) => Ok(path),
Err(e) => Err(anyhow::anyhow!("{e}")),
}
.clone()
}
async fn system_python_name(delegate: &Arc<dyn DapDelegate>) -> Option<String> {
const BINARY_NAMES: [&str; 3] = ["python3", "python", "py"];
let mut name = None;
for cmd in BINARY_NAMES {
let Some(path) = delegate.which(OsStr::new(cmd)).await else {
continue;
};
// Try to detect situations where `python3` exists but is not a real Python interpreter.
// Notably, on fresh Windows installs, `python3` is a shim that opens the Microsoft Store app
// when run with no arguments, and just fails otherwise.
let Some(output) = new_smol_command(&path)
.args(["-c", "print(1 + 2)"])
.output()
name = delegate
.which(OsStr::new(cmd))
.await
.ok()
else {
continue;
};
if output.stdout.trim_ascii() != b"3" {
continue;
.map(|path| path.to_string_lossy().into_owned());
if name.is_some() {
break;
}
name = Some(path.to_string_lossy().into_owned());
break;
}
name
}
@@ -777,10 +746,15 @@ impl DebugAdapter for PythonDebugAdapter {
)
.await;
self.fetch_debugpy_whl(toolchain.clone(), delegate)
let debugpy_path = self
.fetch_debugpy_whl(delegate)
.await
.map_err(|e| anyhow::anyhow!("{e}"))?;
if let Some(toolchain) = &toolchain {
log::debug!(
"Found debugpy in toolchain environment: {}",
debugpy_path.display()
);
return self
.get_installed_binary(
delegate,

View File

@@ -963,21 +963,26 @@ pub fn init(cx: &mut App) {
};
let project = workspace.project();
log_store.update(cx, |store, cx| {
store.add_project(project, cx);
});
if project.read(cx).is_local() {
log_store.update(cx, |store, cx| {
store.add_project(project, cx);
});
}
let log_store = log_store.clone();
workspace.register_action(move |workspace, _: &OpenDebugAdapterLogs, window, cx| {
workspace.add_item_to_active_pane(
Box::new(cx.new(|cx| {
DapLogView::new(workspace.project().clone(), log_store.clone(), window, cx)
})),
None,
true,
window,
cx,
);
let project = workspace.project().read(cx);
if project.is_local() {
workspace.add_item_to_active_pane(
Box::new(cx.new(|cx| {
DapLogView::new(workspace.project().clone(), log_store.clone(), window, cx)
})),
None,
true,
window,
cx,
);
}
});
})
.detach();

View File

@@ -268,12 +268,12 @@ impl DebugPanel {
async move |_, cx| {
if let Err(error) = task.await {
log::error!("{error:#}");
log::error!("{error}");
session
.update(cx, |session, cx| {
session
.console_output(cx)
.unbounded_send(format!("error: {:#}", error))
.unbounded_send(format!("error: {}", error))
.ok();
session.shutdown(cx)
})?

View File

@@ -1,7 +1,7 @@
use std::rc::Rc;
use collections::HashMap;
use gpui::{Corner, Entity, WeakEntity};
use gpui::{Entity, WeakEntity};
use project::debugger::session::{ThreadId, ThreadStatus};
use ui::{CommonAnimationExt, ContextMenu, DropdownMenu, DropdownStyle, Indicator, prelude::*};
use util::{maybe, truncate_and_trailoff};
@@ -211,7 +211,6 @@ impl DebugPanel {
this
}),
)
.attach(Corner::BottomLeft)
.style(DropdownStyle::Ghost)
.handle(self.session_picker_menu_handle.clone());
@@ -323,7 +322,6 @@ impl DebugPanel {
this
}),
)
.attach(Corner::BottomLeft)
.disabled(session_terminated)
.style(DropdownStyle::Ghost)
.handle(self.thread_picker_menu_handle.clone()),

View File

@@ -937,7 +937,6 @@ impl RunningState {
let task_store = project.read(cx).task_store().downgrade();
let weak_project = project.downgrade();
let weak_workspace = workspace.downgrade();
let is_windows = project.read(cx).path_style(cx).is_windows();
let remote_shell = project
.read(cx)
.remote_client()
@@ -1030,7 +1029,7 @@ impl RunningState {
task.resolved.shell = Shell::Program(remote_shell);
}
let builder = ShellBuilder::new(&task.resolved.shell, is_windows);
let builder = ShellBuilder::new(&task.resolved.shell);
let command_label = builder.command_label(task.resolved.command.as_deref().unwrap_or(""));
let (command, args) =
builder.build(task.resolved.command.clone(), &task.resolved.args);

View File

@@ -669,7 +669,11 @@ impl ConsoleQueryBarCompletionProvider {
&snapshot,
),
new_text: string_match.string.clone(),
label: CodeLabel::plain(string_match.string.clone(), None),
label: CodeLabel {
filter_range: 0..string_match.string.len(),
text: string_match.string.clone(),
runs: Vec::new(),
},
icon_path: None,
documentation: Some(CompletionDocumentation::MultiLineMarkdown(
variable_value.into(),
@@ -778,7 +782,11 @@ impl ConsoleQueryBarCompletionProvider {
&snapshot,
),
new_text,
label: CodeLabel::plain(completion.label, None),
label: CodeLabel {
filter_range: 0..completion.label.len(),
text: completion.label,
runs: Vec::new(),
},
icon_path: None,
documentation: completion.detail.map(|detail| {
CompletionDocumentation::MultiLineMarkdown(detail.into())

View File

@@ -965,11 +965,10 @@ async fn heuristic_syntactic_expand(
let row_count = node_end.row - node_start.row + 1;
let mut ancestor_range = None;
let reached_outline_node = cx.background_executor().scoped({
let node_range = node_range.clone();
let outline_range = outline_range.clone();
let ancestor_range = &mut ancestor_range;
|scope| {
scope.spawn(async move {
let node_range = node_range.clone();
let outline_range = outline_range.clone();
let ancestor_range = &mut ancestor_range;
|scope| {scope.spawn(async move {
// Stop if we've exceeded the row count or reached an outline node. Then, find the interval
// of node children which contains the query range. For example, this allows just returning
// the header of a declaration rather than the entire declaration.
@@ -981,11 +980,8 @@ async fn heuristic_syntactic_expand(
if cursor.goto_first_child() {
loop {
let child_node = cursor.node();
let child_range =
previous_end..Point::from_ts_point(child_node.end_position());
if included_child_start.is_none()
&& child_range.contains(&input_range.start)
{
let child_range = previous_end..Point::from_ts_point(child_node.end_position());
if included_child_start.is_none() && child_range.contains(&input_range.start) {
included_child_start = Some(child_range.start);
}
if child_range.contains(&input_range.end) {
@@ -1001,22 +997,19 @@ async fn heuristic_syntactic_expand(
if let Some(start) = included_child_start {
let row_count = end.row - start.row;
if row_count < max_row_count {
*ancestor_range =
Some(Some(RangeInclusive::new(start.row, end.row)));
*ancestor_range = Some(Some(RangeInclusive::new(start.row, end.row)));
return;
}
}
log::info!(
"Expanding to ancestor started on {} node\
exceeding row limit of {max_row_count}.",
"Expanding to ancestor started on {} node exceeding row limit of {max_row_count}.",
node.grammar_name()
);
*ancestor_range = Some(None);
}
})
}
});
}});
reached_outline_node.await;
if let Some(node) = ancestor_range {
return node;

View File

@@ -20,8 +20,6 @@ util.workspace = true
workspace-hack.workspace = true
zed.workspace = true
zlog.workspace = true
task.workspace = true
theme.workspace = true
[lints]
workspace = true

View File

@@ -53,20 +53,9 @@ fn main() -> Result<()> {
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
enum PreprocessorError {
ActionNotFound {
action_name: String,
},
DeprecatedActionUsed {
used: String,
should_be: String,
},
ActionNotFound { action_name: String },
DeprecatedActionUsed { used: String, should_be: String },
InvalidFrontmatterLine(String),
InvalidSettingsJson {
file: std::path::PathBuf,
line: usize,
snippet: String,
error: String,
},
}
impl PreprocessorError {
@@ -83,20 +72,6 @@ impl PreprocessorError {
}
PreprocessorError::ActionNotFound { action_name }
}
fn new_for_invalid_settings_json(
chapter: &Chapter,
location: usize,
snippet: String,
error: String,
) -> Self {
PreprocessorError::InvalidSettingsJson {
file: chapter.path.clone().expect("chapter has path"),
line: chapter.content[..location].lines().count() + 1,
snippet,
error,
}
}
}
impl std::fmt::Display for PreprocessorError {
@@ -113,21 +88,6 @@ impl std::fmt::Display for PreprocessorError {
"Deprecated action used: {} should be {}",
used, should_be
),
PreprocessorError::InvalidSettingsJson {
file,
line,
snippet,
error,
} => {
write!(
f,
"Invalid settings JSON at {}:{}\nError: {}\n\n{}",
file.display(),
line,
error,
snippet
)
}
}
}
}
@@ -140,11 +100,11 @@ fn handle_preprocessing() -> Result<()> {
let (_ctx, mut book) = CmdPreprocessor::parse_input(input.as_bytes())?;
let mut errors = HashSet::<PreprocessorError>::new();
handle_frontmatter(&mut book, &mut errors);
template_big_table_of_actions(&mut book);
template_and_validate_keybindings(&mut book, &mut errors);
template_and_validate_actions(&mut book, &mut errors);
template_and_validate_json_snippets(&mut book, &mut errors);
if !errors.is_empty() {
const ANSI_RED: &str = "\x1b[31m";
@@ -275,161 +235,6 @@ fn find_binding(os: &str, action: &str) -> Option<String> {
})
}
fn template_and_validate_json_snippets(book: &mut Book, errors: &mut HashSet<PreprocessorError>) {
fn for_each_labeled_code_block_mut(
book: &mut Book,
errors: &mut HashSet<PreprocessorError>,
f: impl Fn(&str, &str) -> anyhow::Result<()>,
) {
const TAGGED_JSON_BLOCK_START: &'static str = "```json [";
const JSON_BLOCK_END: &'static str = "```";
for_each_chapter_mut(book, |chapter| {
let mut offset = 0;
while let Some(loc) = chapter.content[offset..].find(TAGGED_JSON_BLOCK_START) {
let loc = loc + offset;
let tag_start = loc + TAGGED_JSON_BLOCK_START.len();
offset = tag_start;
let Some(tag_end) = chapter.content[tag_start..].find(']') else {
errors.insert(PreprocessorError::new_for_invalid_settings_json(
chapter,
loc,
chapter.content[loc..tag_start].to_string(),
"Unclosed JSON block tag".to_string(),
));
continue;
};
let tag_end = tag_end + tag_start;
let tag = &chapter.content[tag_start..tag_end];
if tag.contains('\n') {
errors.insert(PreprocessorError::new_for_invalid_settings_json(
chapter,
loc,
chapter.content[loc..tag_start].to_string(),
"Unclosed JSON block tag".to_string(),
));
continue;
}
let snippet_start = tag_end + 1;
offset = snippet_start;
let Some(snippet_end) = chapter.content[snippet_start..].find(JSON_BLOCK_END)
else {
errors.insert(PreprocessorError::new_for_invalid_settings_json(
chapter,
loc,
chapter.content[loc..tag_end + 1].to_string(),
"Missing closing code block".to_string(),
));
continue;
};
let snippet_end = snippet_start + snippet_end;
let snippet_json = &chapter.content[snippet_start..snippet_end];
offset = snippet_end + 3;
if let Err(err) = f(tag, snippet_json) {
errors.insert(PreprocessorError::new_for_invalid_settings_json(
chapter,
loc,
chapter.content[loc..snippet_end + 3].to_string(),
err.to_string(),
));
continue;
};
let tag_range_complete = tag_start - 1..tag_end + 1;
offset -= tag_range_complete.len();
chapter.content.replace_range(tag_range_complete, "");
}
});
}
for_each_labeled_code_block_mut(book, errors, |label, snippet_json| {
let mut snippet_json_fixed = snippet_json
.to_string()
.replace("\n>", "\n")
.trim()
.to_string();
while snippet_json_fixed.starts_with("//") {
if let Some(line_end) = snippet_json_fixed.find('\n') {
snippet_json_fixed.replace_range(0..line_end, "");
snippet_json_fixed = snippet_json_fixed.trim().to_string();
}
}
match label {
"settings" => {
if !snippet_json_fixed.starts_with('{') || !snippet_json_fixed.ends_with('}') {
snippet_json_fixed.insert(0, '{');
snippet_json_fixed.push_str("\n}");
}
settings::parse_json_with_comments::<settings::SettingsContent>(
&snippet_json_fixed,
)?;
}
"keymap" => {
if !snippet_json_fixed.starts_with('[') || !snippet_json_fixed.ends_with(']') {
snippet_json_fixed.insert(0, '[');
snippet_json_fixed.push_str("\n]");
}
let keymap = settings::KeymapFile::parse(&snippet_json_fixed)
.context("Failed to parse keymap JSON")?;
for section in keymap.sections() {
for (keystrokes, action) in section.bindings() {
keystrokes
.split_whitespace()
.map(|source| gpui::Keystroke::parse(source))
.collect::<std::result::Result<Vec<_>, _>>()
.context("Failed to parse keystroke")?;
if let Some((action_name, _)) = settings::KeymapFile::parse_action(action)
.map_err(|err| anyhow::format_err!(err))
.context("Failed to parse action")?
{
anyhow::ensure!(
find_action_by_name(action_name).is_some(),
"Action not found: {}",
action_name
);
}
}
}
}
"debug" => {
if !snippet_json_fixed.starts_with('[') || !snippet_json_fixed.ends_with(']') {
snippet_json_fixed.insert(0, '[');
snippet_json_fixed.push_str("\n]");
}
settings::parse_json_with_comments::<task::DebugTaskFile>(&snippet_json_fixed)?;
}
"tasks" => {
if !snippet_json_fixed.starts_with('[') || !snippet_json_fixed.ends_with(']') {
snippet_json_fixed.insert(0, '[');
snippet_json_fixed.push_str("\n]");
}
settings::parse_json_with_comments::<task::TaskTemplates>(&snippet_json_fixed)?;
}
"icon-theme" => {
if !snippet_json_fixed.starts_with('{') || !snippet_json_fixed.ends_with('}') {
snippet_json_fixed.insert(0, '{');
snippet_json_fixed.push_str("\n}");
}
settings::parse_json_with_comments::<theme::IconThemeFamilyContent>(
&snippet_json_fixed,
)?;
}
label => {
anyhow::bail!("Unexpected JSON code block tag: {}", label)
}
};
Ok(())
});
}
/// Removes any configurable options from the stringified action if existing,
/// ensuring that only the actual action name is returned. If the action consists
/// only of a string and nothing else, the string is returned as-is.

View File

@@ -2,7 +2,7 @@ use std::ops::Range;
use client::EditPredictionUsage;
use gpui::{App, Context, Entity, SharedString};
use language::{Anchor, Buffer, BufferSnapshot, OffsetRangeExt};
use language::Buffer;
// TODO: Find a better home for `Direction`.
//
@@ -242,51 +242,3 @@ where
self.update(cx, |this, cx| this.suggest(buffer, cursor_position, cx))
}
}
/// Returns edits updated based on user edits since the old snapshot. None is returned if any user
/// edit is not a prefix of a predicted insertion.
pub fn interpolate_edits(
old_snapshot: &BufferSnapshot,
new_snapshot: &BufferSnapshot,
current_edits: &[(Range<Anchor>, String)],
) -> Option<Vec<(Range<Anchor>, String)>> {
let mut edits = Vec::new();
let mut model_edits = current_edits.iter().peekable();
for user_edit in new_snapshot.edits_since::<usize>(&old_snapshot.version) {
while let Some((model_old_range, _)) = model_edits.peek() {
let model_old_range = model_old_range.to_offset(old_snapshot);
if model_old_range.end < user_edit.old.start {
let (model_old_range, model_new_text) = model_edits.next().unwrap();
edits.push((model_old_range.clone(), model_new_text.clone()));
} else {
break;
}
}
if let Some((model_old_range, model_new_text)) = model_edits.peek() {
let model_old_offset_range = model_old_range.to_offset(old_snapshot);
if user_edit.old == model_old_offset_range {
let user_new_text = new_snapshot
.text_for_range(user_edit.new.clone())
.collect::<String>();
if let Some(model_suffix) = model_new_text.strip_prefix(&user_new_text) {
if !model_suffix.is_empty() {
let anchor = old_snapshot.anchor_after(user_edit.old.end);
edits.push((anchor..anchor, model_suffix.to_string()));
}
model_edits.next();
continue;
}
}
}
return None;
}
edits.extend(model_edits.cloned());
if edits.is_empty() { None } else { Some(edits) }
}

View File

@@ -16,7 +16,6 @@ doctest = false
anyhow.workspace = true
client.workspace = true
cloud_llm_client.workspace = true
codestral.workspace = true
copilot.workspace = true
editor.workspace = true
feature_flags.workspace = true

View File

@@ -1,7 +1,6 @@
use anyhow::Result;
use client::{UserStore, zed_urls};
use cloud_llm_client::UsageLimit;
use codestral::CodestralCompletionProvider;
use copilot::{Copilot, Status};
use editor::{Editor, SelectionEffects, actions::ShowEditPrediction, scroll::Autoscroll};
use feature_flags::{FeatureFlagAppExt, PredictEditsRateCompletionsFeatureFlag};
@@ -235,67 +234,6 @@ impl Render for EditPredictionButton {
)
}
EditPredictionProvider::Codestral => {
let enabled = self.editor_enabled.unwrap_or(true);
let has_api_key = CodestralCompletionProvider::has_api_key(cx);
let fs = self.fs.clone();
let this = cx.entity();
div().child(
PopoverMenu::new("codestral")
.menu(move |window, cx| {
if has_api_key {
Some(this.update(cx, |this, cx| {
this.build_codestral_context_menu(window, cx)
}))
} else {
Some(ContextMenu::build(window, cx, |menu, _, _| {
let fs = fs.clone();
menu.entry("Use Zed AI instead", None, move |_, cx| {
set_completion_provider(
fs.clone(),
cx,
EditPredictionProvider::Zed,
)
})
.separator()
.entry(
"Configure Codestral API Key",
None,
move |window, cx| {
window.dispatch_action(
zed_actions::agent::OpenSettings.boxed_clone(),
cx,
);
},
)
}))
}
})
.anchor(Corner::BottomRight)
.trigger_with_tooltip(
IconButton::new("codestral-icon", IconName::AiMistral)
.shape(IconButtonShape::Square)
.when(!has_api_key, |this| {
this.indicator(Indicator::dot().color(Color::Error))
.indicator_border_color(Some(
cx.theme().colors().status_bar_background,
))
})
.when(has_api_key && !enabled, |this| {
this.indicator(Indicator::dot().color(Color::Ignored))
.indicator_border_color(Some(
cx.theme().colors().status_bar_background,
))
}),
move |window, cx| {
Tooltip::for_action("Codestral", &ToggleMenu, window, cx)
},
)
.with_handle(self.popover_menu_handle.clone()),
)
}
EditPredictionProvider::Zed => {
let enabled = self.editor_enabled.unwrap_or(true);
@@ -555,7 +493,6 @@ impl EditPredictionButton {
EditPredictionProvider::Zed
| EditPredictionProvider::Copilot
| EditPredictionProvider::Supermaven
| EditPredictionProvider::Codestral
) {
menu = menu
.separator()
@@ -782,25 +719,6 @@ impl EditPredictionButton {
})
}
fn build_codestral_context_menu(
&self,
window: &mut Window,
cx: &mut Context<Self>,
) -> Entity<ContextMenu> {
let fs = self.fs.clone();
ContextMenu::build(window, cx, |menu, window, cx| {
self.build_language_settings_menu(menu, window, cx)
.separator()
.entry("Use Zed AI instead", None, move |_, cx| {
set_completion_provider(fs.clone(), cx, EditPredictionProvider::Zed)
})
.separator()
.entry("Configure Codestral API Key", None, move |window, cx| {
window.dispatch_action(zed_actions::agent::OpenSettings.boxed_clone(), cx);
})
})
}
fn build_zeta_context_menu(
&self,
window: &mut Window,

View File

@@ -19,7 +19,6 @@ collections.workspace = true
futures.workspace = true
gpui.workspace = true
hashbrown.workspace = true
indoc.workspace = true
itertools.workspace = true
language.workspace = true
log.workspace = true
@@ -46,8 +45,5 @@ project = {workspace= true, features = ["test-support"]}
serde_json.workspace = true
settings = {workspace= true, features = ["test-support"]}
text = { workspace = true, features = ["test-support"] }
tree-sitter-c.workspace = true
tree-sitter-cpp.workspace = true
tree-sitter-go.workspace = true
util = { workspace = true, features = ["test-support"] }
zlog.workspace = true

View File

@@ -1,12 +1,9 @@
use cloud_llm_client::predict_edits_v3::{self, Line};
use language::{Language, LanguageId};
use language::LanguageId;
use project::ProjectEntryId;
use std::borrow::Cow;
use std::ops::Range;
use std::sync::Arc;
use std::{borrow::Cow, path::Path};
use text::{Bias, BufferId, Rope};
use util::paths::{path_ends_with, strip_path_suffix};
use util::rel_path::RelPath;
use crate::outline::OutlineDeclaration;
@@ -25,14 +22,12 @@ pub enum Declaration {
File {
project_entry_id: ProjectEntryId,
declaration: FileDeclaration,
cached_path: CachedDeclarationPath,
},
Buffer {
project_entry_id: ProjectEntryId,
buffer_id: BufferId,
rope: Rope,
declaration: BufferDeclaration,
cached_path: CachedDeclarationPath,
},
}
@@ -78,13 +73,6 @@ impl Declaration {
}
}
pub fn cached_path(&self) -> &CachedDeclarationPath {
match self {
Declaration::File { cached_path, .. } => cached_path,
Declaration::Buffer { cached_path, .. } => cached_path,
}
}
pub fn item_range(&self) -> Range<usize> {
match self {
Declaration::File { declaration, .. } => declaration.item_range.clone(),
@@ -92,18 +80,6 @@ impl Declaration {
}
}
pub fn item_line_range(&self) -> Range<Line> {
match self {
Declaration::File { declaration, .. } => declaration.item_line_range.clone(),
Declaration::Buffer {
declaration, rope, ..
} => {
Line(rope.offset_to_point(declaration.item_range.start).row)
..Line(rope.offset_to_point(declaration.item_range.end).row)
}
}
}
pub fn item_text(&self) -> (Cow<'_, str>, bool) {
match self {
Declaration::File { declaration, .. } => (
@@ -143,18 +119,6 @@ impl Declaration {
}
}
pub fn signature_line_range(&self) -> Range<Line> {
match self {
Declaration::File { declaration, .. } => declaration.signature_line_range.clone(),
Declaration::Buffer {
declaration, rope, ..
} => {
Line(rope.offset_to_point(declaration.signature_range.start).row)
..Line(rope.offset_to_point(declaration.signature_range.end).row)
}
}
}
pub fn signature_range_in_item_text(&self) -> Range<usize> {
let signature_range = self.signature_range();
let item_range = self.item_range();
@@ -167,7 +131,7 @@ fn expand_range_to_line_boundaries_and_truncate(
range: &Range<usize>,
limit: usize,
rope: &Rope,
) -> (Range<usize>, Range<predict_edits_v3::Line>, bool) {
) -> (Range<usize>, bool) {
let mut point_range = rope.offset_to_point(range.start)..rope.offset_to_point(range.end);
point_range.start.column = 0;
point_range.end.row += 1;
@@ -180,10 +144,7 @@ fn expand_range_to_line_boundaries_and_truncate(
item_range.end = item_range.start + limit;
}
item_range.end = rope.clip_offset(item_range.end, Bias::Left);
let line_range =
predict_edits_v3::Line(point_range.start.row)..predict_edits_v3::Line(point_range.end.row);
(item_range, line_range, is_truncated)
(item_range, is_truncated)
}
#[derive(Debug, Clone)]
@@ -192,30 +153,25 @@ pub struct FileDeclaration {
pub identifier: Identifier,
/// offset range of the declaration in the file, expanded to line boundaries and truncated
pub item_range: Range<usize>,
/// line range of the declaration in the file, potentially truncated
pub item_line_range: Range<predict_edits_v3::Line>,
/// text of `item_range`
pub text: Arc<str>,
/// whether `text` was truncated
pub text_is_truncated: bool,
/// offset range of the signature in the file, expanded to line boundaries and truncated
pub signature_range: Range<usize>,
/// line range of the signature in the file, truncated
pub signature_line_range: Range<Line>,
/// whether `signature` was truncated
pub signature_is_truncated: bool,
}
impl FileDeclaration {
pub fn from_outline(declaration: OutlineDeclaration, rope: &Rope) -> FileDeclaration {
let (item_range_in_file, item_line_range_in_file, text_is_truncated) =
expand_range_to_line_boundaries_and_truncate(
&declaration.item_range,
ITEM_TEXT_TRUNCATION_LENGTH,
rope,
);
let (item_range_in_file, text_is_truncated) = expand_range_to_line_boundaries_and_truncate(
&declaration.item_range,
ITEM_TEXT_TRUNCATION_LENGTH,
rope,
);
let (mut signature_range_in_file, signature_line_range, mut signature_is_truncated) =
let (mut signature_range_in_file, mut signature_is_truncated) =
expand_range_to_line_boundaries_and_truncate(
&declaration.signature_range,
ITEM_TEXT_TRUNCATION_LENGTH,
@@ -235,7 +191,6 @@ impl FileDeclaration {
parent: None,
identifier: declaration.identifier,
signature_range: signature_range_in_file,
signature_line_range,
signature_is_truncated,
text: rope
.chunks_in_range(item_range_in_file.clone())
@@ -243,7 +198,6 @@ impl FileDeclaration {
.into(),
text_is_truncated,
item_range: item_range_in_file,
item_line_range: item_line_range_in_file,
}
}
}
@@ -260,13 +214,12 @@ pub struct BufferDeclaration {
impl BufferDeclaration {
pub fn from_outline(declaration: OutlineDeclaration, rope: &Rope) -> Self {
let (item_range, _item_line_range, item_range_is_truncated) =
expand_range_to_line_boundaries_and_truncate(
&declaration.item_range,
ITEM_TEXT_TRUNCATION_LENGTH,
rope,
);
let (signature_range, _signature_line_range, signature_range_is_truncated) =
let (item_range, item_range_is_truncated) = expand_range_to_line_boundaries_and_truncate(
&declaration.item_range,
ITEM_TEXT_TRUNCATION_LENGTH,
rope,
);
let (signature_range, signature_range_is_truncated) =
expand_range_to_line_boundaries_and_truncate(
&declaration.signature_range,
ITEM_TEXT_TRUNCATION_LENGTH,
@@ -282,69 +235,3 @@ impl BufferDeclaration {
}
}
}
#[derive(Debug, Clone)]
pub struct CachedDeclarationPath {
pub worktree_abs_path: Arc<Path>,
pub rel_path: Arc<RelPath>,
/// The relative path of the file, possibly stripped according to `import_path_strip_regex`.
pub rel_path_after_regex_stripping: Arc<RelPath>,
}
impl CachedDeclarationPath {
pub fn new(
worktree_abs_path: Arc<Path>,
path: &Arc<RelPath>,
language: Option<&Arc<Language>>,
) -> Self {
let rel_path = path.clone();
let rel_path_after_regex_stripping = if let Some(language) = language
&& let Some(strip_regex) = language.config().import_path_strip_regex.as_ref()
&& let Ok(stripped) = RelPath::unix(&Path::new(
strip_regex.replace_all(rel_path.as_unix_str(), "").as_ref(),
)) {
Arc::from(stripped)
} else {
rel_path.clone()
};
CachedDeclarationPath {
worktree_abs_path,
rel_path,
rel_path_after_regex_stripping,
}
}
#[cfg(test)]
pub fn new_for_test(worktree_abs_path: &str, rel_path: &str) -> Self {
let rel_path: Arc<RelPath> = util::rel_path::rel_path(rel_path).into();
CachedDeclarationPath {
worktree_abs_path: std::path::PathBuf::from(worktree_abs_path).into(),
rel_path_after_regex_stripping: rel_path.clone(),
rel_path,
}
}
pub fn ends_with_posix_path(&self, path: &Path) -> bool {
if path.as_os_str().len() <= self.rel_path_after_regex_stripping.as_unix_str().len() {
path_ends_with(self.rel_path_after_regex_stripping.as_std_path(), path)
} else {
if let Some(remaining) =
strip_path_suffix(path, self.rel_path_after_regex_stripping.as_std_path())
{
path_ends_with(&self.worktree_abs_path, remaining)
} else {
false
}
}
}
pub fn equals_absolute_path(&self, path: &Path) -> bool {
if let Some(remaining) =
strip_path_suffix(path, &self.rel_path_after_regex_stripping.as_std_path())
{
self.worktree_abs_path.as_ref() == remaining
} else {
false
}
}
}

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