Compare commits

...

20 commits

Author SHA1 Message Date
Peter Tripp
52a60e5115
ci: Switch to Namespace (#35835)
Follow-up to:
- https://github.com/zed-industries/zed/pull/35826

Release Notes:

- N/A
2025-08-07 19:17:25 -04:00
Peter Tripp
8b25b8172b
ci: Switch from BuildJet to GitHub runners (#35826)
In response to an ongoing BuildJet outage, consider migrating CI to
GitHub hosted runners.

Also includes revert of (causing flaky tests):
- https://github.com/zed-industries/zed/pull/35741

Downsides:
- Cost (2x)
- Force migration to Ubuntu 22.04 from 20.04 will bump our glibc minimum
from 2.31 to 2.35. Which would break RHEL 9.x (glibc 2.34), Ubuntu 20.04
(EOL) and derivatives.

Release Notes:

- N/A
2025-08-07 19:16:03 -04:00
Joseph T. Lyons
09845c0a3a zed 0.198.3 2025-08-07 15:16:01 -04:00
Richard Feldman
a6b9668355 Use gpt-4o tokenizer for gpt-5 for now 2025-08-07 15:11:18 -04:00
Richard Feldman
d5b6a4d710 Update GPT-5 input/output token counts 2025-08-07 15:11:18 -04:00
Richard Feldman
bf6f715961 Add GPT-5 support through OpenAI API 2025-08-07 15:11:18 -04:00
Joseph T. Lyons
1934e5c23e v0.198.x stable 2025-08-06 08:40:17 -04:00
Max Brunsfeld
295da0757e Respect paths' content masks when copying them from MSAA texture to drawable (#35688)
Fixes a regression introduced in
https://github.com/zed-industries/zed/pull/34992

Paths are rendered first to an intermediate MSAA texture, and then
copied to the final drawable. Because paths can have transparency, it's
important that pixels are not copied repeatedly if paths have
overlapping bounding boxes. When N paths have the same draw order, we
infer that they must have disjoint bounding boxes, so that we can copy
them each individually (as opposed to copying a single rect that
contains them all). Previously, the bounding box that we were using to
copy paths was not accounting for the path's content mask (but it is
accounted for in the bounds tree that determines their draw order).

This cause bugs like this, where certain path pixels spuriously had
their opacity doubled:

https://github.com/user-attachments/assets/d792e60c-790b-49ad-b435-6695daba430f

This PR fixes that bug.

* [x] mac
* [x] linux
* [x] windows

Release Notes:

- Fixed a bug where a selection's opacity was computed incorrectly when
it overlapped with another editor's selections in a certain way.
2025-08-05 20:41:47 -07:00
Smit Barmase
85261bb5cc assistant_tool: Fix rejecting edits deletes newly created and accepted files (#35622)
Closes #34108
Closes #33234

This PR fixes a bug where a file remained in a Created state after
accept, causing following reject actions to incorrectly delete the file
instead of reverting back to previous state. Now it changes it to
Modified state upon "Accept All" and "Accept Hunk" (when all edits are
accepted).

- [x] Tests

Release Notes:

- Fixed issue where rejecting AI edits on newly created files would
delete the file instead of reverting to previous accepted state.
2025-08-06 07:30:39 +05:30
Joseph T. Lyons
8c159d0fbd zed 0.198.2 2025-08-05 15:08:47 -04:00
Richard Feldman
25f3f88a34 Add Claude Opus 4.1 (#35653)
<img width="348" height="427" alt="Screenshot 2025-08-05 at 1 55 35 PM"
src="https://github.com/user-attachments/assets/52af17a5-0095-4ad9-9afe-ff27aab90e03"
/>

Release Notes:

- Added support for Claude Opus 4.1

Co-authored-by: Marshall Bowers <git@maxdeviant.com>
2025-08-05 14:59:12 -04:00
Peter Tripp
0c24950911
ci: Double Buildjet ARM runner size (24GB to 48GB ram) (#35654)
Release Notes:

- N/A
2025-08-05 14:11:24 -04:00
Peter Tripp
900fe328c0
Fix escape in terminal with JetBrains keymap (#35585)
Closes https://github.com/zed-industries/zed/issues/35429
Closes https://github.com/zed-industries/zed/issues/35091
Follow-up to: https://github.com/zed-industries/zed/pull/35230

Release Notes:

- Fix `escape` in Terminal broken in JetBrains compatability keymaps
2025-08-05 14:10:31 -04:00
Michael Sloan
863e39b774
Cherry-pick #35513 onto v0.198.x (#35591)
Release Notes:

- N/A
2025-08-04 11:20:15 -06:00
Joseph T. Lyons
7d58eb200b zed 0.198.1 2025-08-01 14:15:54 -04:00
gcp-cherry-pick-bot[bot]
87c9f6a52e
debugger: Send initialized event from fake server at a more realistic time (cherry-pick #35446) (#35447)
Cherry-picked debugger: Send initialized event from fake server at a
more realistic time (#35446)

The spec says:

> ⬅️ Initialized Event
> This event indicates that the debug adapter is ready to accept
configuration requests (e.g. setBreakpoints, setExceptionBreakpoints).
>
> A debug adapter is expected to send this event when it is ready to
accept configuration requests (but not before the initialize request has
finished).

Previously in tests, `intercept_debug_sessions` was just spawning off a
background task to send the event after setting up the client, so the
event wasn't actually synchronized with the flow of messages in the way
the spec says it should be. This PR makes it so that the `FakeTransport`
injects the event right after a successful response to the initialize
request, and doesn't send it otherwise.

Release Notes:

- N/A

Co-authored-by: Cole Miller <cole@zed.dev>
2025-08-01 11:23:30 -04:00
gcp-cherry-pick-bot[bot]
059a409235
Fix panic with completion ranges and autoclose regions interop (cherry-pick #35408) (#35414)
Cherry-picked Fix panic with completion ranges and autoclose regions
interop (#35408)

As reported [in

Discord](https://discord.com/channels/869392257814519848/1106226198494859355/1398470747227426948)
C projects with `"` as "brackets" that autoclose, may invoke panics when
edited at the end of the file.

With a single selection-caret (`ˇ`), at the end of the file,
```c
ifndef BAR_H
#define BAR_H

#include <stdbool.h>

int fn_branch(bool do_branch1, bool do_branch2);

#endif // BAR_H
#include"ˇ"
```
gets an LSP response from clangd
```jsonc
{
  "filterText": "AGL/",
  "insertText": "AGL/",
  "insertTextFormat": 1,
  "kind": 17,
  "label": " AGL/",
  "labelDetails": {},
  "score": 0.78725427389144897,
  "sortText": "40b67681AGL/",
  "textEdit": {
    "newText": "AGL/",
    "range": { "end": { "character": 11, "line": 8 }, "start": { "character": 10, "line": 8 } }
  }
}
```

which replaces `"` after the caret (character/column 11, 0-indexed).
This is reasonable, as regular follow-up (proposed in further
completions), is a suffix + a closing `"`:

<img width="842" height="259" alt="image"

src="https://github.com/user-attachments/assets/ea56f621-7008-4ce2-99ba-87344ddf33d2"
/>

Yet when Zed handles user input of `"`, it panics due to multiple
reasons:

* after applying any snippet text edit, Zed did a selection change:

5537987630/crates/editor/src/editor.rs (L9539-L9545)
which caused eventual autoclose region invalidation:

5537987630/crates/editor/src/editor.rs (L2970)

This covers all cases that insert the `include""` text.

* after applying any user input and "plain" text edit, Zed did not
invalidate any autoclose regions at all, relying on the "bracket" (which
includes `"`) autoclose logic to rule edge cases out

* bracket autoclose logic detects previous `"` and considers the new
user input as a valid closure, hence no autoclose region needed.
But there is an autoclose bracket data after the plaintext completion
insertion (`AGL/`) really, and it's not invalidated after `"` handling

* in addition to that, `Anchor::is_valid` method in `text` panicked, and
required `fn try_fragment_id_for_anchor` to handle "pointing at odd,
after the end of the file, offset" cases as `false`

A test reproducing the feedback and 2 fixes added: proper, autoclose
region invalidation call which required the invalidation logic tweaked a
bit, and "superficial", "do not apply bad selections that cause panics"
fix in the editor to be more robust

Release Notes:

- Fixed panic with completion ranges and autoclose regions interop

---------

Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>

Co-authored-by: Kirill Bulatov <kirill@zed.dev>
Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
2025-08-01 08:39:10 +03:00
Peter Tripp
5c450693fa
jetbrains: Unmap cmd-k in Jetbrains keymap (#35443)
This only works after a delay in most situations because of the all
chorded `cmd-k` mappings in the so disable them for now.

Reported by @jer-k:
https://x.com/J_Kreutzbender/status/1951033355434336606

Release Notes:

- Undo mapping of `cmd-k` for Git Panel in default Jetbrains keymap
(thanks [@jer-k](https://github.com/jer-k))
2025-07-31 18:30:09 -04:00
gcp-cherry-pick-bot[bot]
910507d7e5
linux: Fix caps lock not working consistently for certain X11 systems (cherry-pick #35361) (#35365)
Cherry-picked linux: Fix caps lock not working consistently for certain
X11 systems (#35361)

Closes #35316

Bug in https://github.com/zed-industries/zed/pull/34514

Turns out you are not supposed to call `update_key` for modifiers on
`KeyPress`/`KeyRelease`, as modifiers are already updated in
`XkbStateNotify` events. Not sure why this only causes issues on a few
systems and works on others.

Tested on Ubuntu 24.04.2 LTS (initial bug) and Kubuntu 25.04 (worked
fine before too).

Release Notes:

- Fixed an issue where caps lock stopped working consistently on some
Linux X11 systems.

Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
2025-07-31 01:22:41 +05:30
Joseph T. Lyons
3d48f14248 v0.198.x preview 2025-07-30 12:07:29 -04:00
50 changed files with 647 additions and 176 deletions

View file

@ -5,26 +5,25 @@ self-hosted-runner:
# GitHub-hosted Runners
- github-8vcpu-ubuntu-2404
- github-16vcpu-ubuntu-2404
- github-32vcpu-ubuntu-2404
- github-8vcpu-ubuntu-2204
- github-16vcpu-ubuntu-2204
- github-32vcpu-ubuntu-2204
- github-16vcpu-ubuntu-2204-arm
- windows-2025-16
- windows-2025-32
- windows-2025-64
# Buildjet Ubuntu 20.04 - AMD x86_64
- buildjet-2vcpu-ubuntu-2004
- buildjet-4vcpu-ubuntu-2004
- buildjet-8vcpu-ubuntu-2004
- buildjet-16vcpu-ubuntu-2004
- buildjet-32vcpu-ubuntu-2004
# Buildjet Ubuntu 22.04 - AMD x86_64
- buildjet-2vcpu-ubuntu-2204
- buildjet-4vcpu-ubuntu-2204
- buildjet-8vcpu-ubuntu-2204
- buildjet-16vcpu-ubuntu-2204
- buildjet-32vcpu-ubuntu-2204
# Buildjet Ubuntu 22.04 - Graviton aarch64
- buildjet-8vcpu-ubuntu-2204-arm
- buildjet-16vcpu-ubuntu-2204-arm
- buildjet-32vcpu-ubuntu-2204-arm
- buildjet-64vcpu-ubuntu-2204-arm
# Namespace Ubuntu 20.04 (Release builds)
- namespace-profile-16x32-ubuntu-2004
- namespace-profile-32x64-ubuntu-2004
- namespace-profile-16x32-ubuntu-2004-arm
- namespace-profile-32x64-ubuntu-2004-arm
# Namespace Ubuntu 22.04 (Everything else)
- namespace-profile-2x4-ubuntu-2204
- namespace-profile-4x8-ubuntu-2204
- namespace-profile-8x16-ubuntu-2204
- namespace-profile-16x32-ubuntu-2204
- namespace-profile-32x64-ubuntu-2204
# Self Hosted Runners
- self-mini-macos
- self-32vcpu-windows-2022

View file

@ -13,7 +13,7 @@ runs:
uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
cache-provider: "buildjet"
# cache-provider: "buildjet"
- name: Install Linux dependencies
shell: bash -euxo pipefail {0}

View file

@ -16,7 +16,7 @@ jobs:
bump_patch_version:
if: github.repository_owner == 'zed-industries'
runs-on:
- buildjet-16vcpu-ubuntu-2204
- namespace-profile-16x32-ubuntu-2204
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4

View file

@ -136,7 +136,7 @@ jobs:
github.repository_owner == 'zed-industries' &&
needs.job_spec.outputs.run_tests == 'true'
runs-on:
- buildjet-8vcpu-ubuntu-2204
- namespace-profile-8x16-ubuntu-2204
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
@ -167,7 +167,7 @@ jobs:
needs: [job_spec]
if: github.repository_owner == 'zed-industries'
runs-on:
- buildjet-8vcpu-ubuntu-2204
- namespace-profile-4x8-ubuntu-2204
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
@ -220,7 +220,7 @@ jobs:
github.repository_owner == 'zed-industries' &&
(needs.job_spec.outputs.run_tests == 'true' || needs.job_spec.outputs.run_docs == 'true')
runs-on:
- buildjet-8vcpu-ubuntu-2204
- namespace-profile-8x16-ubuntu-2204
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
@ -327,7 +327,7 @@ jobs:
github.repository_owner == 'zed-industries' &&
needs.job_spec.outputs.run_tests == 'true'
runs-on:
- buildjet-16vcpu-ubuntu-2204
- namespace-profile-16x32-ubuntu-2204
steps:
- name: Add Rust to the PATH
run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
@ -341,7 +341,7 @@ jobs:
uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
cache-provider: "buildjet"
# cache-provider: "buildjet"
- name: Install Linux dependencies
run: ./script/linux
@ -379,7 +379,7 @@ jobs:
github.repository_owner == 'zed-industries' &&
needs.job_spec.outputs.run_tests == 'true'
runs-on:
- buildjet-8vcpu-ubuntu-2204
- namespace-profile-16x32-ubuntu-2204
steps:
- name: Add Rust to the PATH
run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
@ -393,7 +393,7 @@ jobs:
uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
cache-provider: "buildjet"
# cache-provider: "buildjet"
- name: Install Clang & Mold
run: ./script/remote-server && ./script/install-mold 2.34.0
@ -596,7 +596,7 @@ jobs:
timeout-minutes: 60
name: Linux x86_x64 release bundle
runs-on:
- buildjet-16vcpu-ubuntu-2004 # ubuntu 20.04 for minimal glibc
- namespace-profile-16x32-ubuntu-2004 # ubuntu 20.04 for minimal glibc
if: |
startsWith(github.ref, 'refs/tags/v')
|| contains(github.event.pull_request.labels.*.name, 'run-bundling')
@ -649,7 +649,7 @@ jobs:
timeout-minutes: 60
name: Linux arm64 release bundle
runs-on:
- buildjet-16vcpu-ubuntu-2204-arm
- namespace-profile-32x64-ubuntu-2004-arm # ubuntu 20.04 for minimal glibc
if: |
startsWith(github.ref, 'refs/tags/v')
|| contains(github.event.pull_request.labels.*.name, 'run-bundling')

View file

@ -9,7 +9,7 @@ jobs:
deploy-docs:
name: Deploy Docs
if: github.repository_owner == 'zed-industries'
runs-on: buildjet-16vcpu-ubuntu-2204
runs-on: namespace-profile-16x32-ubuntu-2204
steps:
- name: Checkout repo

View file

@ -61,7 +61,7 @@ jobs:
- style
- tests
runs-on:
- buildjet-16vcpu-ubuntu-2204
- namespace-profile-16x32-ubuntu-2204
steps:
- name: Install doctl
uses: digitalocean/action-doctl@v2
@ -94,7 +94,7 @@ jobs:
needs:
- publish
runs-on:
- buildjet-16vcpu-ubuntu-2204
- namespace-profile-16x32-ubuntu-2204
steps:
- name: Checkout repo

View file

@ -32,7 +32,7 @@ jobs:
github.repository_owner == 'zed-industries' &&
(github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'run-eval'))
runs-on:
- buildjet-16vcpu-ubuntu-2204
- namespace-profile-16x32-ubuntu-2204
steps:
- name: Add Rust to the PATH
run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
@ -46,7 +46,7 @@ jobs:
uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
cache-provider: "buildjet"
# cache-provider: "buildjet"
- name: Install Linux dependencies
run: ./script/linux

View file

@ -20,7 +20,7 @@ jobs:
matrix:
system:
- os: x86 Linux
runner: buildjet-16vcpu-ubuntu-2204
runner: namespace-profile-16x32-ubuntu-2204
install_nix: true
- os: arm Mac
runner: [macOS, ARM64, test]

View file

@ -20,7 +20,7 @@ jobs:
name: Run randomized tests
if: github.repository_owner == 'zed-industries'
runs-on:
- buildjet-16vcpu-ubuntu-2204
- namespace-profile-16x32-ubuntu-2204
steps:
- name: Install Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4

View file

@ -127,7 +127,7 @@ jobs:
name: Create a Linux *.tar.gz bundle for x86
if: github.repository_owner == 'zed-industries'
runs-on:
- buildjet-16vcpu-ubuntu-2004
- namespace-profile-16x32-ubuntu-2004 # ubuntu 20.04 for minimal glibc
needs: tests
steps:
- name: Checkout repo
@ -167,7 +167,7 @@ jobs:
name: Create a Linux *.tar.gz bundle for ARM
if: github.repository_owner == 'zed-industries'
runs-on:
- buildjet-16vcpu-ubuntu-2204-arm
- namespace-profile-32x64-ubuntu-2004-arm # ubuntu 20.04 for minimal glibc
needs: tests
steps:
- name: Checkout repo

View file

@ -23,7 +23,7 @@ jobs:
timeout-minutes: 60
name: Run unit evals
runs-on:
- buildjet-16vcpu-ubuntu-2204
- namespace-profile-16x32-ubuntu-2204
steps:
- name: Add Rust to the PATH
run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
@ -37,7 +37,7 @@ jobs:
uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
cache-provider: "buildjet"
# cache-provider: "buildjet"
- name: Install Linux dependencies
run: ./script/linux

4
Cargo.lock generated
View file

@ -4960,6 +4960,7 @@ dependencies = [
"theme",
"time",
"tree-sitter-bash",
"tree-sitter-c",
"tree-sitter-html",
"tree-sitter-python",
"tree-sitter-rust",
@ -6362,7 +6363,6 @@ dependencies = [
"buffer_diff",
"call",
"chrono",
"client",
"cloud_llm_client",
"collections",
"command_palette_hooks",
@ -20193,7 +20193,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.198.0"
version = "0.198.3"
dependencies = [
"activity_indicator",
"agent",

View file

@ -95,7 +95,7 @@
"ctrl-shift-r": ["pane::DeploySearch", { "replace_enabled": true }],
"alt-shift-f10": "task::Spawn",
"ctrl-e": "file_finder::Toggle",
"ctrl-k": "git_panel::ToggleFocus", // bug: This should also focus commit editor
// "ctrl-k": "git_panel::ToggleFocus", // bug: This should also focus commit editor
"ctrl-shift-n": "file_finder::Toggle",
"ctrl-shift-a": "command_palette::Toggle",
"shift shift": "command_palette::Toggle",
@ -166,7 +166,7 @@
{ "context": "Diagnostics > Editor", "bindings": { "alt-6": "pane::CloseActiveItem" } },
{ "context": "OutlinePanel", "bindings": { "alt-7": "workspace::CloseActiveDock" } },
{
"context": "Dock || Workspace || Terminal || OutlinePanel || ProjectPanel || CollabPanel || (Editor && mode == auto_height)",
"context": "Dock || Workspace || OutlinePanel || ProjectPanel || CollabPanel || (Editor && mode == auto_height)",
"bindings": {
"escape": "editor::ToggleFocus",
"shift-escape": "workspace::CloseActiveDock"

View file

@ -97,7 +97,7 @@
"cmd-shift-r": ["pane::DeploySearch", { "replace_enabled": true }],
"ctrl-alt-r": "task::Spawn",
"cmd-e": "file_finder::Toggle",
"cmd-k": "git_panel::ToggleFocus", // bug: This should also focus commit editor
// "cmd-k": "git_panel::ToggleFocus", // bug: This should also focus commit editor
"cmd-shift-o": "file_finder::Toggle",
"cmd-shift-a": "command_palette::Toggle",
"shift shift": "command_palette::Toggle",
@ -167,7 +167,7 @@
{ "context": "Diagnostics > Editor", "bindings": { "cmd-6": "pane::CloseActiveItem" } },
{ "context": "OutlinePanel", "bindings": { "cmd-7": "workspace::CloseActiveDock" } },
{
"context": "Dock || Workspace || Terminal || OutlinePanel || ProjectPanel || CollabPanel || (Editor && mode == auto_height)",
"context": "Dock || Workspace || OutlinePanel || ProjectPanel || CollabPanel || (Editor && mode == auto_height)",
"bindings": {
"escape": "editor::ToggleFocus",
"shift-escape": "workspace::CloseActiveDock"

View file

@ -43,7 +43,7 @@ use anyhow::{Result, anyhow};
use assistant_context::{AssistantContext, ContextEvent, ContextSummary};
use assistant_slash_command::SlashCommandWorkingSet;
use assistant_tool::ToolWorkingSet;
use client::{DisableAiSettings, UserStore, zed_urls};
use client::{UserStore, zed_urls};
use cloud_llm_client::{CompletionIntent, UsageLimit};
use editor::{Anchor, AnchorRangeExt as _, Editor, EditorEvent, MultiBuffer};
use feature_flags::{self, FeatureFlagAppExt};
@ -58,7 +58,7 @@ use language::LanguageRegistry;
use language_model::{
ConfigurationError, ConfiguredModel, LanguageModelProviderTosView, LanguageModelRegistry,
};
use project::{Project, ProjectPath, Worktree};
use project::{DisableAiSettings, Project, ProjectPath, Worktree};
use prompt_store::{PromptBuilder, PromptStore, UserPromptId};
use proto::Plan;
use rules_library::{RulesLibrary, open_rules_library};

View file

@ -31,7 +31,7 @@ use std::sync::Arc;
use agent::{Thread, ThreadId};
use agent_settings::{AgentProfileId, AgentSettings, LanguageModelSelection};
use assistant_slash_command::SlashCommandRegistry;
use client::{Client, DisableAiSettings};
use client::Client;
use command_palette_hooks::CommandPaletteFilter;
use feature_flags::FeatureFlagAppExt as _;
use fs::Fs;
@ -40,6 +40,7 @@ use language::LanguageRegistry;
use language_model::{
ConfiguredModel, LanguageModel, LanguageModelId, LanguageModelProviderId, LanguageModelRegistry,
};
use project::DisableAiSettings;
use prompt_store::PromptBuilder;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

View file

@ -16,7 +16,7 @@ use agent::{
};
use agent_settings::AgentSettings;
use anyhow::{Context as _, Result};
use client::{DisableAiSettings, telemetry::Telemetry};
use client::telemetry::Telemetry;
use collections::{HashMap, HashSet, VecDeque, hash_map};
use editor::SelectionEffects;
use editor::{
@ -39,7 +39,7 @@ use language_model::{
};
use multi_buffer::MultiBufferRow;
use parking_lot::Mutex;
use project::{CodeAction, LspAction, Project, ProjectTransaction};
use project::{CodeAction, DisableAiSettings, LspAction, Project, ProjectTransaction};
use prompt_store::{PromptBuilder, PromptStore};
use settings::{Settings, SettingsStore};
use telemetry_events::{AssistantEventData, AssistantKind, AssistantPhase};

View file

@ -36,11 +36,18 @@ pub enum AnthropicModelMode {
pub enum Model {
#[serde(rename = "claude-opus-4", alias = "claude-opus-4-latest")]
ClaudeOpus4,
#[serde(rename = "claude-opus-4-1", alias = "claude-opus-4-1-latest")]
ClaudeOpus4_1,
#[serde(
rename = "claude-opus-4-thinking",
alias = "claude-opus-4-thinking-latest"
)]
ClaudeOpus4Thinking,
#[serde(
rename = "claude-opus-4-1-thinking",
alias = "claude-opus-4-1-thinking-latest"
)]
ClaudeOpus4_1Thinking,
#[default]
#[serde(rename = "claude-sonnet-4", alias = "claude-sonnet-4-latest")]
ClaudeSonnet4,
@ -91,10 +98,18 @@ impl Model {
}
pub fn from_id(id: &str) -> Result<Self> {
if id.starts_with("claude-opus-4-1-thinking") {
return Ok(Self::ClaudeOpus4_1Thinking);
}
if id.starts_with("claude-opus-4-thinking") {
return Ok(Self::ClaudeOpus4Thinking);
}
if id.starts_with("claude-opus-4-1") {
return Ok(Self::ClaudeOpus4_1);
}
if id.starts_with("claude-opus-4") {
return Ok(Self::ClaudeOpus4);
}
@ -141,7 +156,9 @@ impl Model {
pub fn id(&self) -> &str {
match self {
Self::ClaudeOpus4 => "claude-opus-4-latest",
Self::ClaudeOpus4_1 => "claude-opus-4-1-latest",
Self::ClaudeOpus4Thinking => "claude-opus-4-thinking-latest",
Self::ClaudeOpus4_1Thinking => "claude-opus-4-1-thinking-latest",
Self::ClaudeSonnet4 => "claude-sonnet-4-latest",
Self::ClaudeSonnet4Thinking => "claude-sonnet-4-thinking-latest",
Self::Claude3_5Sonnet => "claude-3-5-sonnet-latest",
@ -159,6 +176,7 @@ impl Model {
pub fn request_id(&self) -> &str {
match self {
Self::ClaudeOpus4 | Self::ClaudeOpus4Thinking => "claude-opus-4-20250514",
Self::ClaudeOpus4_1 | Self::ClaudeOpus4_1Thinking => "claude-opus-4-1-20250805",
Self::ClaudeSonnet4 | Self::ClaudeSonnet4Thinking => "claude-sonnet-4-20250514",
Self::Claude3_5Sonnet => "claude-3-5-sonnet-latest",
Self::Claude3_7Sonnet | Self::Claude3_7SonnetThinking => "claude-3-7-sonnet-latest",
@ -173,7 +191,9 @@ impl Model {
pub fn display_name(&self) -> &str {
match self {
Self::ClaudeOpus4 => "Claude Opus 4",
Self::ClaudeOpus4_1 => "Claude Opus 4.1",
Self::ClaudeOpus4Thinking => "Claude Opus 4 Thinking",
Self::ClaudeOpus4_1Thinking => "Claude Opus 4.1 Thinking",
Self::ClaudeSonnet4 => "Claude Sonnet 4",
Self::ClaudeSonnet4Thinking => "Claude Sonnet 4 Thinking",
Self::Claude3_7Sonnet => "Claude 3.7 Sonnet",
@ -192,7 +212,9 @@ impl Model {
pub fn cache_configuration(&self) -> Option<AnthropicModelCacheConfiguration> {
match self {
Self::ClaudeOpus4
| Self::ClaudeOpus4_1
| Self::ClaudeOpus4Thinking
| Self::ClaudeOpus4_1Thinking
| Self::ClaudeSonnet4
| Self::ClaudeSonnet4Thinking
| Self::Claude3_5Sonnet
@ -215,7 +237,9 @@ impl Model {
pub fn max_token_count(&self) -> u64 {
match self {
Self::ClaudeOpus4
| Self::ClaudeOpus4_1
| Self::ClaudeOpus4Thinking
| Self::ClaudeOpus4_1Thinking
| Self::ClaudeSonnet4
| Self::ClaudeSonnet4Thinking
| Self::Claude3_5Sonnet
@ -232,7 +256,9 @@ impl Model {
pub fn max_output_tokens(&self) -> u64 {
match self {
Self::ClaudeOpus4
| Self::ClaudeOpus4_1
| Self::ClaudeOpus4Thinking
| Self::ClaudeOpus4_1Thinking
| Self::ClaudeSonnet4
| Self::ClaudeSonnet4Thinking
| Self::Claude3_5Sonnet
@ -249,7 +275,9 @@ impl Model {
pub fn default_temperature(&self) -> f32 {
match self {
Self::ClaudeOpus4
| Self::ClaudeOpus4_1
| Self::ClaudeOpus4Thinking
| Self::ClaudeOpus4_1Thinking
| Self::ClaudeSonnet4
| Self::ClaudeSonnet4Thinking
| Self::Claude3_5Sonnet
@ -269,6 +297,7 @@ impl Model {
pub fn mode(&self) -> AnthropicModelMode {
match self {
Self::ClaudeOpus4
| Self::ClaudeOpus4_1
| Self::ClaudeSonnet4
| Self::Claude3_5Sonnet
| Self::Claude3_7Sonnet
@ -277,6 +306,7 @@ impl Model {
| Self::Claude3Sonnet
| Self::Claude3Haiku => AnthropicModelMode::Default,
Self::ClaudeOpus4Thinking
| Self::ClaudeOpus4_1Thinking
| Self::ClaudeSonnet4Thinking
| Self::Claude3_7SonnetThinking => AnthropicModelMode::Thinking {
budget_tokens: Some(4_096),

View file

@ -630,6 +630,11 @@ impl ActionLog {
false
}
});
if tracked_buffer.unreviewed_edits.is_empty() {
if let TrackedBufferStatus::Created { .. } = &mut tracked_buffer.status {
tracked_buffer.status = TrackedBufferStatus::Modified;
}
}
tracked_buffer.schedule_diff_update(ChangeAuthor::User, cx);
}
}
@ -775,6 +780,9 @@ impl ActionLog {
.retain(|_buffer, tracked_buffer| match tracked_buffer.status {
TrackedBufferStatus::Deleted => false,
_ => {
if let TrackedBufferStatus::Created { .. } = &mut tracked_buffer.status {
tracked_buffer.status = TrackedBufferStatus::Modified;
}
tracked_buffer.unreviewed_edits.clear();
tracked_buffer.diff_base = tracked_buffer.snapshot.as_rope().clone();
tracked_buffer.schedule_diff_update(ChangeAuthor::User, cx);
@ -2075,6 +2083,134 @@ mod tests {
assert_eq!(content, "ai content\nuser added this line");
}
#[gpui::test]
async fn test_reject_after_accepting_hunk_on_created_file(cx: &mut TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor());
let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
let action_log = cx.new(|_| ActionLog::new(project.clone()));
let file_path = project
.read_with(cx, |project, cx| {
project.find_project_path("dir/new_file", cx)
})
.unwrap();
let buffer = project
.update(cx, |project, cx| project.open_buffer(file_path.clone(), cx))
.await
.unwrap();
// AI creates file with initial content
cx.update(|cx| {
action_log.update(cx, |log, cx| log.buffer_created(buffer.clone(), cx));
buffer.update(cx, |buffer, cx| buffer.set_text("ai content v1", cx));
action_log.update(cx, |log, cx| log.buffer_edited(buffer.clone(), cx));
});
project
.update(cx, |project, cx| project.save_buffer(buffer.clone(), cx))
.await
.unwrap();
cx.run_until_parked();
assert_ne!(unreviewed_hunks(&action_log, cx), vec![]);
// User accepts the single hunk
action_log.update(cx, |log, cx| {
log.keep_edits_in_range(buffer.clone(), Anchor::MIN..Anchor::MAX, cx)
});
cx.run_until_parked();
assert_eq!(unreviewed_hunks(&action_log, cx), vec![]);
assert!(fs.is_file(path!("/dir/new_file").as_ref()).await);
// AI modifies the file
cx.update(|cx| {
buffer.update(cx, |buffer, cx| buffer.set_text("ai content v2", cx));
action_log.update(cx, |log, cx| log.buffer_edited(buffer.clone(), cx));
});
project
.update(cx, |project, cx| project.save_buffer(buffer.clone(), cx))
.await
.unwrap();
cx.run_until_parked();
assert_ne!(unreviewed_hunks(&action_log, cx), vec![]);
// User rejects the hunk
action_log
.update(cx, |log, cx| {
log.reject_edits_in_ranges(buffer.clone(), vec![Anchor::MIN..Anchor::MAX], cx)
})
.await
.unwrap();
cx.run_until_parked();
assert!(fs.is_file(path!("/dir/new_file").as_ref()).await,);
assert_eq!(
buffer.read_with(cx, |buffer, _| buffer.text()),
"ai content v1"
);
assert_eq!(unreviewed_hunks(&action_log, cx), vec![]);
}
#[gpui::test]
async fn test_reject_edits_on_previously_accepted_created_file(cx: &mut TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor());
let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
let action_log = cx.new(|_| ActionLog::new(project.clone()));
let file_path = project
.read_with(cx, |project, cx| {
project.find_project_path("dir/new_file", cx)
})
.unwrap();
let buffer = project
.update(cx, |project, cx| project.open_buffer(file_path.clone(), cx))
.await
.unwrap();
// AI creates file with initial content
cx.update(|cx| {
action_log.update(cx, |log, cx| log.buffer_created(buffer.clone(), cx));
buffer.update(cx, |buffer, cx| buffer.set_text("ai content v1", cx));
action_log.update(cx, |log, cx| log.buffer_edited(buffer.clone(), cx));
});
project
.update(cx, |project, cx| project.save_buffer(buffer.clone(), cx))
.await
.unwrap();
cx.run_until_parked();
// User clicks "Accept All"
action_log.update(cx, |log, cx| log.keep_all_edits(cx));
cx.run_until_parked();
assert!(fs.is_file(path!("/dir/new_file").as_ref()).await);
assert_eq!(unreviewed_hunks(&action_log, cx), vec![]); // Hunks are cleared
// AI modifies file again
cx.update(|cx| {
buffer.update(cx, |buffer, cx| buffer.set_text("ai content v2", cx));
action_log.update(cx, |log, cx| log.buffer_edited(buffer.clone(), cx));
});
project
.update(cx, |project, cx| project.save_buffer(buffer.clone(), cx))
.await
.unwrap();
cx.run_until_parked();
assert_ne!(unreviewed_hunks(&action_log, cx), vec![]);
// User clicks "Reject All"
action_log
.update(cx, |log, cx| log.reject_all_edits(cx))
.await;
cx.run_until_parked();
assert!(fs.is_file(path!("/dir/new_file").as_ref()).await);
assert_eq!(
buffer.read_with(cx, |buffer, _| buffer.text()),
"ai content v1"
);
assert_eq!(unreviewed_hunks(&action_log, cx), vec![]);
}
#[gpui::test(iterations = 100)]
async fn test_random_diffs(mut rng: StdRng, cx: &mut TestAppContext) {
init_test(cx);

View file

@ -32,11 +32,18 @@ pub enum Model {
ClaudeSonnet4Thinking,
#[serde(rename = "claude-opus-4", alias = "claude-opus-4-latest")]
ClaudeOpus4,
#[serde(rename = "claude-opus-4-1", alias = "claude-opus-4-1-latest")]
ClaudeOpus4_1,
#[serde(
rename = "claude-opus-4-thinking",
alias = "claude-opus-4-thinking-latest"
)]
ClaudeOpus4Thinking,
#[serde(
rename = "claude-opus-4-1-thinking",
alias = "claude-opus-4-1-thinking-latest"
)]
ClaudeOpus4_1Thinking,
#[serde(rename = "claude-3-5-sonnet-v2", alias = "claude-3-5-sonnet-latest")]
Claude3_5SonnetV2,
#[serde(rename = "claude-3-7-sonnet", alias = "claude-3-7-sonnet-latest")]
@ -147,7 +154,9 @@ impl Model {
Model::ClaudeSonnet4 => "claude-4-sonnet",
Model::ClaudeSonnet4Thinking => "claude-4-sonnet-thinking",
Model::ClaudeOpus4 => "claude-4-opus",
Model::ClaudeOpus4_1 => "claude-4-opus-1",
Model::ClaudeOpus4Thinking => "claude-4-opus-thinking",
Model::ClaudeOpus4_1Thinking => "claude-4-opus-1-thinking",
Model::Claude3_5SonnetV2 => "claude-3-5-sonnet-v2",
Model::Claude3_5Sonnet => "claude-3-5-sonnet",
Model::Claude3Opus => "claude-3-opus",
@ -208,6 +217,9 @@ impl Model {
Model::ClaudeOpus4 | Model::ClaudeOpus4Thinking => {
"anthropic.claude-opus-4-20250514-v1:0"
}
Model::ClaudeOpus4_1 | Model::ClaudeOpus4_1Thinking => {
"anthropic.claude-opus-4-1-20250805-v1:0"
}
Model::Claude3_5SonnetV2 => "anthropic.claude-3-5-sonnet-20241022-v2:0",
Model::Claude3_5Sonnet => "anthropic.claude-3-5-sonnet-20240620-v1:0",
Model::Claude3Opus => "anthropic.claude-3-opus-20240229-v1:0",
@ -266,7 +278,9 @@ impl Model {
Self::ClaudeSonnet4 => "Claude Sonnet 4",
Self::ClaudeSonnet4Thinking => "Claude Sonnet 4 Thinking",
Self::ClaudeOpus4 => "Claude Opus 4",
Self::ClaudeOpus4_1 => "Claude Opus 4.1",
Self::ClaudeOpus4Thinking => "Claude Opus 4 Thinking",
Self::ClaudeOpus4_1Thinking => "Claude Opus 4.1 Thinking",
Self::Claude3_5SonnetV2 => "Claude 3.5 Sonnet v2",
Self::Claude3_5Sonnet => "Claude 3.5 Sonnet",
Self::Claude3Opus => "Claude 3 Opus",
@ -330,8 +344,10 @@ impl Model {
| Self::Claude3_7Sonnet
| Self::ClaudeSonnet4
| Self::ClaudeOpus4
| Self::ClaudeOpus4_1
| Self::ClaudeSonnet4Thinking
| Self::ClaudeOpus4Thinking => 200_000,
| Self::ClaudeOpus4Thinking
| Self::ClaudeOpus4_1Thinking => 200_000,
Self::AmazonNovaPremier => 1_000_000,
Self::PalmyraWriterX5 => 1_000_000,
Self::PalmyraWriterX4 => 128_000,
@ -348,7 +364,9 @@ impl Model {
| Self::ClaudeSonnet4
| Self::ClaudeSonnet4Thinking
| Self::ClaudeOpus4
| Model::ClaudeOpus4Thinking => 128_000,
| Model::ClaudeOpus4Thinking
| Self::ClaudeOpus4_1
| Model::ClaudeOpus4_1Thinking => 128_000,
Self::Claude3_5SonnetV2 | Self::PalmyraWriterX4 | Self::PalmyraWriterX5 => 8_192,
Self::Custom {
max_output_tokens, ..
@ -366,6 +384,8 @@ impl Model {
| Self::Claude3_7Sonnet
| Self::ClaudeOpus4
| Self::ClaudeOpus4Thinking
| Self::ClaudeOpus4_1
| Self::ClaudeOpus4_1Thinking
| Self::ClaudeSonnet4
| Self::ClaudeSonnet4Thinking => 1.0,
Self::Custom {
@ -387,6 +407,8 @@ impl Model {
| Self::Claude3_7SonnetThinking
| Self::ClaudeOpus4
| Self::ClaudeOpus4Thinking
| Self::ClaudeOpus4_1
| Self::ClaudeOpus4_1Thinking
| Self::ClaudeSonnet4
| Self::ClaudeSonnet4Thinking
| Self::Claude3_5Haiku => true,
@ -420,7 +442,9 @@ impl Model {
| Self::ClaudeSonnet4
| Self::ClaudeSonnet4Thinking
| Self::ClaudeOpus4
| Self::ClaudeOpus4Thinking => true,
| Self::ClaudeOpus4Thinking
| Self::ClaudeOpus4_1
| Self::ClaudeOpus4_1Thinking => true,
// Custom models - check if they have cache configuration
Self::Custom {
@ -440,7 +464,9 @@ impl Model {
| Self::ClaudeSonnet4
| Self::ClaudeSonnet4Thinking
| Self::ClaudeOpus4
| Self::ClaudeOpus4Thinking => Some(BedrockModelCacheConfiguration {
| Self::ClaudeOpus4Thinking
| Self::ClaudeOpus4_1
| Self::ClaudeOpus4_1Thinking => Some(BedrockModelCacheConfiguration {
max_cache_anchors: 4,
min_total_token: 1024,
}),
@ -467,9 +493,11 @@ impl Model {
Model::ClaudeSonnet4Thinking => BedrockModelMode::Thinking {
budget_tokens: Some(4096),
},
Model::ClaudeOpus4Thinking => BedrockModelMode::Thinking {
budget_tokens: Some(4096),
},
Model::ClaudeOpus4Thinking | Model::ClaudeOpus4_1Thinking => {
BedrockModelMode::Thinking {
budget_tokens: Some(4096),
}
}
_ => BedrockModelMode::Default,
}
}
@ -518,6 +546,8 @@ impl Model {
| Model::ClaudeSonnet4Thinking
| Model::ClaudeOpus4
| Model::ClaudeOpus4Thinking
| Model::ClaudeOpus4_1
| Model::ClaudeOpus4_1Thinking
| Model::Claude3Haiku
| Model::Claude3Opus
| Model::Claude3Sonnet

View file

@ -151,7 +151,6 @@ impl Settings for ProxySettings {
pub fn init_settings(cx: &mut App) {
TelemetrySettings::register(cx);
DisableAiSettings::register(cx);
ClientSettings::register(cx);
ProxySettings::register(cx);
}
@ -549,33 +548,6 @@ impl settings::Settings for TelemetrySettings {
}
}
/// Whether to disable all AI features in Zed.
///
/// Default: false
#[derive(Copy, Clone, Debug)]
pub struct DisableAiSettings {
pub disable_ai: bool,
}
impl settings::Settings for DisableAiSettings {
const KEY: Option<&'static str> = Some("disable_ai");
type FileContent = Option<bool>;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
Ok(Self {
disable_ai: sources
.user
.or(sources.server)
.copied()
.flatten()
.unwrap_or(sources.default.ok_or_else(Self::missing_default)?),
})
}
fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {}
}
impl Client {
pub fn new(
clock: Arc<dyn SystemClock>,

View file

@ -6,7 +6,6 @@ mod sign_in;
use crate::sign_in::initiate_sign_in_within_workspace;
use ::fs::Fs;
use anyhow::{Context as _, Result, anyhow};
use client::DisableAiSettings;
use collections::{HashMap, HashSet};
use command_palette_hooks::CommandPaletteFilter;
use futures::{Future, FutureExt, TryFutureExt, channel::oneshot, future::Shared};
@ -24,6 +23,7 @@ use language::{
use lsp::{LanguageServer, LanguageServerBinary, LanguageServerId, LanguageServerName};
use node_runtime::NodeRuntime;
use parking_lot::Mutex;
use project::DisableAiSettings;
use request::StatusNotification;
use serde_json::json;
use settings::Settings;

View file

@ -295,7 +295,7 @@ mod tests {
request: dap_types::StartDebuggingRequestArgumentsRequest::Launch,
},
},
Box::new(|_| panic!("Did not expect to hit this code path")),
Box::new(|_| {}),
&mut cx.to_async(),
)
.await

View file

@ -883,6 +883,7 @@ impl FakeTransport {
break Err(anyhow!("exit in response to request"));
}
};
let success = response.success;
let message =
serde_json::to_string(&Message::Response(response)).unwrap();
@ -893,6 +894,25 @@ impl FakeTransport {
)
.await
.unwrap();
if request.command == dap_types::requests::Initialize::COMMAND
&& success
{
let message = serde_json::to_string(&Message::Event(Box::new(
dap_types::messages::Events::Initialized(Some(
Default::default(),
)),
)))
.unwrap();
writer
.write_all(
TransportDelegate::build_rpc_message(message)
.as_bytes(),
)
.await
.unwrap();
}
writer.flush().await.unwrap();
}
}

View file

@ -22,6 +22,7 @@ test-support = [
"theme/test-support",
"util/test-support",
"workspace/test-support",
"tree-sitter-c",
"tree-sitter-rust",
"tree-sitter-typescript",
"tree-sitter-html",
@ -76,6 +77,7 @@ telemetry.workspace = true
text.workspace = true
time.workspace = true
theme.workspace = true
tree-sitter-c = { workspace = true, optional = true }
tree-sitter-html = { workspace = true, optional = true }
tree-sitter-rust = { workspace = true, optional = true }
tree-sitter-typescript = { workspace = true, optional = true }
@ -106,6 +108,7 @@ settings = { workspace = true, features = ["test-support"] }
tempfile.workspace = true
text = { workspace = true, features = ["test-support"] }
theme = { workspace = true, features = ["test-support"] }
tree-sitter-c.workspace = true
tree-sitter-html.workspace = true
tree-sitter-rust.workspace = true
tree-sitter-typescript.workspace = true

View file

@ -56,7 +56,7 @@ use aho_corasick::AhoCorasick;
use anyhow::{Context as _, Result, anyhow};
use blink_manager::BlinkManager;
use buffer_diff::DiffHunkStatus;
use client::{Collaborator, DisableAiSettings, ParticipantIndex};
use client::{Collaborator, ParticipantIndex};
use clock::{AGENT_REPLICA_ID, ReplicaId};
use collections::{BTreeMap, HashMap, HashSet, VecDeque};
use convert_case::{Case, Casing};
@ -125,7 +125,7 @@ use markdown::Markdown;
use mouse_context_menu::MouseContextMenu;
use persistence::DB;
use project::{
BreakpointWithPosition, CompletionResponse, ProjectPath,
BreakpointWithPosition, CompletionResponse, DisableAiSettings, ProjectPath,
debugger::{
breakpoint_store::{
BreakpointEditAction, BreakpointSessionState, BreakpointState, BreakpointStore,
@ -1305,6 +1305,7 @@ impl Default for SelectionHistoryMode {
///
/// Similarly, you might want to disable scrolling if you don't want the viewport to
/// move.
#[derive(Clone)]
pub struct SelectionEffects {
nav_history: Option<bool>,
completions: bool,
@ -2944,10 +2945,12 @@ impl Editor {
}
}
let selection_anchors = self.selections.disjoint_anchors();
if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
self.buffer.update(cx, |buffer, cx| {
buffer.set_active_selections(
&self.selections.disjoint_anchors(),
&selection_anchors,
self.selections.line_mode,
self.cursor_shape,
cx,
@ -2964,9 +2967,8 @@ impl Editor {
self.select_next_state = None;
self.select_prev_state = None;
self.select_syntax_node_history.try_clear();
self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
self.snippet_stack
.invalidate(&self.selections.disjoint_anchors(), buffer);
self.invalidate_autoclose_regions(&selection_anchors, buffer);
self.snippet_stack.invalidate(&selection_anchors, buffer);
self.take_rename(false, window, cx);
let newest_selection = self.selections.newest_anchor();
@ -4047,7 +4049,8 @@ impl Editor {
// then don't insert that closing bracket again; just move the selection
// past the closing bracket.
let should_skip = selection.end == region.range.end.to_point(&snapshot)
&& text.as_ref() == region.pair.end.as_str();
&& text.as_ref() == region.pair.end.as_str()
&& snapshot.contains_str_at(region.range.end, text.as_ref());
if should_skip {
let anchor = snapshot.anchor_after(selection.end);
new_selections
@ -4973,13 +4976,17 @@ impl Editor {
})
}
/// Remove any autoclose regions that no longer contain their selection.
/// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
fn invalidate_autoclose_regions(
&mut self,
mut selections: &[Selection<Anchor>],
buffer: &MultiBufferSnapshot,
) {
self.autoclose_regions.retain(|state| {
if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
return false;
}
let mut i = 0;
while let Some(selection) = selections.get(i) {
if selection.end.cmp(&state.range.start, buffer).is_lt() {
@ -5891,18 +5898,20 @@ impl Editor {
text: new_text[common_prefix_len..].into(),
});
self.transact(window, cx, |this, window, cx| {
self.transact(window, cx, |editor, window, cx| {
if let Some(mut snippet) = snippet {
snippet.text = new_text.to_string();
this.insert_snippet(&ranges, snippet, window, cx).log_err();
editor
.insert_snippet(&ranges, snippet, window, cx)
.log_err();
} else {
this.buffer.update(cx, |buffer, cx| {
editor.buffer.update(cx, |multi_buffer, cx| {
let auto_indent = match completion.insert_text_mode {
Some(InsertTextMode::AS_IS) => None,
_ => this.autoindent_mode.clone(),
_ => editor.autoindent_mode.clone(),
};
let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
buffer.edit(edits, auto_indent, cx);
multi_buffer.edit(edits, auto_indent, cx);
});
}
for (buffer, edits) in linked_edits {
@ -5921,8 +5930,9 @@ impl Editor {
})
}
this.refresh_inline_completion(true, false, window, cx);
editor.refresh_inline_completion(true, false, window, cx);
});
self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), &snapshot);
let show_new_completions_on_confirm = completion
.confirm
@ -6993,6 +7003,10 @@ impl Editor {
window: &mut Window,
cx: &mut Context<Self>,
) -> Option<()> {
if DisableAiSettings::get_global(cx).disable_ai {
return None;
}
let provider = self.edit_prediction_provider()?;
let cursor = self.selections.newest_anchor().head();
let (buffer, cursor_buffer_position) =
@ -7050,6 +7064,7 @@ impl Editor {
pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
self.edit_prediction_settings = EditPredictionSettings::Disabled;
self.discard_inline_completion(false, cx);
} else {
let selection = self.selections.newest_anchor();
let cursor = selection.head();
@ -7667,6 +7682,10 @@ impl Editor {
_window: &mut Window,
cx: &mut Context<Self>,
) -> Option<()> {
if DisableAiSettings::get_global(cx).disable_ai {
return None;
}
let selection = self.selections.newest_anchor();
let cursor = selection.head();
let multibuffer = self.buffer.read(cx).snapshot(cx);
@ -9562,27 +9581,46 @@ impl Editor {
// Check whether the just-entered snippet ends with an auto-closable bracket.
if self.autoclose_regions.is_empty() {
let snapshot = self.buffer.read(cx).snapshot(cx);
for selection in &mut self.selections.all::<Point>(cx) {
let mut all_selections = self.selections.all::<Point>(cx);
for selection in &mut all_selections {
let selection_head = selection.head();
let Some(scope) = snapshot.language_scope_at(selection_head) else {
continue;
};
let mut bracket_pair = None;
let next_chars = snapshot.chars_at(selection_head).collect::<String>();
let prev_chars = snapshot
.reversed_chars_at(selection_head)
.collect::<String>();
for (pair, enabled) in scope.brackets() {
if enabled
&& pair.close
&& prev_chars.starts_with(pair.start.as_str())
&& next_chars.starts_with(pair.end.as_str())
{
bracket_pair = Some(pair.clone());
break;
let max_lookup_length = scope
.brackets()
.map(|(pair, _)| {
pair.start
.as_str()
.chars()
.count()
.max(pair.end.as_str().chars().count())
})
.max();
if let Some(max_lookup_length) = max_lookup_length {
let next_text = snapshot
.chars_at(selection_head)
.take(max_lookup_length)
.collect::<String>();
let prev_text = snapshot
.reversed_chars_at(selection_head)
.take(max_lookup_length)
.collect::<String>();
for (pair, enabled) in scope.brackets() {
if enabled
&& pair.close
&& prev_text.starts_with(pair.start.as_str())
&& next_text.starts_with(pair.end.as_str())
{
bracket_pair = Some(pair.clone());
break;
}
}
}
if let Some(pair) = bracket_pair {
let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
let autoclose_enabled =

View file

@ -13400,6 +13400,178 @@ async fn test_as_is_completions(cx: &mut TestAppContext) {
cx.assert_editor_state("fn a() {}\n unsafeˇ");
}
#[gpui::test]
async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let language =
Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
let mut cx = EditorLspTestContext::new(
language,
lsp::ServerCapabilities {
completion_provider: Some(lsp::CompletionOptions {
..lsp::CompletionOptions::default()
}),
..lsp::ServerCapabilities::default()
},
cx,
)
.await;
cx.set_state(
"#ifndef BAR_H
#define BAR_H
#include <stdbool.h>
int fn_branch(bool do_branch1, bool do_branch2);
#endif // BAR_H
ˇ",
);
cx.executor().run_until_parked();
cx.update_editor(|editor, window, cx| {
editor.handle_input("#", window, cx);
});
cx.executor().run_until_parked();
cx.update_editor(|editor, window, cx| {
editor.handle_input("i", window, cx);
});
cx.executor().run_until_parked();
cx.update_editor(|editor, window, cx| {
editor.handle_input("n", window, cx);
});
cx.executor().run_until_parked();
cx.assert_editor_state(
"#ifndef BAR_H
#define BAR_H
#include <stdbool.h>
int fn_branch(bool do_branch1, bool do_branch2);
#endif // BAR_H
#inˇ",
);
cx.lsp
.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
is_incomplete: false,
item_defaults: None,
items: vec![lsp::CompletionItem {
kind: Some(lsp::CompletionItemKind::SNIPPET),
label_details: Some(lsp::CompletionItemLabelDetails {
detail: Some("header".to_string()),
description: None,
}),
label: " include".to_string(),
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
range: lsp::Range {
start: lsp::Position {
line: 8,
character: 1,
},
end: lsp::Position {
line: 8,
character: 1,
},
},
new_text: "include \"$0\"".to_string(),
})),
sort_text: Some("40b67681include".to_string()),
insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
filter_text: Some("include".to_string()),
insert_text: Some("include \"$0\"".to_string()),
..lsp::CompletionItem::default()
}],
})))
});
cx.update_editor(|editor, window, cx| {
editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
});
cx.executor().run_until_parked();
cx.update_editor(|editor, window, cx| {
editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
});
cx.executor().run_until_parked();
cx.assert_editor_state(
"#ifndef BAR_H
#define BAR_H
#include <stdbool.h>
int fn_branch(bool do_branch1, bool do_branch2);
#endif // BAR_H
#include \"ˇ\"",
);
cx.lsp
.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
is_incomplete: true,
item_defaults: None,
items: vec![lsp::CompletionItem {
kind: Some(lsp::CompletionItemKind::FILE),
label: "AGL/".to_string(),
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
range: lsp::Range {
start: lsp::Position {
line: 8,
character: 10,
},
end: lsp::Position {
line: 8,
character: 11,
},
},
new_text: "AGL/".to_string(),
})),
sort_text: Some("40b67681AGL/".to_string()),
insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
filter_text: Some("AGL/".to_string()),
insert_text: Some("AGL/".to_string()),
..lsp::CompletionItem::default()
}],
})))
});
cx.update_editor(|editor, window, cx| {
editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
});
cx.executor().run_until_parked();
cx.update_editor(|editor, window, cx| {
editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
});
cx.executor().run_until_parked();
cx.assert_editor_state(
r##"#ifndef BAR_H
#define BAR_H
#include <stdbool.h>
int fn_branch(bool do_branch1, bool do_branch2);
#endif // BAR_H
#include "AGL/ˇ"##,
);
cx.update_editor(|editor, window, cx| {
editor.handle_input("\"", window, cx);
});
cx.executor().run_until_parked();
cx.assert_editor_state(
r##"#ifndef BAR_H
#define BAR_H
#include <stdbool.h>
int fn_branch(bool do_branch1, bool do_branch2);
#endif // BAR_H
#include "AGL/"ˇ"##,
);
}
#[gpui::test]
async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
init_test(cx, |_| {});

View file

@ -23,7 +23,6 @@ askpass.workspace = true
buffer_diff.workspace = true
call.workspace = true
chrono.workspace = true
client.workspace = true
cloud_llm_client.workspace = true
collections.workspace = true
command_palette_hooks.workspace = true

View file

@ -1,9 +1,9 @@
use crate::branch_picker::{self, BranchList};
use crate::git_panel::{GitPanel, commit_message_editor};
use client::DisableAiSettings;
use git::repository::CommitOptions;
use git::{Amend, Commit, GenerateCommitMessage, Signoff};
use panel::{panel_button, panel_editor_style};
use project::DisableAiSettings;
use settings::Settings;
use ui::{
ContextMenu, KeybindingHint, PopoverMenu, PopoverMenuHandle, SplitButton, Tooltip, prelude::*,

View file

@ -12,7 +12,6 @@ use crate::{
use agent_settings::AgentSettings;
use anyhow::Context as _;
use askpass::AskPassDelegate;
use client::DisableAiSettings;
use db::kvp::KEY_VALUE_STORE;
use editor::{
Editor, EditorElement, EditorMode, EditorSettings, MultiBuffer, ShowScrollbar,
@ -51,10 +50,9 @@ use panel::{
PanelHeader, panel_button, panel_editor_container, panel_editor_style, panel_filled_button,
panel_icon_button,
};
use project::git_store::{RepositoryEvent, RepositoryId};
use project::{
Fs, Project, ProjectPath,
git_store::{GitStoreEvent, Repository},
DisableAiSettings, Fs, Project, ProjectPath,
git_store::{GitStoreEvent, Repository, RepositoryEvent, RepositoryId},
};
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsStore};
@ -5115,7 +5113,6 @@ mod tests {
language::init(cx);
editor::init(cx);
Project::init_settings(cx);
client::DisableAiSettings::register(cx);
crate::init(cx);
});
}

View file

@ -606,7 +606,7 @@ impl BladeRenderer {
xy_position: v.xy_position,
st_position: v.st_position,
color: path.color,
bounds: path.bounds.intersect(&path.content_mask.bounds),
bounds: path.clipped_bounds(),
}));
}
let vertex_buf = unsafe { self.instance_belt.alloc_typed(&vertices, &self.gpu) };
@ -735,13 +735,13 @@ impl BladeRenderer {
paths
.iter()
.map(|path| PathSprite {
bounds: path.bounds,
bounds: path.clipped_bounds(),
})
.collect()
} else {
let mut bounds = first_path.bounds;
let mut bounds = first_path.clipped_bounds();
for path in paths.iter().skip(1) {
bounds = bounds.union(&path.bounds);
bounds = bounds.union(&path.clipped_bounds());
}
vec![PathSprite { bounds }]
};

View file

@ -1004,12 +1004,13 @@ impl X11Client {
let mut keystroke = crate::Keystroke::from_xkb(&state.xkb, modifiers, code);
let keysym = state.xkb.key_get_one_sym(code);
// should be called after key_get_one_sym
state.xkb.update_key(code, xkbc::KeyDirection::Down);
if keysym.is_modifier_key() {
return Some(());
}
// should be called after key_get_one_sym
state.xkb.update_key(code, xkbc::KeyDirection::Down);
if let Some(mut compose_state) = state.compose_state.take() {
compose_state.feed(keysym);
match compose_state.status() {
@ -1067,12 +1068,13 @@ impl X11Client {
let keystroke = crate::Keystroke::from_xkb(&state.xkb, modifiers, code);
let keysym = state.xkb.key_get_one_sym(code);
// should be called after key_get_one_sym
state.xkb.update_key(code, xkbc::KeyDirection::Up);
if keysym.is_modifier_key() {
return Some(());
}
// should be called after key_get_one_sym
state.xkb.update_key(code, xkbc::KeyDirection::Up);
keystroke
};
drop(state);

View file

@ -791,13 +791,13 @@ impl MetalRenderer {
sprites = paths
.iter()
.map(|path| PathSprite {
bounds: path.bounds,
bounds: path.clipped_bounds(),
})
.collect();
} else {
let mut bounds = first_path.bounds;
let mut bounds = first_path.clipped_bounds();
for path in paths.iter().skip(1) {
bounds = bounds.union(&path.bounds);
bounds = bounds.union(&path.clipped_bounds());
}
sprites = vec![PathSprite { bounds }];
}

View file

@ -8,7 +8,12 @@ use crate::{
AtlasTextureId, AtlasTile, Background, Bounds, ContentMask, Corners, Edges, Hsla, Pixels,
Point, Radians, ScaledPixels, Size, bounds_tree::BoundsTree, point,
};
use std::{fmt::Debug, iter::Peekable, ops::Range, slice};
use std::{
fmt::Debug,
iter::Peekable,
ops::{Add, Range, Sub},
slice,
};
#[allow(non_camel_case_types, unused)]
pub(crate) type PathVertex_ScaledPixels = PathVertex<ScaledPixels>;
@ -793,6 +798,16 @@ impl Path<Pixels> {
}
}
impl<T> Path<T>
where
T: Clone + Debug + Default + PartialEq + PartialOrd + Add<T, Output = T> + Sub<Output = T>,
{
#[allow(unused)]
pub(crate) fn clipped_bounds(&self) -> Bounds<T> {
self.bounds.intersect(&self.content_mask.bounds)
}
}
impl From<Path<ScaledPixels>> for Primitive {
fn from(path: Path<ScaledPixels>) -> Self {
Primitive::Path(path)

View file

@ -25,6 +25,7 @@ indoc.workspace = true
inline_completion.workspace = true
language.workspace = true
paths.workspace = true
project.workspace = true
regex.workspace = true
settings.workspace = true
supermaven.workspace = true

View file

@ -1,5 +1,5 @@
use anyhow::Result;
use client::{DisableAiSettings, UserStore, zed_urls};
use client::{UserStore, zed_urls};
use cloud_llm_client::UsageLimit;
use copilot::{Copilot, Status};
use editor::{
@ -19,6 +19,7 @@ use language::{
EditPredictionsMode, File, Language,
language_settings::{self, AllLanguageSettings, EditPredictionProvider, all_language_settings},
};
use project::DisableAiSettings;
use regex::Regex;
use settings::{Settings, SettingsStore, update_settings_file};
use std::{

View file

@ -674,6 +674,10 @@ pub fn count_open_ai_tokens(
| Model::O3
| Model::O3Mini
| Model::O4Mini => tiktoken_rs::num_tokens_from_messages(model.id(), &messages),
// GPT-5 models don't have tiktoken support yet; fall back on gpt-4o tokenizer
Model::Five | Model::FiveMini | Model::FiveNano => {
tiktoken_rs::num_tokens_from_messages("gpt-4o", &messages)
}
}
.map(|tokens| tokens as u64)
})

View file

@ -167,10 +167,10 @@ impl Anchor {
if *self == Anchor::min() || *self == Anchor::max() {
true
} else if let Some(excerpt) = snapshot.excerpt(self.excerpt_id) {
excerpt.contains(self)
&& (self.text_anchor == excerpt.range.context.start
|| self.text_anchor == excerpt.range.context.end
|| self.text_anchor.is_valid(&excerpt.buffer))
(self.text_anchor == excerpt.range.context.start
|| self.text_anchor == excerpt.range.context.end
|| self.text_anchor.is_valid(&excerpt.buffer))
&& excerpt.contains(self)
} else {
false
}

View file

@ -74,6 +74,12 @@ pub enum Model {
O3,
#[serde(rename = "o4-mini")]
O4Mini,
#[serde(rename = "gpt-5")]
Five,
#[serde(rename = "gpt-5-mini")]
FiveMini,
#[serde(rename = "gpt-5-nano")]
FiveNano,
#[serde(rename = "custom")]
Custom {
@ -105,6 +111,9 @@ impl Model {
"o3-mini" => Ok(Self::O3Mini),
"o3" => Ok(Self::O3),
"o4-mini" => Ok(Self::O4Mini),
"gpt-5" => Ok(Self::Five),
"gpt-5-mini" => Ok(Self::FiveMini),
"gpt-5-nano" => Ok(Self::FiveNano),
invalid_id => anyhow::bail!("invalid model id '{invalid_id}'"),
}
}
@ -123,6 +132,9 @@ impl Model {
Self::O3Mini => "o3-mini",
Self::O3 => "o3",
Self::O4Mini => "o4-mini",
Self::Five => "gpt-5",
Self::FiveMini => "gpt-5-mini",
Self::FiveNano => "gpt-5-nano",
Self::Custom { name, .. } => name,
}
}
@ -141,6 +153,9 @@ impl Model {
Self::O3Mini => "o3-mini",
Self::O3 => "o3",
Self::O4Mini => "o4-mini",
Self::Five => "gpt-5",
Self::FiveMini => "gpt-5-mini",
Self::FiveNano => "gpt-5-nano",
Self::Custom {
name, display_name, ..
} => display_name.as_ref().unwrap_or(name),
@ -161,6 +176,9 @@ impl Model {
Self::O3Mini => 200_000,
Self::O3 => 200_000,
Self::O4Mini => 200_000,
Self::Five => 272_000,
Self::FiveMini => 272_000,
Self::FiveNano => 272_000,
Self::Custom { max_tokens, .. } => *max_tokens,
}
}
@ -182,6 +200,9 @@ impl Model {
Self::O3Mini => Some(100_000),
Self::O3 => Some(100_000),
Self::O4Mini => Some(100_000),
Self::Five => Some(128_000),
Self::FiveMini => Some(128_000),
Self::FiveNano => Some(128_000),
}
}
@ -197,7 +218,10 @@ impl Model {
| Self::FourOmniMini
| Self::FourPointOne
| Self::FourPointOneMini
| Self::FourPointOneNano => true,
| Self::FourPointOneNano
| Self::Five
| Self::FiveMini
| Self::FiveNano => true,
Self::O1 | Self::O3 | Self::O3Mini | Self::O4Mini | Model::Custom { .. } => false,
}
}

View file

@ -1,7 +1,7 @@
use std::{path::Path, sync::Arc};
use dap::client::DebugAdapterClient;
use gpui::{App, AppContext, Subscription};
use gpui::{App, Subscription};
use super::session::{Session, SessionStateEvent};
@ -19,14 +19,6 @@ pub fn intercept_debug_sessions<T: Fn(&Arc<DebugAdapterClient>) + 'static>(
let client = session.adapter_client().unwrap();
register_default_handlers(session, &client, cx);
configure(&client);
cx.background_spawn(async move {
client
.fake_event(dap::messages::Events::Initialized(
Some(Default::default()),
))
.await
})
.detach();
}
})
.detach();

View file

@ -2269,7 +2269,7 @@ impl LspCommand for GetCompletions {
// the range based on the syntax tree.
None => {
if self.position != clipped_position {
log::info!("completion out of expected range");
log::info!("completion out of expected range ");
return false;
}
@ -2483,7 +2483,9 @@ pub(crate) fn parse_completion_text_edit(
let start = snapshot.clip_point_utf16(range.start, Bias::Left);
let end = snapshot.clip_point_utf16(range.end, Bias::Left);
if start != range.start.0 || end != range.end.0 {
log::info!("completion out of expected range");
log::info!(
"completion out of expected range, start: {start:?}, end: {end:?}, range: {range:?}"
);
return None;
}
snapshot.anchor_before(start)..snapshot.anchor_after(end)

View file

@ -97,7 +97,7 @@ use rpc::{
};
use search::{SearchInputKind, SearchQuery, SearchResult};
use search_history::SearchHistory;
use settings::{InvalidSettingsError, Settings, SettingsLocation, SettingsStore};
use settings::{InvalidSettingsError, Settings, SettingsLocation, SettingsSources, SettingsStore};
use smol::channel::Receiver;
use snippet::Snippet;
use snippet_provider::SnippetProvider;
@ -942,10 +942,38 @@ pub enum PulledDiagnostics {
},
}
/// Whether to disable all AI features in Zed.
///
/// Default: false
#[derive(Copy, Clone, Debug)]
pub struct DisableAiSettings {
pub disable_ai: bool,
}
impl settings::Settings for DisableAiSettings {
const KEY: Option<&'static str> = Some("disable_ai");
type FileContent = Option<bool>;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
Ok(Self {
disable_ai: sources
.user
.or(sources.server)
.copied()
.flatten()
.unwrap_or(sources.default.ok_or_else(Self::missing_default)?),
})
}
fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {}
}
impl Project {
pub fn init_settings(cx: &mut App) {
WorktreeSettings::register(cx);
ProjectSettings::register(cx);
DisableAiSettings::register(cx);
}
pub fn init(client: &Arc<Client>, cx: &mut App) {

View file

@ -99,7 +99,9 @@ impl Anchor {
} else if self.buffer_id != Some(buffer.remote_id) {
false
} else {
let fragment_id = buffer.fragment_id_for_anchor(self);
let Some(fragment_id) = buffer.try_fragment_id_for_anchor(self) else {
return false;
};
let mut fragment_cursor = buffer.fragments.cursor::<(Option<&Locator>, usize)>(&None);
fragment_cursor.seek(&Some(fragment_id), Bias::Left);
fragment_cursor

View file

@ -2330,10 +2330,19 @@ impl BufferSnapshot {
}
fn fragment_id_for_anchor(&self, anchor: &Anchor) -> &Locator {
self.try_fragment_id_for_anchor(anchor).unwrap_or_else(|| {
panic!(
"invalid anchor {:?}. buffer id: {}, version: {:?}",
anchor, self.remote_id, self.version,
)
})
}
fn try_fragment_id_for_anchor(&self, anchor: &Anchor) -> Option<&Locator> {
if *anchor == Anchor::MIN {
Locator::min_ref()
Some(Locator::min_ref())
} else if *anchor == Anchor::MAX {
Locator::max_ref()
Some(Locator::max_ref())
} else {
let anchor_key = InsertionFragmentKey {
timestamp: anchor.timestamp,
@ -2354,20 +2363,12 @@ impl BufferSnapshot {
insertion_cursor.prev();
}
let Some(insertion) = insertion_cursor.item().filter(|insertion| {
if cfg!(debug_assertions) {
insertion.timestamp == anchor.timestamp
} else {
true
}
}) else {
panic!(
"invalid anchor {:?}. buffer id: {}, version: {:?}",
anchor, self.remote_id, self.version
);
};
&insertion.fragment_id
insertion_cursor
.item()
.filter(|insertion| {
!cfg!(debug_assertions) || insertion.timestamp == anchor.timestamp
})
.map(|insertion| &insertion.fragment_id)
}
}

View file

@ -1,10 +1,11 @@
use client::{DisableAiSettings, TelemetrySettings, telemetry::Telemetry};
use client::{TelemetrySettings, telemetry::Telemetry};
use db::kvp::KEY_VALUE_STORE;
use gpui::{
Action, App, Context, Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement,
ParentElement, Render, Styled, Subscription, Task, WeakEntity, Window, actions, svg,
};
use language::language_settings::{EditPredictionProvider, all_language_settings};
use project::DisableAiSettings;
use settings::{Settings, SettingsStore};
use std::sync::Arc;
use ui::{CheckboxWithLabel, ElevationIndex, Tooltip, prelude::*};

View file

@ -2,7 +2,7 @@
description = "The fast, collaborative code editor."
edition.workspace = true
name = "zed"
version = "0.198.0"
version = "0.198.3"
publish.workspace = true
license = "GPL-3.0-or-later"
authors = ["Zed Team <hi@zed.dev>"]

View file

@ -1 +1 @@
dev
stable

View file

@ -1,9 +1,10 @@
use client::{Client, DisableAiSettings, UserStore};
use client::{Client, UserStore};
use collections::HashMap;
use copilot::{Copilot, CopilotCompletionProvider};
use editor::Editor;
use gpui::{AnyWindowHandle, App, AppContext as _, Context, Entity, WeakEntity};
use language::language_settings::{EditPredictionProvider, all_language_settings};
use project::DisableAiSettings;
use settings::{Settings as _, SettingsStore};
use smol::stream::StreamExt;
use std::{cell::RefCell, rc::Rc, sync::Arc};

View file

@ -2,7 +2,6 @@ mod preview;
mod repl_menu;
use agent_settings::AgentSettings;
use client::DisableAiSettings;
use editor::actions::{
AddSelectionAbove, AddSelectionBelow, CodeActionSource, DuplicateLineDown, GoToDiagnostic,
GoToHunk, GoToPreviousDiagnostic, GoToPreviousHunk, MoveLineDown, MoveLineUp, SelectAll,
@ -16,6 +15,7 @@ use gpui::{
FocusHandle, Focusable, InteractiveElement, ParentElement, Render, Styled, Subscription,
WeakEntity, Window, anchored, deferred, point,
};
use project::DisableAiSettings;
use project::project_settings::DiagnosticSeverity;
use search::{BufferSearchBar, buffer_search};
use settings::{Settings, SettingsStore};

View file

@ -1,10 +1,10 @@
use std::any::{Any, TypeId};
use client::DisableAiSettings;
use command_palette_hooks::CommandPaletteFilter;
use feature_flags::{FeatureFlagAppExt as _, PredictEditsRateCompletionsFeatureFlag};
use gpui::actions;
use language::language_settings::{AllLanguageSettings, EditPredictionProvider};
use project::DisableAiSettings;
use settings::{Settings, SettingsStore, update_settings_file};
use ui::App;
use workspace::Workspace;