Compare commits

..

33 Commits

Author SHA1 Message Date
Thomas Mickley-Doyle
df0f37f247 Update handling fetch from actions 2025-04-17 07:12:50 -05:00
Thomas Mickley-Doyle
2b49347e7a Run all tests 2025-04-16 22:59:39 -05:00
Thomas Mickley-Doyle
40bf0b9338 git fetch update 2025-04-16 22:19:13 -05:00
Thomas Mickley-Doyle
245a5e4c19 Create run dir 2025-04-16 21:01:06 -05:00
Thomas Mickley-Doyle
70b757229f Create run dir 2025-04-16 20:46:09 -05:00
Thomas Mickley-Doyle
5d927cf551 Create directories 2025-04-16 16:34:44 -05:00
Thomas Mickley-Doyle
ec586d6267 Add one example to test 2025-04-16 16:18:58 -05:00
Thomas Mickley-Doyle
90d7a1a2d8 Add Anthropic key 2025-04-16 15:50:43 -05:00
Thomas Mickley-Doyle
a20e9ade0d add more depends 2025-04-16 14:49:16 -05:00
Thomas Mickley-Doyle
1a348c19f1 Add x11 and alsa 2025-04-16 14:33:10 -05:00
Thomas Mickley-Doyle
61ba414b27 fix errors 2025-04-16 14:17:20 -05:00
Thomas Mickley-Doyle
3a58360037 agent: Add git commit ID to the eval telemetry data (#28895)
Release Notes:

- N/A
2025-04-16 14:17:20 -05:00
Marshall Bowers
484a8aee54 collab: Add plan column to subscription_usages (#28889)
This PR adds a `plan` column to the `subscription_usages` table.

These tables don't have any records in them yet, so it's fine to make
the column required without a default.

Release Notes:

- N/A
2025-04-16 14:17:20 -05:00
Conrad Irwin
d21a23f536 Fix anchor_in_excerpt on replaced excerpts (#28880)
Release Notes:

- N/A
2025-04-16 14:17:20 -05:00
Bennet Bo Fenner
fb56e6f393 agent: Allow quoting selection when text thread is active (#28887)
This makes the `assistant: Quote selection` work again for text threads.
Next up is supporting this also in normal threads.

Release Notes:

- agent: Add support for inserting selections (assistant: Quote
selection) into text threads
2025-04-16 14:17:20 -05:00
Mikayla Maki
46dc85dc9e Remove bottom dock layout button (#28876)
Release Notes:

- Preview: Removed the layout button from the title bar. The
`bottom_dock_layout` setting still functions.
- Added a setting, `bottom_dock_layout`, for controlling the
relationship between the bottom dock and the left and right docks.
2025-04-16 14:17:20 -05:00
Marshall Bowers
2eaddd4e47 proto: Add ZedProTrial to Plan (#28885)
This PR adds the `ZedProTrial` member to the `Plan` enum.

Release Notes:

- N/A
2025-04-16 14:17:20 -05:00
Bennet Bo Fenner
89eadaa806 agent: Improve fuzzy matching for @mentions (#28883)
Make fuzzy search in @-mention match paths and context kinds as well
(e.g., typing "sym" should let me select the "Symbols" label, as opposed
to just paths)

Release Notes:

- agent: Improve fuzzy-matching when using @mentions
2025-04-16 14:17:20 -05:00
Anthony Eid
8dd7acb6a4 debugger: Remove or move breakpoints on file deletion/rename (#28882)
Release Notes:

- N/A

---------

Co-authored-by: Cole Miller <m@cole-miller.net>
2025-04-16 14:17:20 -05:00
Danilo Leal
0ead83a885 agent: Add item to open Prompt Library in the panel's menu (#28877)
Release Notes:

- agent: Added a menu item to open the Prompt Library from the panel's
dropdown menu on the top right.
2025-04-16 14:17:20 -05:00
Bennet Bo Fenner
693a2241bb agent: Add websearch tool (#28621)
Staff only for now. We'll work on making this usable for non zed.dev
users later

Release Notes:

- N/A

---------

Co-authored-by: Antonio Scandurra <me@as-cii.com>
Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
Co-authored-by: Marshall Bowers <git@maxdeviant.com>
2025-04-16 14:17:20 -05:00
5brian
56a8dd12fe vim: Fix LineUp (#27754)
Closes #27423

Release Notes:

- vim: Fixed cursor scrolling off screen with `ctrl-y`.

---------

Co-authored-by: Ben Kunkle <ben@zed.dev>
2025-04-16 14:17:20 -05:00
Marshall Bowers
7281002c2e collab: Remove has_predict_edits_feature_flag from LlmTokenClaims (#28879)
This PR removes the `has_predict_edits_feature_flag` field from the
`LlmTokenClaims`.

We are no longer reading this anywhere.

Release Notes:

- N/A
2025-04-16 14:17:20 -05:00
Piotr Osiewicz
45003fd265 toolchain: Respect currently focused file when querying toolchains (#28875)
Closes #21743


https://github.com/user-attachments/assets/0230f233-58a4-494c-90af-28ce82f9fc1d


Release Notes:

- Virtual environment picker now looks up virtual environment based on
parent directory of active file; this enables having multiple active
virtual environments in a single worktree.
2025-04-16 14:17:20 -05:00
Danilo Leal
0a87d7499e agent: Add small design tweaks (#28874)
Some small adjustments to simplify the agent panel's design.

Release Notes:

- N/A
2025-04-16 14:17:20 -05:00
Kirill Bulatov
b591c86305 Fix more panics when removing excerpts (#28836)
Release Notes:

- Fixed a panic when an excerpt removed has an edit suggestion inlay in
it

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-04-16 14:17:20 -05:00
Smit Barmase
bccddbaaf5 editor: Expand selection to word under cursor before expanding to next enclosing syntax node (#28864)
Closes #27995

For strings in any language and Markdown, `select_larger_syntax_node`
will first select the word and then expand from there if:
- The cursor is on the word.
- The selection is inside the word.

It will not select the word and will directly proceed to expand if:
- The word is already selected.
- Multiple partial words are selected.

Todo:
- [x] Tests

Release Notes:

- Fixed `select_larger_syntax_node` to first expand to the word within a
string, and then to the larger syntax node.
2025-04-16 14:17:20 -05:00
Marshall Bowers
0c52f5384d agent: Show an error when the model requests limit has been reached (#28868)
This PR adds an error message when the model requests limit has been
hit.

Release Notes:

- N/A

Co-authored-by: Oleksiy Syvokon <oleksiy.syvokon@gmail.com>
2025-04-16 14:17:20 -05:00
Mikayla Maki
28375376dd Adjust dependencies 2025-04-16 11:52:07 -07:00
Thomas Mickley-Doyle
9c8c65b487 add libx11 2025-04-16 13:28:20 -05:00
Thomas Mickley-Doyle
07d659157e Add alsa library 2025-04-16 12:49:04 -05:00
Thomas Mickley-Doyle
1c6d79e836 Add x11 library 2025-04-16 09:39:30 -05:00
Thomas Mickley-Doyle
ee7fef2d02 Add mold linker to Eval Runs 2025-04-16 09:30:11 -05:00
52 changed files with 571 additions and 1202 deletions

View File

@@ -20,6 +20,7 @@ platforms = [
[traversal-excludes]
workspace-members = [
"remote_server",
"eval",
]
third-party = [
{ name = "reqwest", version = "0.11.27" },

View File

@@ -325,7 +325,7 @@ jobs:
cache-provider: "buildjet"
- name: Install Clang & Mold
run: ./script/remote-server && ./script/install-mold 2.34.0
run: ./script/headless && ./script/install-mold 2.34.0
- name: Configure CI
run: |

View File

@@ -17,12 +17,30 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
uses: actions/checkout@v4
with:
clean: false
- name: Install Dependencies
run: |
sudo apt-get update
sudo apt-get install -y libx11-dev libxcb-shape0-dev libxcb-xfixes0-dev libxcb1-dev libxcb-render0-dev libxcb-randr0-dev libxcb-xtest0-dev libxcb-keysyms1-dev libxext-dev libxrender-dev libxrandr-dev libxtst-dev libxfixes-dev pkg-config libasound2-dev libx11-xcb-dev libxkbcommon-dev libxkbcommon-x11-dev
./script/headless && ./script/install-mold 2.34.0
- name: Create required directories
run: |
mkdir -p /home/runner/.config/zed
touch /home/runner/.config/zed/settings.json
mkdir -p ./crates/eval/repos
mkdir -p ./crates/eval/worktrees
mkdir -p ./crates/eval/runs
- name: Set up Anthropic API key
run: echo "ANTHROPIC_API_KEY=${{ secrets.ANTHROPIC_API_KEY }}" >> $GITHUB_ENV
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
- name: Run cargo eval
run: cargo run -p eval
run: cargo run -p eval -- --cohort_id daily-run-${{ github.run_id }}

52
Cargo.lock generated
View File

@@ -324,7 +324,7 @@ dependencies = [
"schemars",
"serde",
"serde_json",
"strum 0.27.1",
"strum 0.26.3",
"thiserror 2.0.12",
"workspace-hack",
]
@@ -337,9 +337,9 @@ checksum = "34cd60c5e3152cef0a592f1b296f1cc93715d89d2551d85315828c3a09575ff4"
[[package]]
name = "anyhow"
version = "1.0.98"
version = "1.0.97"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f"
[[package]]
name = "approx"
@@ -567,7 +567,7 @@ dependencies = [
"settings",
"smallvec",
"smol",
"strum 0.27.1",
"strum 0.26.3",
"telemetry_events",
"text",
"theme",
@@ -1884,7 +1884,7 @@ dependencies = [
"schemars",
"serde",
"serde_json",
"strum 0.27.1",
"strum 0.26.3",
"thiserror 2.0.12",
"tokio",
"workspace-hack",
@@ -3031,7 +3031,7 @@ dependencies = [
"settings",
"sha2",
"sqlx",
"strum 0.27.1",
"strum 0.26.3",
"subtle",
"supermaven_api",
"telemetry_events",
@@ -3051,7 +3051,6 @@ dependencies = [
"workspace",
"workspace-hack",
"worktree",
"zed_llm_client",
]
[[package]]
@@ -3364,7 +3363,7 @@ dependencies = [
"serde",
"serde_json",
"settings",
"strum 0.27.1",
"strum 0.26.3",
"task",
"theme",
"ui",
@@ -4917,7 +4916,6 @@ dependencies = [
"unindent",
"util",
"uuid",
"workspace-hack",
]
[[package]]
@@ -5126,7 +5124,7 @@ dependencies = [
"serde",
"settings",
"smallvec",
"strum 0.27.1",
"strum 0.26.3",
"telemetry",
"theme",
"ui",
@@ -5977,7 +5975,7 @@ dependencies = [
"serde_derive",
"serde_json",
"settings",
"strum 0.27.1",
"strum 0.26.3",
"telemetry",
"theme",
"time",
@@ -6070,7 +6068,7 @@ dependencies = [
"schemars",
"serde",
"serde_json",
"strum 0.27.1",
"strum 0.26.3",
"workspace-hack",
]
@@ -6176,7 +6174,7 @@ dependencies = [
"slotmap",
"smallvec",
"smol",
"strum 0.27.1",
"strum 0.26.3",
"sum_tree",
"taffy",
"thiserror 2.0.12",
@@ -6824,7 +6822,7 @@ name = "icons"
version = "0.1.0"
dependencies = [
"serde",
"strum 0.27.1",
"strum 0.26.3",
"workspace-hack",
]
@@ -7092,7 +7090,7 @@ dependencies = [
"paths",
"pretty_assertions",
"serde",
"strum 0.27.1",
"strum 0.26.3",
"util",
"workspace-hack",
]
@@ -7678,7 +7676,7 @@ dependencies = [
"serde",
"serde_json",
"smol",
"strum 0.27.1",
"strum 0.26.3",
"telemetry_events",
"thiserror 2.0.12",
"util",
@@ -7738,7 +7736,7 @@ dependencies = [
"serde_json",
"settings",
"smol",
"strum 0.27.1",
"strum 0.26.3",
"theme",
"thiserror 2.0.12",
"tiktoken-rs",
@@ -8711,7 +8709,7 @@ dependencies = [
"schemars",
"serde",
"serde_json",
"strum 0.27.1",
"strum 0.26.3",
"workspace-hack",
]
@@ -9558,7 +9556,7 @@ dependencies = [
"schemars",
"serde",
"serde_json",
"strum 0.27.1",
"strum 0.26.3",
"workspace-hack",
]
@@ -12137,7 +12135,7 @@ dependencies = [
"serde",
"serde_json",
"sha2",
"strum 0.27.1",
"strum 0.26.3",
"tracing",
"util",
"workspace-hack",
@@ -13710,7 +13708,7 @@ dependencies = [
"settings",
"simplelog",
"story",
"strum 0.27.1",
"strum 0.26.3",
"theme",
"title_bar",
"ui",
@@ -14445,7 +14443,7 @@ dependencies = [
"serde_json_lenient",
"serde_repr",
"settings",
"strum 0.27.1",
"strum 0.26.3",
"thiserror 2.0.12",
"util",
"uuid",
@@ -14479,7 +14477,7 @@ dependencies = [
"serde_json",
"serde_json_lenient",
"simplelog",
"strum 0.27.1",
"strum 0.26.3",
"theme",
"vscode_theme",
"workspace-hack",
@@ -15480,7 +15478,7 @@ dependencies = [
"settings",
"smallvec",
"story",
"strum 0.27.1",
"strum 0.26.3",
"theme",
"ui_macros",
"util",
@@ -17681,7 +17679,7 @@ dependencies = [
"settings",
"smallvec",
"sqlez",
"strum 0.27.1",
"strum 0.26.3",
"task",
"telemetry",
"tempfile",
@@ -18387,9 +18385,9 @@ dependencies = [
[[package]]
name = "zed_llm_client"
version = "0.5.1"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ee4d410dbc030c3e6e3af78fc76296f6bebe20dcb6d7d3fa24bca306fc8c1ce"
checksum = "57a5e1b5b3ace3fb55292a4c14036723bb8a01fac4aeaa3c2b63b51228412f94"
dependencies = [
"serde",
"serde_json",

View File

@@ -540,7 +540,7 @@ smol = "2.0"
sqlformat = "0.2"
streaming-iterator = "0.1"
strsim = "0.11"
strum = { version = "0.27.0", features = ["derive"] }
strum = { version = "0.26.0", features = ["derive"] }
subtle = "2.5.0"
syn = { version = "1.0.72", features = ["full", "extra-traits"] }
sys-locale = "0.3.1"
@@ -605,7 +605,7 @@ wasmtime-wasi = "29"
which = "6.0.0"
wit-component = "0.221"
workspace-hack = "0.1.0"
zed_llm_client = "0.5.1"
zed_llm_client = "0.5.0"
zstd = "0.11"
metal = "0.29"

View File

@@ -11,7 +11,7 @@ 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 script/headless script/
RUN ./script/headless
COPY . .

View File

@@ -134,9 +134,7 @@
"shift-f10": "editor::OpenContextMenu",
"ctrl-shift-e": "editor::ToggleEditPrediction",
"f9": "editor::ToggleBreakpoint",
"shift-f9": "editor::EditLogBreakpoint",
"ctrl-shift-backspace": "editor::GoToPreviousChange",
"ctrl-shift-alt-backspace": "editor::GoToNextChange"
"shift-f9": "editor::EditLogBreakpoint"
}
},
{
@@ -721,7 +719,7 @@
"alt-shift-copy": "workspace::CopyRelativePath",
"alt-ctrl-shift-c": "workspace::CopyRelativePath",
"alt-ctrl-r": "outline_panel::RevealInFileManager",
"space": "outline_panel::OpenSelectedEntry",
"space": "outline_panel::Open",
"shift-down": "menu::SelectNext",
"shift-up": "menu::SelectPrevious",
"alt-enter": "editor::OpenExcerpts",

View File

@@ -542,9 +542,7 @@
"cmd-\\": "pane::SplitRight",
"cmd-k v": "markdown::OpenPreviewToTheSide",
"cmd-shift-v": "markdown::OpenPreview",
"ctrl-cmd-c": "editor::DisplayCursorNames",
"cmd-shift-backspace": "editor::GoToPreviousChange",
"cmd-shift-alt-backspace": "editor::GoToNextChange"
"ctrl-cmd-c": "editor::DisplayCursorNames"
}
},
{
@@ -789,7 +787,7 @@
"cmd-alt-c": "workspace::CopyPath",
"alt-cmd-shift-c": "workspace::CopyRelativePath",
"alt-cmd-r": "outline_panel::RevealInFileManager",
"space": "outline_panel::OpenSelectedEntry",
"space": "outline_panel::Open",
"shift-down": "menu::SelectNext",
"shift-up": "menu::SelectPrevious",
"alt-enter": "editor::OpenExcerpts",

View File

@@ -1,4 +1,4 @@
use crate::context::{AssistantContext, ContextId, format_context_as_string};
use crate::context::{AssistantContext, ContextId};
use crate::context_picker::MentionLink;
use crate::thread::{
LastRestoreCheckpoint, MessageId, MessageSegment, RequestKind, Thread, ThreadError,
@@ -13,18 +13,16 @@ use assistant_settings::{AssistantSettings, NotifyWhenAgentWaiting};
use assistant_tool::ToolUseStatus;
use collections::{HashMap, HashSet};
use editor::scroll::Autoscroll;
use editor::{Editor, EditorElement, EditorEvent, EditorStyle, MultiBuffer};
use editor::{Editor, EditorElement, EditorStyle, MultiBuffer};
use gpui::{
AbsoluteLength, Animation, AnimationExt, AnyElement, App, ClickEvent, ClipboardItem,
DefiniteLength, EdgesRefinement, Empty, Entity, EventEmitter, Focusable, Hsla, ListAlignment,
ListState, MouseButton, PlatformDisplay, ScrollHandle, Stateful, StyleRefinement, Subscription,
Task, TextStyle, TextStyleRefinement, Transformation, UnderlineStyle, WeakEntity, WindowHandle,
DefiniteLength, EdgesRefinement, Empty, Entity, Focusable, Hsla, ListAlignment, ListState,
MouseButton, PlatformDisplay, ScrollHandle, Stateful, StyleRefinement, Subscription, Task,
TextStyle, TextStyleRefinement, Transformation, UnderlineStyle, WeakEntity, WindowHandle,
linear_color_stop, linear_gradient, list, percentage, pulsating_between,
};
use language::{Buffer, LanguageRegistry};
use language_model::{
LanguageModelRegistry, LanguageModelRequestMessage, LanguageModelToolUseId, Role, StopReason,
};
use language_model::{LanguageModelRegistry, LanguageModelToolUseId, Role, StopReason};
use markdown::parser::{CodeBlockKind, CodeBlockMetadata};
use markdown::{HeadingLevelStyles, Markdown, MarkdownElement, MarkdownStyle, ParsedMarkdown};
use project::ProjectItem as _;
@@ -684,9 +682,6 @@ fn open_markdown_link(
struct EditMessageState {
editor: Entity<Editor>,
last_estimated_token_count: Option<usize>,
_subscription: Subscription,
_update_token_count_task: Option<Task<anyhow::Result<()>>>,
}
impl ActiveThread {
@@ -786,13 +781,6 @@ impl ActiveThread {
self.last_error.take();
}
/// Returns the editing message id and the estimated token count in the content
pub fn editing_message_id(&self) -> Option<(MessageId, usize)> {
self.editing_message
.as_ref()
.map(|(id, state)| (*id, state.last_estimated_token_count.unwrap_or(0)))
}
fn push_message(
&mut self,
id: &MessageId,
@@ -1138,91 +1126,15 @@ impl ActiveThread {
editor.move_to_end(&editor::actions::MoveToEnd, window, cx);
editor
});
let subscription = cx.subscribe(&editor, |this, _, event, cx| match event {
EditorEvent::BufferEdited => {
this.update_editing_message_token_count(true, cx);
}
_ => {}
});
self.editing_message = Some((
message_id,
EditMessageState {
editor: editor.clone(),
last_estimated_token_count: None,
_subscription: subscription,
_update_token_count_task: None,
},
));
self.update_editing_message_token_count(false, cx);
cx.notify();
}
fn update_editing_message_token_count(&mut self, debounce: bool, cx: &mut Context<Self>) {
let Some((message_id, state)) = self.editing_message.as_mut() else {
return;
};
cx.emit(ActiveThreadEvent::EditingMessageTokenCountChanged);
state._update_token_count_task.take();
let Some(default_model) = LanguageModelRegistry::read_global(cx).default_model() else {
state.last_estimated_token_count.take();
return;
};
let editor = state.editor.clone();
let thread = self.thread.clone();
let message_id = *message_id;
state._update_token_count_task = Some(cx.spawn(async move |this, cx| {
if debounce {
cx.background_executor()
.timer(Duration::from_millis(200))
.await;
}
let token_count = if let Some(task) = cx.update(|cx| {
let context = thread.read(cx).context_for_message(message_id);
let new_context = thread.read(cx).filter_new_context(context);
let context_text =
format_context_as_string(new_context, cx).unwrap_or(String::new());
let message_text = editor.read(cx).text(cx);
let content = context_text + &message_text;
if content.is_empty() {
return None;
}
let request = language_model::LanguageModelRequest {
messages: vec![LanguageModelRequestMessage {
role: language_model::Role::User,
content: vec![content.into()],
cache: false,
}],
tools: vec![],
stop: vec![],
temperature: None,
};
Some(default_model.model.count_tokens(request, cx))
})? {
task.await?
} else {
0
};
this.update(cx, |this, cx| {
let Some((_message_id, state)) = this.editing_message.as_mut() else {
return;
};
state.last_estimated_token_count = Some(token_count);
cx.emit(ActiveThreadEvent::EditingMessageTokenCountChanged);
})
}));
}
fn cancel_editing_message(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context<Self>) {
self.editing_message.take();
cx.notify();
@@ -1764,9 +1676,6 @@ impl ActiveThread {
"confirm-edit-message",
"Regenerate",
)
.disabled(
edit_message_editor.read(cx).is_empty(cx),
)
.label_size(LabelSize::Small)
.key_binding(
KeyBinding::for_action_in(
@@ -1829,16 +1738,8 @@ impl ActiveThread {
),
};
let after_editing_message = self
.editing_message
.as_ref()
.map_or(false, |(editing_message_id, _)| {
message_id > *editing_message_id
});
v_flex()
.w_full()
.when(after_editing_message, |parent| parent.opacity(0.2))
.when_some(checkpoint, |parent, checkpoint| {
let mut is_pending = false;
let mut error = None;
@@ -3064,12 +2965,6 @@ impl ActiveThread {
}
}
pub enum ActiveThreadEvent {
EditingMessageTokenCountChanged,
}
impl EventEmitter<ActiveThreadEvent> for ActiveThread {}
impl Render for ActiveThread {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
v_flex()

View File

@@ -5,7 +5,7 @@ use std::time::Duration;
use anyhow::{Result, anyhow};
use assistant_context_editor::{
AssistantPanelDelegate, ConfigurationError, ContextEditor, SlashCommandCompletionProvider,
humanize_token_count, make_lsp_adapter_delegate, render_remaining_tokens,
make_lsp_adapter_delegate, render_remaining_tokens,
};
use assistant_settings::{AssistantDockPosition, AssistantSettings};
use assistant_slash_command::SlashCommandWorkingSet;
@@ -37,10 +37,10 @@ use workspace::dock::{DockPosition, Panel, PanelEvent};
use zed_actions::agent::OpenConfiguration;
use zed_actions::assistant::{OpenPromptLibrary, ToggleFocus};
use crate::active_thread::{ActiveThread, ActiveThreadEvent};
use crate::active_thread::ActiveThread;
use crate::assistant_configuration::{AssistantConfiguration, AssistantConfigurationEvent};
use crate::history_store::{HistoryEntry, HistoryStore};
use crate::message_editor::{MessageEditor, MessageEditorEvent};
use crate::message_editor::MessageEditor;
use crate::thread::{Thread, ThreadError, ThreadId, TokenUsageRatio};
use crate::thread_history::{PastContext, PastThread, ThreadHistory};
use crate::thread_store::ThreadStore;
@@ -181,8 +181,8 @@ pub struct AssistantPanel {
language_registry: Arc<LanguageRegistry>,
thread_store: Entity<ThreadStore>,
thread: Entity<ActiveThread>,
_thread_subscription: Subscription,
message_editor: Entity<MessageEditor>,
_active_thread_subscriptions: Vec<Subscription>,
context_store: Entity<assistant_context_editor::ContextStore>,
context_editor: Option<Entity<ContextEditor>>,
configuration: Option<Entity<AssistantConfiguration>>,
@@ -264,13 +264,6 @@ impl AssistantPanel {
)
});
let message_editor_subscription =
cx.subscribe(&message_editor, |_, _, event, cx| match event {
MessageEditorEvent::Changed | MessageEditorEvent::EstimatedTokenCount => {
cx.notify();
}
});
let history_store =
cx.new(|cx| HistoryStore::new(thread_store.clone(), context_store.clone(), cx));
@@ -295,12 +288,6 @@ impl AssistantPanel {
)
});
let active_thread_subscription = cx.subscribe(&thread, |_, _, event, cx| match &event {
ActiveThreadEvent::EditingMessageTokenCountChanged => {
cx.notify();
}
});
Self {
active_view,
workspace,
@@ -309,12 +296,8 @@ impl AssistantPanel {
language_registry,
thread_store: thread_store.clone(),
thread,
_thread_subscription: thread_subscription,
message_editor,
_active_thread_subscriptions: vec![
thread_subscription,
active_thread_subscription,
message_editor_subscription,
],
context_store,
context_editor: None,
configuration: None,
@@ -399,13 +382,6 @@ impl AssistantPanel {
.detach_and_log_err(cx);
}
let thread_subscription = cx.subscribe(&thread, |_, _, event, cx| {
if let ThreadEvent::MessageAdded(_) = &event {
// needed to leave empty state
cx.notify();
}
});
self.thread = cx.new(|cx| {
ActiveThread::new(
thread.clone(),
@@ -418,12 +394,12 @@ impl AssistantPanel {
)
});
let active_thread_subscription =
cx.subscribe(&self.thread, |_, _, event, cx| match &event {
ActiveThreadEvent::EditingMessageTokenCountChanged => {
cx.notify();
}
});
self._thread_subscription = cx.subscribe(&thread, |_, _, event, cx| {
if let ThreadEvent::MessageAdded(_) = &event {
// needed to leave empty state
cx.notify();
}
});
self.message_editor = cx.new(|cx| {
MessageEditor::new(
@@ -437,19 +413,6 @@ impl AssistantPanel {
)
});
self.message_editor.focus_handle(cx).focus(window);
let message_editor_subscription =
cx.subscribe(&self.message_editor, |_, _, event, cx| match event {
MessageEditorEvent::Changed | MessageEditorEvent::EstimatedTokenCount => {
cx.notify();
}
});
self._active_thread_subscriptions = vec![
thread_subscription,
active_thread_subscription,
message_editor_subscription,
];
}
fn new_prompt_editor(&mut self, window: &mut Window, cx: &mut Context<Self>) {
@@ -575,13 +538,6 @@ impl AssistantPanel {
Some(this.thread_store.downgrade()),
)
});
let thread_subscription = cx.subscribe(&thread, |_, _, event, cx| {
if let ThreadEvent::MessageAdded(_) = &event {
// needed to leave empty state
cx.notify();
}
});
this.thread = cx.new(|cx| {
ActiveThread::new(
thread.clone(),
@@ -593,14 +549,6 @@ impl AssistantPanel {
cx,
)
});
let active_thread_subscription =
cx.subscribe(&this.thread, |_, _, event, cx| match &event {
ActiveThreadEvent::EditingMessageTokenCountChanged => {
cx.notify();
}
});
this.message_editor = cx.new(|cx| {
MessageEditor::new(
this.fs.clone(),
@@ -613,19 +561,6 @@ impl AssistantPanel {
)
});
this.message_editor.focus_handle(cx).focus(window);
let message_editor_subscription =
cx.subscribe(&this.message_editor, |_, _, event, cx| match event {
MessageEditorEvent::Changed | MessageEditorEvent::EstimatedTokenCount => {
cx.notify();
}
});
this._active_thread_subscriptions = vec![
thread_subscription,
active_thread_subscription,
message_editor_subscription,
];
})
})
}
@@ -918,7 +853,7 @@ impl Panel for AssistantPanel {
}
impl AssistantPanel {
fn render_title_view(&self, _window: &mut Window, cx: &Context<Self>) -> AnyElement {
fn render_title_view(&self, _window: &mut Window, cx: &mut Context<Self>) -> AnyElement {
const LOADING_SUMMARY_PLACEHOLDER: &str = "Loading Summary…";
let content = match &self.active_view {
@@ -978,8 +913,13 @@ impl AssistantPanel {
fn render_toolbar(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let active_thread = self.thread.read(cx);
let thread = active_thread.thread().read(cx);
let token_usage = thread.total_token_usage(cx);
let thread_id = thread.id().clone();
let is_generating = thread.is_generating();
let is_empty = active_thread.is_empty();
let focus_handle = self.focus_handle(cx);
let is_history = matches!(self.active_view, ActiveView::History);
let show_token_count = match &self.active_view {
@@ -988,8 +928,6 @@ impl AssistantPanel {
_ => false,
};
let focus_handle = self.focus_handle(cx);
let go_back_button = match &self.active_view {
ActiveView::History | ActiveView::Configuration => Some(
div().pl_1().child(
@@ -1036,9 +974,69 @@ impl AssistantPanel {
h_flex()
.h_full()
.gap_2()
.when(show_token_count, |parent|
parent.children(self.render_token_count(&thread, cx))
)
.when(show_token_count, |parent| match self.active_view {
ActiveView::Thread { .. } => {
if token_usage.total == 0 {
return parent;
}
let token_color = match token_usage.ratio {
TokenUsageRatio::Normal => Color::Muted,
TokenUsageRatio::Warning => Color::Warning,
TokenUsageRatio::Exceeded => Color::Error,
};
parent.child(
h_flex()
.flex_shrink_0()
.gap_0p5()
.child(
Label::new(assistant_context_editor::humanize_token_count(
token_usage.total,
))
.size(LabelSize::Small)
.color(token_color)
.map(|label| {
if is_generating {
label
.with_animation(
"used-tokens-label",
Animation::new(Duration::from_secs(2))
.repeat()
.with_easing(pulsating_between(
0.6, 1.,
)),
|label, delta| label.alpha(delta),
)
.into_any()
} else {
label.into_any_element()
}
}),
)
.child(
Label::new("/").size(LabelSize::Small).color(Color::Muted),
)
.child(
Label::new(assistant_context_editor::humanize_token_count(
token_usage.max,
))
.size(LabelSize::Small)
.color(Color::Muted),
),
)
}
ActiveView::PromptEditor => {
let Some(editor) = self.context_editor.as_ref() else {
return parent;
};
let Some(element) = render_remaining_tokens(editor, cx) else {
return parent;
};
parent.child(element)
}
_ => parent,
})
.child(
h_flex()
.h_full()
@@ -1134,111 +1132,6 @@ impl AssistantPanel {
)
}
fn render_token_count(&self, thread: &Thread, cx: &App) -> Option<AnyElement> {
let is_generating = thread.is_generating();
let message_editor = self.message_editor.read(cx);
let conversation_token_usage = thread.total_token_usage(cx);
let (total_token_usage, is_estimating) = if let Some((editing_message_id, unsent_tokens)) =
self.thread.read(cx).editing_message_id()
{
let combined = thread
.token_usage_up_to_message(editing_message_id, cx)
.add(unsent_tokens);
(combined, unsent_tokens > 0)
} else {
let unsent_tokens = message_editor.last_estimated_token_count().unwrap_or(0);
let combined = conversation_token_usage.add(unsent_tokens);
(combined, unsent_tokens > 0)
};
let is_waiting_to_update_token_count = message_editor.is_waiting_to_update_token_count();
match self.active_view {
ActiveView::Thread { .. } => {
if total_token_usage.total == 0 {
return None;
}
let token_color = match total_token_usage.ratio() {
TokenUsageRatio::Normal if is_estimating => Color::Default,
TokenUsageRatio::Normal => Color::Muted,
TokenUsageRatio::Warning => Color::Warning,
TokenUsageRatio::Exceeded => Color::Error,
};
let token_count = h_flex()
.id("token-count")
.flex_shrink_0()
.gap_0p5()
.when(!is_generating && is_estimating, |parent| {
parent
.child(
h_flex()
.mr_0p5()
.size_2()
.justify_center()
.rounded_full()
.bg(cx.theme().colors().text.opacity(0.1))
.child(
div().size_1().rounded_full().bg(cx.theme().colors().text),
),
)
.tooltip(move |window, cx| {
Tooltip::with_meta(
"Estimated New Token Count",
None,
format!(
"Current Conversation Tokens: {}",
humanize_token_count(conversation_token_usage.total)
),
window,
cx,
)
})
})
.child(
Label::new(humanize_token_count(total_token_usage.total))
.size(LabelSize::Small)
.color(token_color)
.map(|label| {
if is_generating || is_waiting_to_update_token_count {
label
.with_animation(
"used-tokens-label",
Animation::new(Duration::from_secs(2))
.repeat()
.with_easing(pulsating_between(0.6, 1.)),
|label, delta| label.alpha(delta),
)
.into_any()
} else {
label.into_any_element()
}
}),
)
.child(Label::new("/").size(LabelSize::Small).color(Color::Muted))
.child(
Label::new(humanize_token_count(total_token_usage.max))
.size(LabelSize::Small)
.color(Color::Muted),
)
.into_any();
Some(token_count)
}
ActiveView::PromptEditor => {
let editor = self.context_editor.as_ref()?;
let element = render_remaining_tokens(editor, cx)?;
Some(element.into_any_element())
}
_ => None,
}
}
fn render_active_thread_or_empty_state(
&self,
window: &mut Window,

View File

@@ -2,23 +2,22 @@ use std::collections::BTreeMap;
use std::sync::Arc;
use crate::assistant_model_selector::ModelType;
use crate::context::format_context_as_string;
use crate::tool_compatibility::{IncompatibleToolsState, IncompatibleToolsTooltip};
use buffer_diff::BufferDiff;
use collections::HashSet;
use editor::actions::MoveUp;
use editor::{
ContextMenuOptions, ContextMenuPlacement, Editor, EditorElement, EditorEvent, EditorMode,
EditorStyle, MultiBuffer,
ContextMenuOptions, ContextMenuPlacement, Editor, EditorElement, EditorMode, EditorStyle,
MultiBuffer,
};
use file_icons::FileIcons;
use fs::Fs;
use gpui::{
Animation, AnimationExt, App, Entity, EventEmitter, Focusable, Subscription, Task, TextStyle,
WeakEntity, linear_color_stop, linear_gradient, point, pulsating_between,
Animation, AnimationExt, App, Entity, Focusable, Subscription, TextStyle, WeakEntity,
linear_color_stop, linear_gradient, point, pulsating_between,
};
use language::{Buffer, Language};
use language_model::{ConfiguredModel, LanguageModelRegistry, LanguageModelRequestMessage};
use language_model::{ConfiguredModel, LanguageModelRegistry};
use language_model_selector::ToggleModelSelector;
use multi_buffer;
use project::Project;
@@ -56,8 +55,6 @@ pub struct MessageEditor {
edits_expanded: bool,
editor_is_expanded: bool,
waiting_for_summaries_to_send: bool,
last_estimated_token_count: Option<usize>,
update_token_count_task: Option<Task<anyhow::Result<()>>>,
_subscriptions: Vec<Subscription>,
}
@@ -132,18 +129,8 @@ impl MessageEditor {
let incompatible_tools =
cx.new(|cx| IncompatibleToolsState::new(thread.read(cx).tools().clone(), cx));
let subscriptions = vec![
cx.subscribe_in(&context_strip, window, Self::handle_context_strip_event),
cx.subscribe(&editor, |this, _, event, cx| match event {
EditorEvent::BufferEdited => {
this.message_or_context_changed(true, cx);
}
_ => {}
}),
cx.observe(&context_store, |this, _, cx| {
this.message_or_context_changed(false, cx);
}),
];
let subscriptions =
vec![cx.subscribe_in(&context_strip, window, Self::handle_context_strip_event)];
Self {
editor: editor.clone(),
@@ -169,8 +156,6 @@ impl MessageEditor {
waiting_for_summaries_to_send: false,
profile_selector: cx
.new(|cx| ProfileSelector::new(fs, thread_store, editor.focus_handle(cx), cx)),
last_estimated_token_count: None,
update_token_count_task: None,
_subscriptions: subscriptions,
}
}
@@ -271,9 +256,6 @@ impl MessageEditor {
text
});
self.last_estimated_token_count.take();
cx.emit(MessageEditorEvent::EstimatedTokenCount);
let refresh_task =
refresh_context_store_text(self.context_store.clone(), &HashSet::default(), cx);
@@ -955,80 +937,6 @@ impl MessageEditor {
.label_size(LabelSize::Small),
)
}
pub fn last_estimated_token_count(&self) -> Option<usize> {
self.last_estimated_token_count
}
pub fn is_waiting_to_update_token_count(&self) -> bool {
self.update_token_count_task.is_some()
}
fn message_or_context_changed(&mut self, debounce: bool, cx: &mut Context<Self>) {
cx.emit(MessageEditorEvent::Changed);
self.update_token_count_task.take();
let Some(default_model) = LanguageModelRegistry::read_global(cx).default_model() else {
self.last_estimated_token_count.take();
return;
};
let context_store = self.context_store.clone();
let editor = self.editor.clone();
let thread = self.thread.clone();
self.update_token_count_task = Some(cx.spawn(async move |this, cx| {
if debounce {
cx.background_executor()
.timer(Duration::from_millis(200))
.await;
}
let token_count = if let Some(task) = cx.update(|cx| {
let context = context_store.read(cx).context().iter();
let new_context = thread.read(cx).filter_new_context(context);
let context_text =
format_context_as_string(new_context, cx).unwrap_or(String::new());
let message_text = editor.read(cx).text(cx);
let content = context_text + &message_text;
if content.is_empty() {
return None;
}
let request = language_model::LanguageModelRequest {
messages: vec![LanguageModelRequestMessage {
role: language_model::Role::User,
content: vec![content.into()],
cache: false,
}],
tools: vec![],
stop: vec![],
temperature: None,
};
Some(default_model.model.count_tokens(request, cx))
})? {
task.await?
} else {
0
};
this.update(cx, |this, cx| {
this.last_estimated_token_count = Some(token_count);
cx.emit(MessageEditorEvent::EstimatedTokenCount);
this.update_token_count_task.take();
})
}));
}
}
impl EventEmitter<MessageEditorEvent> for MessageEditor {}
pub enum MessageEditorEvent {
EstimatedTokenCount,
Changed,
}
impl Focusable for MessageEditor {
@@ -1041,7 +949,6 @@ impl Render for MessageEditor {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let thread = self.thread.read(cx);
let total_token_usage = thread.total_token_usage(cx);
let token_usage_ratio = total_token_usage.ratio();
let action_log = self.thread.read(cx).action_log();
let changed_buffers = action_log.read(cx).changed_buffers(cx);
@@ -1090,8 +997,15 @@ impl Render for MessageEditor {
parent.child(self.render_changed_buffers(&changed_buffers, window, cx))
})
.child(self.render_editor(font_size, line_height, window, cx))
.when(token_usage_ratio != TokenUsageRatio::Normal, |parent| {
parent.child(self.render_token_limit_callout(line_height, token_usage_ratio, cx))
})
.when(
total_token_usage.ratio != TokenUsageRatio::Normal,
|parent| {
parent.child(self.render_token_limit_callout(
line_height,
total_token_usage.ratio,
cx,
))
},
)
}
}

View File

@@ -227,33 +227,7 @@ pub enum DetailedSummaryState {
pub struct TotalTokenUsage {
pub total: usize,
pub max: usize,
}
impl TotalTokenUsage {
pub fn ratio(&self) -> TokenUsageRatio {
#[cfg(debug_assertions)]
let warning_threshold: f32 = std::env::var("ZED_THREAD_WARNING_THRESHOLD")
.unwrap_or("0.8".to_string())
.parse()
.unwrap();
#[cfg(not(debug_assertions))]
let warning_threshold: f32 = 0.8;
if self.total >= self.max {
TokenUsageRatio::Exceeded
} else if self.total as f32 / self.max as f32 >= warning_threshold {
TokenUsageRatio::Warning
} else {
TokenUsageRatio::Normal
}
}
pub fn add(&self, tokens: usize) -> TotalTokenUsage {
TotalTokenUsage {
total: self.total + tokens,
max: self.max,
}
}
pub ratio: TokenUsageRatio,
}
#[derive(Debug, Default, PartialEq, Eq)]
@@ -287,7 +261,6 @@ pub struct Thread {
last_restore_checkpoint: Option<LastRestoreCheckpoint>,
pending_checkpoint: Option<ThreadCheckpoint>,
initial_project_snapshot: Shared<Task<Option<Arc<ProjectSnapshot>>>>,
request_token_usage: Vec<TokenUsage>,
cumulative_token_usage: TokenUsage,
exceeded_window_error: Option<ExceededWindowError>,
feedback: Option<ThreadFeedback>,
@@ -338,7 +311,6 @@ impl Thread {
.spawn(async move { Some(project_snapshot.await) })
.shared()
},
request_token_usage: Vec::new(),
cumulative_token_usage: TokenUsage::default(),
exceeded_window_error: None,
feedback: None,
@@ -406,7 +378,6 @@ impl Thread {
tool_use,
action_log: cx.new(|_| ActionLog::new(project)),
initial_project_snapshot: Task::ready(serialized.initial_project_snapshot).shared(),
request_token_usage: serialized.request_token_usage,
cumulative_token_usage: serialized.cumulative_token_usage,
exceeded_window_error: None,
feedback: None,
@@ -672,18 +643,6 @@ impl Thread {
self.tool_use.message_has_tool_results(message_id)
}
/// Filter out contexts that have already been included in previous messages
pub fn filter_new_context<'a>(
&self,
context: impl Iterator<Item = &'a AssistantContext>,
) -> impl Iterator<Item = &'a AssistantContext> {
context.filter(|ctx| self.is_context_new(ctx))
}
fn is_context_new(&self, context: &AssistantContext) -> bool {
!self.context.contains_key(&context.id())
}
pub fn insert_user_message(
&mut self,
text: impl Into<String>,
@@ -695,9 +654,10 @@ impl Thread {
let message_id = self.insert_message(Role::User, vec![MessageSegment::Text(text)], cx);
// Filter out contexts that have already been included in previous messages
let new_context: Vec<_> = context
.into_iter()
.filter(|ctx| self.is_context_new(ctx))
.filter(|ctx| !self.context.contains_key(&ctx.id()))
.collect();
if !new_context.is_empty() {
@@ -877,7 +837,6 @@ impl Thread {
.collect(),
initial_project_snapshot,
cumulative_token_usage: this.cumulative_token_usage,
request_token_usage: this.request_token_usage.clone(),
detailed_summary_state: this.detailed_summary_state.clone(),
exceeded_window_error: this.exceeded_window_error.clone(),
})
@@ -1063,6 +1022,7 @@ impl Thread {
cx: &mut Context<Self>,
) {
let pending_completion_id = post_inc(&mut self.completion_count);
let task = cx.spawn(async move |thread, cx| {
let stream = model.stream_completion(request, &cx);
let initial_token_usage =
@@ -1088,7 +1048,6 @@ impl Thread {
stop_reason = reason;
}
LanguageModelCompletionEvent::UsageUpdate(token_usage) => {
thread.update_token_usage_at_last_message(token_usage);
thread.cumulative_token_usage = thread.cumulative_token_usage
+ token_usage
- current_token_usage;
@@ -1930,35 +1889,6 @@ impl Thread {
self.cumulative_token_usage
}
pub fn token_usage_up_to_message(&self, message_id: MessageId, cx: &App) -> TotalTokenUsage {
let Some(model) = LanguageModelRegistry::read_global(cx).default_model() else {
return TotalTokenUsage::default();
};
let max = model.model.max_token_count();
let index = self
.messages
.iter()
.position(|msg| msg.id == message_id)
.unwrap_or(0);
if index == 0 {
return TotalTokenUsage { total: 0, max };
}
let token_usage = &self
.request_token_usage
.get(index - 1)
.cloned()
.unwrap_or_default();
TotalTokenUsage {
total: token_usage.total_tokens() as usize,
max,
}
}
pub fn total_token_usage(&self, cx: &App) -> TotalTokenUsage {
let model_registry = LanguageModelRegistry::read_global(cx);
let Some(model) = model_registry.default_model() else {
@@ -1972,33 +1902,30 @@ impl Thread {
return TotalTokenUsage {
total: exceeded_error.token_count,
max,
ratio: TokenUsageRatio::Exceeded,
};
}
}
let total = self
.token_usage_at_last_message()
.unwrap_or_default()
.total_tokens() as usize;
#[cfg(debug_assertions)]
let warning_threshold: f32 = std::env::var("ZED_THREAD_WARNING_THRESHOLD")
.unwrap_or("0.8".to_string())
.parse()
.unwrap();
#[cfg(not(debug_assertions))]
let warning_threshold: f32 = 0.8;
TotalTokenUsage { total, max }
}
let total = self.cumulative_token_usage.total_tokens() as usize;
fn token_usage_at_last_message(&self) -> Option<TokenUsage> {
self.request_token_usage
.get(self.messages.len().saturating_sub(1))
.or_else(|| self.request_token_usage.last())
.cloned()
}
let ratio = if total >= max {
TokenUsageRatio::Exceeded
} else if total as f32 / max as f32 >= warning_threshold {
TokenUsageRatio::Warning
} else {
TokenUsageRatio::Normal
};
fn update_token_usage_at_last_message(&mut self, token_usage: TokenUsage) {
let placeholder = self.token_usage_at_last_message().unwrap_or_default();
self.request_token_usage
.resize(self.messages.len(), placeholder);
if let Some(last) = self.request_token_usage.last_mut() {
*last = token_usage;
}
TotalTokenUsage { total, max, ratio }
}
pub fn deny_tool_use(

View File

@@ -509,8 +509,6 @@ pub struct SerializedThread {
#[serde(default)]
pub cumulative_token_usage: TokenUsage,
#[serde(default)]
pub request_token_usage: Vec<TokenUsage>,
#[serde(default)]
pub detailed_summary_state: DetailedSummaryState,
#[serde(default)]
pub exceeded_window_error: Option<ExceededWindowError>,
@@ -599,7 +597,6 @@ impl LegacySerializedThread {
messages: self.messages.into_iter().map(|msg| msg.upgrade()).collect(),
initial_project_snapshot: self.initial_project_snapshot,
cumulative_token_usage: TokenUsage::default(),
request_token_usage: Vec::new(),
detailed_summary_state: DetailedSummaryState::default(),
exceeded_window_error: None,
}

View File

@@ -75,7 +75,6 @@ tracing-subscriber = { version = "0.3.18", features = ["env-filter", "json", "re
util.workspace = true
uuid.workspace = true
workspace-hack.workspace = true
zed_llm_client.workspace = true
[dev-dependencies]
assistant = { workspace = true, features = ["test-support"] }

View File

@@ -330,10 +330,8 @@ async fn create_billing_subscription(
.await?
}
None => {
let default_model = llm_db.model(
zed_llm_client::LanguageModelProvider::Anthropic,
"claude-3-7-sonnet",
)?;
let default_model =
llm_db.model(rpc::LanguageModelProvider::Anthropic, "claude-3-7-sonnet")?;
let stripe_model = stripe_billing.register_model(default_model).await?;
stripe_billing
.checkout(customer_id, &user.github_login, &stripe_model, &success_url)
@@ -1020,20 +1018,8 @@ async fn get_current_usage(
return Ok(Json(empty_usage));
};
let plan = match usage.plan {
SubscriptionKind::ZedPro => zed_llm_client::Plan::ZedPro,
SubscriptionKind::ZedProTrial => zed_llm_client::Plan::ZedProTrial,
SubscriptionKind::ZedFree => zed_llm_client::Plan::Free,
};
let model_requests_limit = match plan.model_requests_limit() {
zed_llm_client::UsageLimit::Limited(limit) => Some(limit),
zed_llm_client::UsageLimit::Unlimited => None,
};
let edit_prediction_limit = match plan.edit_predictions_limit() {
zed_llm_client::UsageLimit::Limited(limit) => Some(limit),
zed_llm_client::UsageLimit::Unlimited => None,
};
let model_requests_limit = Some(500);
let edit_prediction_limit = Some(2000);
Ok(Json(GetCurrentUsageResponse {
model_requests: UsageCounts {

View File

@@ -8,9 +8,9 @@ mod tests;
use collections::HashMap;
pub use ids::*;
use rpc::LanguageModelProvider;
pub use seed::*;
pub use tables::*;
use zed_llm_client::LanguageModelProvider;
#[cfg(test)]
pub use tests::TestLlmDb;

View File

@@ -1,5 +1,5 @@
use pretty_assertions::assert_eq;
use zed_llm_client::LanguageModelProvider;
use rpc::LanguageModelProvider;
use crate::llm::db::LlmDatabase;
use crate::test_llm_db;

View File

@@ -10,7 +10,6 @@ use std::time::Duration;
use thiserror::Error;
use util::maybe;
use uuid::Uuid;
use zed_llm_client::Plan;
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
@@ -29,7 +28,7 @@ pub struct LlmTokenClaims {
pub has_llm_subscription: bool,
pub max_monthly_spend_in_cents: u32,
pub custom_llm_monthly_allowance_in_cents: Option<u32>,
pub plan: Plan,
pub plan: rpc::proto::Plan,
#[serde(default)]
pub subscription_period: Option<(NaiveDateTime, NaiveDateTime)>,
}
@@ -78,11 +77,7 @@ impl LlmTokenClaims {
custom_llm_monthly_allowance_in_cents: user
.custom_llm_monthly_allowance_in_cents
.map(|allowance| allowance as u32),
plan: match plan {
rpc::proto::Plan::Free => Plan::Free,
rpc::proto::Plan::ZedPro => Plan::ZedPro,
rpc::proto::Plan::ZedProTrial => Plan::ZedProTrial,
},
plan,
subscription_period: maybe!({
let subscription = subscription?;
let period_start_at = subscription.current_period_start_at()?;

View File

@@ -1,5 +1,5 @@
use ::fs::Fs;
use anyhow::{Context as _, Result, anyhow};
use anyhow::{Context as _, Ok, Result, anyhow};
use async_compression::futures::bufread::GzipDecoder;
use async_tar::Archive;
use async_trait::async_trait;
@@ -256,21 +256,7 @@ pub trait DebugAdapter: 'static + Send + Sync {
self.name()
);
delegate.update_status(self.name(), DapStatus::Downloading);
match self.install_binary(version, delegate).await {
Ok(_) => {
delegate.update_status(self.name(), DapStatus::None);
}
Err(error) => {
delegate.update_status(
self.name(),
DapStatus::Failed {
error: error.to_string(),
},
);
return Err(error);
}
}
self.install_binary(version, delegate).await?;
delegate
.updated_adapters()

View File

@@ -7,7 +7,7 @@ pub mod transport;
pub use dap_types::*;
pub use registry::DapRegistry;
pub use task::DebugRequestType;
pub use task::{DebugAdapterConfig, DebugRequestType};
pub type ScopeId = u64;
pub type VariableReference = u64;

View File

@@ -33,7 +33,6 @@ use std::sync::Arc;
use task::DebugTaskDefinition;
use terminal_view::terminal_panel::TerminalPanel;
use ui::{ContextMenu, Divider, DropdownMenu, Tooltip, prelude::*};
use util::debug_panic;
use workspace::{
Workspace,
dock::{DockPosition, Panel, PanelEvent},
@@ -317,20 +316,8 @@ impl DebugPanel {
.any(|item| item.read(cx).session_id(cx) == session_id)
{
// We already have an item for this session.
debug_panic!("We should never reuse session ids");
return;
}
this.sessions.retain(|session| {
session
.read(cx)
.mode()
.as_running()
.map_or(false, |running_state| {
!running_state.read(cx).session().read(cx).is_terminated()
})
});
let session_item = DebugSession::running(
project,
this.workspace.clone(),
@@ -782,6 +769,9 @@ impl DebugPanel {
this.restart_session(cx);
},
))
.disabled(
!capabilities.supports_restart_request.unwrap_or_default(),
)
.tooltip(move |window, cx| {
Tooltip::text("Restart")(window, cx)
}),

View File

@@ -46,7 +46,7 @@ use workspace::{
actions!(diagnostics, [Deploy, ToggleWarnings]);
pub(crate) struct IncludeWarnings(bool);
struct IncludeWarnings(bool);
impl Global for IncludeWarnings {}
pub fn init(cx: &mut App) {
@@ -379,6 +379,7 @@ impl ProjectDiagnosticsEditor {
Point::zero()..buffer_snapshot.max_point(),
false,
)
.filter(|d| !(d.diagnostic.is_primary && d.diagnostic.is_unnecessary))
.collect::<Vec<_>>();
let unchanged = this.update(cx, |this, _| {
if this.diagnostics.get(&buffer_id).is_some_and(|existing| {

View File

@@ -9,7 +9,7 @@ use language::Diagnostic;
use ui::{Button, ButtonLike, Color, Icon, IconName, Label, Tooltip, h_flex, prelude::*};
use workspace::{StatusItemView, ToolbarItemEvent, Workspace, item::ItemHandle};
use crate::{Deploy, IncludeWarnings, ProjectDiagnosticsEditor};
use crate::{Deploy, ProjectDiagnosticsEditor};
pub struct DiagnosticIndicator {
summary: project::DiagnosticSummary,
@@ -94,11 +94,6 @@ impl Render for DiagnosticIndicator {
})
.on_click(cx.listener(|this, _, window, cx| {
if let Some(workspace) = this.workspace.upgrade() {
if this.summary.error_count == 0 && this.summary.warning_count > 0 {
cx.update_global(|show_warnings: &mut IncludeWarnings, _| {
show_warnings.0 = true
});
}
workspace.update(cx, |workspace, cx| {
ProjectDiagnosticsEditor::deploy(
workspace,

View File

@@ -306,8 +306,6 @@ actions!(
GoToPreviousHunk,
GoToImplementation,
GoToImplementationSplit,
GoToNextChange,
GoToPreviousChange,
GoToPreviousDiagnostic,
GoToTypeDefinition,
GoToTypeDefinitionSplit,

View File

@@ -693,52 +693,6 @@ pub trait Addon: 'static {
fn to_any(&self) -> &dyn std::any::Any;
}
/// A set of caret positions, registered when the editor was edited.
pub struct ChangeList {
changes: Vec<Vec<Anchor>>,
/// Currently "selected" change.
position: Option<usize>,
}
impl ChangeList {
pub fn new() -> Self {
Self {
changes: Vec::new(),
position: None,
}
}
/// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
/// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
if self.changes.is_empty() {
return None;
}
let prev = self.position.unwrap_or(self.changes.len());
let next = if direction == Direction::Prev {
prev.saturating_sub(count)
} else {
(prev + count).min(self.changes.len() - 1)
};
self.position = Some(next);
self.changes.get(next).map(|anchors| anchors.as_slice())
}
/// Adds a new change to the list, resetting the change list position.
pub fn push_to_change_list(&mut self, pop_state: bool, new_positions: Vec<Anchor>) {
self.position.take();
if pop_state {
self.changes.pop();
}
self.changes.push(new_positions.clone());
}
pub fn last(&self) -> Option<&[Anchor]> {
self.changes.last().map(|anchors| anchors.as_slice())
}
}
/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
///
/// See the [module level documentation](self) for more information.
@@ -903,7 +857,6 @@ pub struct Editor {
serialize_folds: Task<()>,
mouse_cursor_hidden: bool,
hide_mouse_mode: HideMouseMode,
pub change_list: ChangeList,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
@@ -1695,7 +1648,6 @@ impl Editor {
hide_mouse_mode: EditorSettings::get_global(cx)
.hide_mouse
.unwrap_or_default(),
change_list: ChangeList::new(),
};
if let Some(breakpoints) = this.breakpoint_store.as_ref() {
this._subscriptions
@@ -1709,8 +1661,8 @@ impl Editor {
this._subscriptions.push(cx.subscribe_in(
&cx.entity(),
window,
|editor, _, e: &EditorEvent, window, cx| match e {
EditorEvent::ScrollPositionChanged { local, .. } => {
|editor, _, e: &EditorEvent, window, cx| {
if let EditorEvent::SelectionsChanged { local } = e {
if *local {
let new_anchor = editor.scroll_manager.anchor();
let snapshot = editor.snapshot(window, cx);
@@ -1722,30 +1674,6 @@ impl Editor {
});
}
}
EditorEvent::Edited { .. } => {
if !vim_enabled(cx) {
let (map, selections) = editor.selections.all_adjusted_display(cx);
let pop_state = editor
.change_list
.last()
.map(|previous| {
previous.len() == selections.len()
&& previous.iter().enumerate().all(|(ix, p)| {
p.to_display_point(&map).row()
== selections[ix].head().row()
})
})
.unwrap_or(false);
let new_positions = selections
.into_iter()
.map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
.collect();
editor
.change_list
.push_to_change_list(pop_state, new_positions);
}
}
_ => (),
},
));
@@ -13375,48 +13303,6 @@ impl Editor {
.or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
}
fn go_to_next_change(
&mut self,
_: &GoToNextChange,
window: &mut Window,
cx: &mut Context<Self>,
) {
if let Some(selections) = self
.change_list
.next_change(1, Direction::Next)
.map(|s| s.to_vec())
{
self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
let map = s.display_map();
s.select_display_ranges(selections.iter().map(|a| {
let point = a.to_display_point(&map);
point..point
}))
})
}
}
fn go_to_previous_change(
&mut self,
_: &GoToPreviousChange,
window: &mut Window,
cx: &mut Context<Self>,
) {
if let Some(selections) = self
.change_list
.next_change(1, Direction::Prev)
.map(|s| s.to_vec())
{
self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
let map = s.display_map();
s.select_display_ranges(selections.iter().map(|a| {
let point = a.to_display_point(&map);
point..point
}))
})
}
}
fn go_to_line<T: 'static>(
&mut self,
position: Anchor,
@@ -17825,7 +17711,11 @@ impl Editor {
.and_then(|e| e.to_str())
.map(|a| a.to_string()));
let vim_mode = vim_enabled(cx);
let vim_mode = cx
.global::<SettingsStore>()
.raw_user_settings()
.get("vim_mode")
== Some(&serde_json::Value::Bool(true));
let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
let copilot_enabled = edit_predictions_provider
@@ -18271,13 +18161,6 @@ impl Editor {
}
}
fn vim_enabled(cx: &App) -> bool {
cx.global::<SettingsStore>()
.raw_user_settings()
.get("vim_mode")
== Some(&serde_json::Value::Bool(true))
}
// Consider user intent and default settings
fn choose_completion_range(
completion: &Completion,

View File

@@ -435,8 +435,6 @@ impl EditorElement {
register_action(editor, window, Editor::stage_and_next);
register_action(editor, window, Editor::unstage_and_next);
register_action(editor, window, Editor::expand_all_diff_hunks);
register_action(editor, window, Editor::go_to_previous_change);
register_action(editor, window, Editor::go_to_next_change);
register_action(editor, window, |editor, action, window, cx| {
if let Some(task) = editor.format(action, window, cx) {

View File

@@ -43,7 +43,6 @@ toml.workspace = true
unindent.workspace = true
util.workspace = true
uuid = { version = "1.6", features = ["v4"] }
workspace-hack.workspace = true
[[bin]]
name = "eval"

View File

@@ -54,6 +54,9 @@ struct Args {
/// Maximum number of examples to run concurrently.
#[arg(long, default_value = "10")]
concurrency: usize,
/// Optional cohort ID to group runs together (useful for GitHub Actions)
#[arg(long)]
cohort_id: Option<String>,
}
fn main() {
@@ -187,6 +190,7 @@ fn main() {
);
let repo_url = example.base.url.clone();
let revision = example.base.revision.clone();
if repo_urls.insert(repo_url.clone()) {
let repo_path = repo_path_for_url(&repo_url);
@@ -201,7 +205,18 @@ fn main() {
let git_task = cx.spawn(async move |_cx| {
std::fs::create_dir_all(&repo_path)?;
run_git(&repo_path, &["init"]).await?;
run_git(&repo_path, &["remote", "add", "origin", &repo_url]).await
run_git(&repo_path, &["remote", "add", "origin", &repo_url]).await?;
run_git(
&repo_path,
&[
"fetch",
"origin",
"+refs/heads/*:refs/remotes/origin/*",
],
)
.await?;
run_git(&repo_path, &["fetch", "origin", &revision])
.await
});
clone_tasks.push(git_task);
@@ -234,15 +249,17 @@ fn main() {
let judge_repetitions = args.judge_repetitions;
let concurrency = args.concurrency;
let cohort_id = args.cohort_id.clone();
let tasks = examples
.into_iter()
.map(|example| {
let app_state = app_state.clone();
let model = model.clone();
let cohort_id = cohort_id.clone();
cx.spawn(async move |cx| {
let result =
run_example(&example, model, app_state, judge_repetitions, cx).await;
run_example(&example, model, app_state, judge_repetitions, cohort_id, cx).await;
(result, example)
})
})
@@ -317,6 +334,7 @@ async fn run_example(
model: Arc<dyn LanguageModel>,
app_state: Arc<AgentAppState>,
judge_repetitions: u32,
optional_cohort_id: Option<String>,
cx: &mut AsyncApp,
) -> Result<Vec<Result<JudgeOutput>>> {
let run_output = cx
@@ -330,12 +348,15 @@ async fn run_example(
let judge_result = example.judge(model.clone(), diff.clone(), round, cx).await;
if let Ok(judge_output) = &judge_result {
let cohort_id = example
.output_file_path
.parent()
.and_then(|p| p.file_name())
.map(|name| name.to_string_lossy().to_string())
.unwrap_or(chrono::Local::now().format("%Y-%m-%d_%H-%M-%S").to_string());
// Use the provided cohort_id if available, otherwise generate one from the output path
let cohort_id = optional_cohort_id.clone().unwrap_or_else(|| {
example
.output_file_path
.parent()
.and_then(|p| p.file_name())
.map(|name| name.to_string_lossy().to_string())
.unwrap_or(chrono::Local::now().format("%Y-%m-%d_%H-%M-%S").to_string())
});
let path = std::path::Path::new(".");
let commit_id = get_current_commit_id(path).await.unwrap_or_default();

View File

@@ -151,7 +151,7 @@ impl Example {
);
run_git(
&repo_path,
&["fetch", "--depth", "1", "origin", &self.base.revision],
&["fetch", "origin", &self.base.revision],
)
.await?;
}

View File

@@ -4468,33 +4468,36 @@ impl<'a> Iterator for BufferChunks<'a> {
}
self.diagnostic_endpoints = diagnostic_endpoints;
let chunk = self.chunks.peek()?;
let chunk_start = self.range.start;
let mut chunk_end = (self.chunks.offset() + chunk.len())
.min(next_capture_start)
.min(next_diagnostic_endpoint);
let mut highlight_id = None;
if let Some(highlights) = self.highlights.as_ref() {
if let Some((parent_capture_end, parent_highlight_id)) = highlights.stack.last() {
chunk_end = chunk_end.min(*parent_capture_end);
highlight_id = Some(*parent_highlight_id);
if let Some(chunk) = self.chunks.peek() {
let chunk_start = self.range.start;
let mut chunk_end = (self.chunks.offset() + chunk.len())
.min(next_capture_start)
.min(next_diagnostic_endpoint);
let mut highlight_id = None;
if let Some(highlights) = self.highlights.as_ref() {
if let Some((parent_capture_end, parent_highlight_id)) = highlights.stack.last() {
chunk_end = chunk_end.min(*parent_capture_end);
highlight_id = Some(*parent_highlight_id);
}
}
}
let slice = &chunk[chunk_start - self.chunks.offset()..chunk_end - self.chunks.offset()];
self.range.start = chunk_end;
if self.range.start == self.chunks.offset() + chunk.len() {
self.chunks.next().unwrap();
}
let slice =
&chunk[chunk_start - self.chunks.offset()..chunk_end - self.chunks.offset()];
self.range.start = chunk_end;
if self.range.start == self.chunks.offset() + chunk.len() {
self.chunks.next().unwrap();
}
Some(Chunk {
text: slice,
syntax_highlight_id: highlight_id,
diagnostic_severity: self.current_diagnostic_severity(),
is_unnecessary: self.current_code_is_unnecessary(),
..Default::default()
})
Some(Chunk {
text: slice,
syntax_highlight_id: highlight_id,
diagnostic_severity: self.current_diagnostic_severity(),
is_unnecessary: self.current_code_is_unnecessary(),
..Default::default()
})
} else {
None
}
}
}

View File

@@ -92,7 +92,6 @@ struct SyntaxLayerEntry {
enum SyntaxLayerContent {
Parsed {
tree: tree_sitter::Tree,
brackets: SumTree<BracketItem>,
language: Arc<Language>,
},
Pending {
@@ -116,72 +115,6 @@ impl SyntaxLayerContent {
}
}
// "fn main() { }"
// [Iso(7), Open(1), Close(1), Iso(2), Open(1), Iso(3), Close(1)]
#[derive(Clone)]
enum BracketItem {
Isomorphic { len: usize },
OpenBracket { len: usize },
CloseBracket { len: usize },
}
impl BracketItem {
pub fn len(&self) -> usize {
match self {
&Self::Isomorphic { len } => len,
&Self::OpenBracket { len } => len,
&Self::CloseBracket { len } => len,
}
}
}
#[derive(Clone, Default)]
struct BracketSummary {
len: usize,
/// The change in depth that happened inside this summary.
depth_diff: i32,
}
impl sum_tree::Summary for BracketSummary {
type Context = ();
fn zero(_: &()) -> Self {
Self::default()
}
fn add_summary(&mut self, summary: &Self, _: &()) {
self.len += summary.len;
self.depth_diff += summary.depth_diff;
}
}
impl sum_tree::Item for BracketItem {
type Summary = BracketSummary;
fn summary(&self, _: &()) -> Self::Summary {
let depth_diff = match self {
&Self::Isomorphic { .. } => 0,
&Self::OpenBracket { .. } => 1,
&Self::CloseBracket { .. } => -1,
};
BracketSummary {
len: self.len(),
depth_diff,
}
}
}
impl<'a> sum_tree::Dimension<'a, BracketSummary> for usize {
fn zero(_cx: &()) -> Self {
0
}
fn add_summary(&mut self, summary: &'a BracketSummary, _: &()) {
*self += summary.len;
}
}
/// A layer of syntax highlighting, corresponding to a single syntax
/// tree in a particular language.
#[derive(Debug)]
@@ -515,6 +448,7 @@ impl SyntaxSnapshot {
let mut changed_regions = ChangeRegionSet::default();
let mut queue = BinaryHeap::new();
let mut combined_injection_ranges = HashMap::default();
queue.push(ParseStep {
depth: 0,
language: ParseStepLanguage::Loaded {
@@ -612,13 +546,12 @@ impl SyntaxSnapshot {
}
let content = match step.language {
ParseStepLanguage::Pending { name } => SyntaxLayerContent::Pending {
language_name: name,
},
ParseStepLanguage::Loaded { language } => {
let Some(grammar) = language.grammar() else {
continue;
};
let tree;
let changed_ranges;
let mut included_ranges = step.included_ranges;
for range in &mut included_ranges {
@@ -632,14 +565,7 @@ impl SyntaxSnapshot {
.to_ts_point();
}
let (old_tree, mut brackets) = if let Some((
SyntaxLayerContent::Parsed {
tree: old_tree,
brackets,
..
},
layer_start,
)) =
if let Some((SyntaxLayerContent::Parsed { tree: old_tree, .. }, layer_start)) =
old_layer.map(|layer| (&layer.content, layer.range.start))
{
log::trace!(
@@ -675,7 +601,12 @@ impl SyntaxSnapshot {
}
if included_ranges.is_empty() {
included_ranges.push(zeroed_tree_sitter_range());
included_ranges.push(tree_sitter::Range {
start_byte: 0,
end_byte: 0,
start_point: Default::default(),
end_point: Default::default(),
});
}
log::trace!(
@@ -685,7 +616,32 @@ impl SyntaxSnapshot {
LogIncludedRanges(&included_ranges),
);
(Some(old_tree), brackets.clone())
let result = parse_text(
grammar,
text.as_rope(),
step_start_byte,
included_ranges,
Some(old_tree.clone()),
);
match result {
Ok(t) => tree = t,
Err(e) => {
log::error!("error parsing text: {:?}", e);
continue;
}
};
changed_ranges = join_ranges(
invalidated_ranges
.iter()
.filter(|&range| {
range.start <= step_end_byte && range.end >= step_start_byte
})
.cloned(),
old_tree.changed_ranges(&tree).map(|r| {
step_start_byte + r.start_byte..step_start_byte + r.end_byte
}),
);
} else {
if matches!(step.mode, ParseMode::Combined { .. }) {
insert_newlines_between_ranges(
@@ -698,7 +654,12 @@ impl SyntaxSnapshot {
}
if included_ranges.is_empty() {
included_ranges.push(zeroed_tree_sitter_range());
included_ranges.push(tree_sitter::Range {
start_byte: 0,
end_byte: 0,
start_point: Default::default(),
end_point: Default::default(),
});
}
log::trace!(
@@ -708,89 +669,58 @@ impl SyntaxSnapshot {
LogIncludedRanges(&included_ranges),
);
(None, SumTree::new(&()))
};
let result = parse_text(
grammar,
text.as_rope(),
step_start_byte,
included_ranges,
None,
);
match result {
Ok(t) => tree = t,
Err(e) => {
log::error!("error parsing text: {:?}", e);
continue;
}
};
changed_ranges = vec![step_start_byte..step_end_byte];
}
let result = parse_text(
grammar,
text.as_rope(),
step_start_byte,
included_ranges,
old_tree.cloned(),
);
let tree = match result {
Ok(inner) => inner,
Err(e) => {
log::error!("error parsing text: {:?}", e);
continue;
}
};
let changed_ranges = if let Some(old_tree) = old_tree {
join_ranges(
invalidated_ranges
.iter()
.filter(|&range| {
range.start <= step_end_byte && range.end >= step_start_byte
})
.cloned(),
old_tree.changed_ranges(&tree).map(|r| {
step_start_byte + r.start_byte..step_start_byte + r.end_byte
}),
)
} else {
vec![step_start_byte..step_end_byte]
};
// re-run queries if something changed
if !changed_ranges.is_empty() {
if let Some((config, registry)) =
grammar.injection_config.as_ref().zip(registry.as_ref())
{
for range in &changed_ranges {
let region = ChangedRegion {
if let (Some((config, registry)), false) = (
grammar.injection_config.as_ref().zip(registry.as_ref()),
changed_ranges.is_empty(),
) {
for range in &changed_ranges {
changed_regions.insert(
ChangedRegion {
depth: step.depth + 1,
range: text.anchor_before(range.start)
..text.anchor_after(range.end),
};
changed_regions.insert(region, text);
}
update_injection_parse_steps(
config,
},
text,
step.range.clone(),
tree.root_node_with_offset(
step_start_byte,
step_start_point.to_ts_point(),
),
registry,
step.depth + 1,
&changed_ranges,
&mut queue,
);
}
if let Some(config) = grammar.brackets_config.as_ref() {
update_brackets_in_range(
config,
text,
tree.root_node_with_offset(
step_start_byte,
step_start_point.to_ts_point(),
),
&changed_ranges,
&mut brackets,
);
}
get_injections(
config,
text,
step.range.clone(),
tree.root_node_with_offset(
step_start_byte,
step_start_point.to_ts_point(),
),
registry,
step.depth + 1,
&changed_ranges,
&mut combined_injection_ranges,
&mut queue,
);
}
SyntaxLayerContent::Parsed {
tree,
language,
brackets,
}
SyntaxLayerContent::Parsed { tree, language }
}
ParseStepLanguage::Pending { name } => SyntaxLayerContent::Pending {
language_name: name,
},
};
layers.push(
@@ -936,7 +866,7 @@ impl SyntaxSnapshot {
iter::from_fn(move || {
while let Some(layer) = cursor.item() {
let mut info = None;
if let SyntaxLayerContent::Parsed { tree, language, .. } = &layer.content {
if let SyntaxLayerContent::Parsed { tree, language } = &layer.content {
let layer_start_offset = layer.range.start.to_offset(buffer);
let layer_start_point = layer.range.start.to_point(buffer).to_ts_point();
if include_hidden || !language.config.hidden {
@@ -1093,7 +1023,6 @@ impl<'a> SyntaxMapCaptures<'a> {
pub struct TreeSitterOptions {
max_start_depth: Option<u32>,
}
impl TreeSitterOptions {
pub fn max_start_depth(max_start_depth: u32) -> Self {
Self {
@@ -1321,8 +1250,7 @@ fn parse_text(
})
}
#[allow(clippy::too_many_arguments)]
fn update_injection_parse_steps(
fn get_injections(
config: &InjectionConfig,
text: &BufferSnapshot,
outer_range: Range<Anchor>,
@@ -1330,16 +1258,15 @@ fn update_injection_parse_steps(
language_registry: &Arc<LanguageRegistry>,
depth: usize,
changed_ranges: &[Range<usize>],
combined_injection_ranges: &mut HashMap<LanguageId, (Arc<Language>, Vec<tree_sitter::Range>)>,
queue: &mut BinaryHeap<ParseStep>,
) {
let mut query_cursor = QueryCursorHandle::new();
let mut prev_match = None;
// Note: a `ParseStep` must be created for every combined injection language, even
// if there are currently no matches for that injection.
let mut combined_injection_ranges =
HashMap::<LanguageId, (Arc<Language>, Vec<tree_sitter::Range>)>::default();
// Ensure that a `ParseStep` is created for every combined injection language, even
// if there currently no matches for that injection.
combined_injection_ranges.clear();
for pattern in &config.patterns {
if let (Some(language_name), true) = (pattern.language.as_ref(), pattern.combined) {
if let Some(language) = language_registry
@@ -1363,6 +1290,7 @@ fn update_injection_parse_steps(
if content_ranges.is_empty() {
continue;
}
let content_range =
content_ranges.first().unwrap().start_byte..content_ranges.last().unwrap().end_byte;
@@ -1453,46 +1381,6 @@ fn update_injection_parse_steps(
}
}
fn update_brackets_in_range(
config: &BracketConfig,
text: &BufferSnapshot,
node: Node,
changed_ranges: &[Range<usize>],
brackets_tree: &mut SumTree<BracketItem>,
) {
let mut query_cursor = QueryCursorHandle::new();
for changed_range in changed_ranges {
let mut new_bracket_subtree = SumTree::new(&());
// TODO: is this saturating_sub necessary?
query_cursor.set_byte_range(changed_range.start.saturating_sub(1)..changed_range.end + 1);
for mat in query_cursor.matches(&config.query, node, TextProvider(text.as_rope())) {
let open_capture = mat.nodes_for_capture_index(config.open_capture_ix).next();
let close_capture = mat.nodes_for_capture_index(config.close_capture_ix).next();
let Some((open_capture, close_capture)) = open_capture.zip(close_capture) else {
log::warn!("couldn't find @open and @close captures in brackets pattern");
continue;
};
todo!("how do I insert this into the SumTree, or, should I put it in a BTreeSet");
// cursor.see
}
let mut cursor = brackets_tree.cursor::<usize>(&());
let mut left_side = cursor.slice(&changed_range.start, Bias::Left, &());
cursor.seek(&changed_range.end, Bias::Right, &());
let right_side = cursor.suffix(&());
left_side.append(new_bracket_subtree, &());
left_side.append(right_side, &());
drop(cursor); // make the borrow checker happy
*brackets_tree = left_side;
}
}
/// Updates the given list of included `ranges`, removing any ranges that intersect
/// `removed_ranges`, and inserting the given `new_ranges`.
///
@@ -2015,12 +1903,3 @@ impl fmt::Debug for LogPoint {
(self.0.row, self.0.column).fmt(f)
}
}
fn zeroed_tree_sitter_range() -> tree_sitter::Range {
tree_sitter::Range {
start_byte: 0,
end_byte: 0,
start_point: Default::default(),
end_point: Default::default(),
}
}

View File

@@ -97,10 +97,7 @@ pub struct TokenUsage {
impl TokenUsage {
pub fn total_tokens(&self) -> u32 {
self.input_tokens
+ self.output_tokens
+ self.cache_read_input_tokens
+ self.cache_creation_input_tokens
self.input_tokens + self.output_tokens
}
}

View File

@@ -705,12 +705,12 @@ pub fn map_to_language_model_completion_events(
update_usage(&mut state.usage, &message.usage);
return Some((
vec![
Ok(LanguageModelCompletionEvent::UsageUpdate(convert_usage(
&state.usage,
))),
Ok(LanguageModelCompletionEvent::StartMessage {
message_id: message.id,
}),
Ok(LanguageModelCompletionEvent::UsageUpdate(convert_usage(
&state.usage,
))),
],
state,
));

View File

@@ -1,6 +1,9 @@
use anthropic::{AnthropicError, AnthropicModelMode, parse_prompt_too_long};
use anyhow::{Result, anyhow};
use client::{Client, UserStore, zed_urls};
use client::{
Client, EXPIRED_LLM_TOKEN_HEADER_NAME, MAX_LLM_MONTHLY_SPEND_REACHED_HEADER_NAME,
PerformCompletionParams, UserStore, zed_urls,
};
use collections::BTreeMap;
use feature_flags::{FeatureFlagAppExt, LlmClosedBeta, ZedPro};
use futures::{
@@ -23,6 +26,7 @@ use language_model::{
use proto::Plan;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize, de::DeserializeOwned};
use serde_json::value::RawValue;
use settings::{Settings, SettingsStore};
use smol::Timer;
use smol::io::{AsyncReadExt, BufReader};
@@ -34,11 +38,7 @@ use std::{
use strum::IntoEnumIterator;
use thiserror::Error;
use ui::{TintColor, prelude::*};
use zed_llm_client::{
CURRENT_PLAN_HEADER_NAME, CompletionBody, EXPIRED_LLM_TOKEN_HEADER_NAME,
MAX_LLM_MONTHLY_SPEND_REACHED_HEADER_NAME, MODEL_REQUESTS_RESOURCE_HEADER_VALUE,
SUBSCRIPTION_LIMIT_RESOURCE_HEADER_NAME,
};
use zed_llm_client::{CURRENT_PLAN_HEADER_NAME, SUBSCRIPTION_LIMIT_RESOURCE_HEADER_NAME};
use crate::AllLanguageModelSettings;
use crate::provider::anthropic::{count_anthropic_tokens, into_anthropic};
@@ -517,7 +517,7 @@ impl CloudLanguageModel {
async fn perform_llm_completion(
client: Arc<Client>,
llm_api_token: LlmApiToken,
body: CompletionBody,
body: PerformCompletionParams,
) -> Result<Response<AsyncBody>> {
let http_client = &client.http_client();
@@ -561,7 +561,7 @@ impl CloudLanguageModel {
.get(SUBSCRIPTION_LIMIT_RESOURCE_HEADER_NAME)
.is_some()
{
if let Some(MODEL_REQUESTS_RESOURCE_HEADER_VALUE) = response
if let Some("model_requests") = response
.headers()
.get(SUBSCRIPTION_LIMIT_RESOURCE_HEADER_NAME)
.and_then(|resource| resource.to_str().ok())
@@ -575,7 +575,6 @@ impl CloudLanguageModel {
let plan = match plan {
zed_llm_client::Plan::Free => Plan::Free,
zed_llm_client::Plan::ZedPro => Plan::ZedPro,
zed_llm_client::Plan::ZedProTrial => Plan::ZedProTrial,
};
return Err(anyhow!(ModelRequestLimitReachedError { plan }));
}
@@ -725,10 +724,12 @@ impl LanguageModel for CloudLanguageModel {
let response = Self::perform_llm_completion(
client.clone(),
llm_api_token,
CompletionBody {
provider: zed_llm_client::LanguageModelProvider::Anthropic,
PerformCompletionParams {
provider: client::LanguageModelProvider::Anthropic,
model: request.model.clone(),
provider_request: serde_json::to_value(&request)?,
provider_request: RawValue::from_string(serde_json::to_string(
&request,
)?)?,
},
)
.await
@@ -764,10 +765,12 @@ impl LanguageModel for CloudLanguageModel {
let response = Self::perform_llm_completion(
client.clone(),
llm_api_token,
CompletionBody {
provider: zed_llm_client::LanguageModelProvider::OpenAi,
PerformCompletionParams {
provider: client::LanguageModelProvider::OpenAi,
model: request.model.clone(),
provider_request: serde_json::to_value(&request)?,
provider_request: RawValue::from_string(serde_json::to_string(
&request,
)?)?,
},
)
.await?;
@@ -787,10 +790,12 @@ impl LanguageModel for CloudLanguageModel {
let response = Self::perform_llm_completion(
client.clone(),
llm_api_token,
CompletionBody {
provider: zed_llm_client::LanguageModelProvider::Google,
PerformCompletionParams {
provider: client::LanguageModelProvider::Google,
model: request.model.clone(),
provider_request: serde_json::to_value(&request)?,
provider_request: RawValue::from_string(serde_json::to_string(
&request,
)?)?,
},
)
.await?;

View File

@@ -206,12 +206,12 @@ impl Render for KeyContextView {
.mt_4()
.gap_4()
.child(
Button::new("open_documentation", "Open Documentation")
Button::new("default", "Open Documentation")
.style(ButtonStyle::Filled)
.on_click(|_, _, cx| cx.open_url("https://zed.dev/docs/key-bindings")),
)
.child(
Button::new("view_default_keymap", "View default keymap")
Button::new("default", "View default keymap")
.style(ButtonStyle::Filled)
.key_binding(ui::KeyBinding::for_action(
&zed_actions::OpenDefaultKeymap,
@@ -219,14 +219,16 @@ impl Render for KeyContextView {
cx
))
.on_click(|_, window, cx| {
window.dispatch_action(workspace::SplitRight.boxed_clone(), cx);
window.dispatch_action(zed_actions::OpenDefaultKeymap.boxed_clone(), cx);
}),
)
.child(
Button::new("edit_your_keymap", "Edit your keymap")
Button::new("default", "Edit your keymap")
.style(ButtonStyle::Filled)
.key_binding(ui::KeyBinding::for_action(&zed_actions::OpenKeymap, window, cx))
.on_click(|_, window, cx| {
window.dispatch_action(workspace::SplitRight.boxed_clone(), cx);
window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx);
}),
),

View File

@@ -39,9 +39,7 @@ pub(crate) mod m_2025_03_29 {
}
pub(crate) mod m_2025_04_15 {
mod keymap;
mod settings;
pub(crate) use keymap::KEYMAP_PATTERNS;
pub(crate) use settings::SETTINGS_PATTERNS;
}

View File

@@ -1,31 +0,0 @@
use collections::HashMap;
use std::{ops::Range, sync::LazyLock};
use tree_sitter::{Query, QueryMatch};
use crate::MigrationPatterns;
use crate::patterns::KEYMAP_ACTION_STRING_PATTERN;
pub const KEYMAP_PATTERNS: MigrationPatterns =
&[(KEYMAP_ACTION_STRING_PATTERN, replace_string_action)];
fn replace_string_action(
contents: &str,
mat: &QueryMatch,
query: &Query,
) -> Option<(Range<usize>, String)> {
let action_name_ix = query.capture_index_for_name("action_name")?;
let action_name_node = mat.nodes_for_capture_index(action_name_ix).next()?;
let action_name_range = action_name_node.byte_range();
let action_name = contents.get(action_name_range.clone())?;
if let Some(new_action_name) = STRING_REPLACE.get(&action_name) {
return Some((action_name_range, new_action_name.to_string()));
}
None
}
/// "ctrl-k ctrl-1": "inline_completion::ToggleMenu" -> "edit_prediction::ToggleMenu"
static STRING_REPLACE: LazyLock<HashMap<&str, &str>> = LazyLock::new(|| {
HashMap::from_iter([("outline_panel::Open", "outline_panel::OpenSelectedEntry")])
});

View File

@@ -98,10 +98,6 @@ pub fn migrate_keymap(text: &str) -> Result<Option<String>> {
migrations::m_2025_03_06::KEYMAP_PATTERNS,
&KEYMAP_QUERY_2025_03_06,
),
(
migrations::m_2025_04_15::KEYMAP_PATTERNS,
&KEYMAP_QUERY_2025_04_15,
),
];
run_migrations(text, migrations)
}
@@ -180,10 +176,6 @@ define_query!(
KEYMAP_QUERY_2025_03_06,
migrations::m_2025_03_06::KEYMAP_PATTERNS
);
define_query!(
KEYMAP_QUERY_2025_04_15,
migrations::m_2025_04_15::KEYMAP_PATTERNS
);
// settings
define_query!(

View File

@@ -70,7 +70,7 @@ actions!(
ExpandAllEntries,
ExpandSelectedEntry,
FoldDirectory,
OpenSelectedEntry,
Open,
RevealInFileManager,
SelectParent,
ToggleActiveEditorPin,
@@ -922,12 +922,7 @@ impl OutlinePanel {
self.update_cached_entries(None, window, cx);
}
fn open_selected_entry(
&mut self,
_: &OpenSelectedEntry,
window: &mut Window,
cx: &mut Context<Self>,
) {
fn open(&mut self, _: &Open, window: &mut Window, cx: &mut Context<Self>) {
if self.filter_editor.focus_handle(cx).is_focused(window) {
cx.propagate()
} else if let Some(selected_entry) = self.selected_entry().cloned() {
@@ -4911,7 +4906,7 @@ impl Render for OutlinePanel {
}
}))
.key_context(self.dispatch_context(window, cx))
.on_action(cx.listener(Self::open_selected_entry))
.on_action(cx.listener(Self::open))
.on_action(cx.listener(Self::cancel))
.on_action(cx.listener(Self::select_next))
.on_action(cx.listener(Self::select_previous))
@@ -5682,7 +5677,7 @@ mod tests {
});
outline_panel.update_in(cx, |outline_panel, window, cx| {
outline_panel.open_selected_entry(&OpenSelectedEntry, window, cx);
outline_panel.open(&Open, window, cx);
});
outline_panel.update(cx, |_outline_panel, cx| {
assert_eq!(
@@ -5857,7 +5852,7 @@ mod tests {
outline_panel.update_in(cx, |outline_panel, window, cx| {
outline_panel.select_previous(&SelectPrevious, window, cx);
outline_panel.open_selected_entry(&OpenSelectedEntry, window, cx);
outline_panel.open(&Open, window, cx);
});
cx.executor()
.advance_clock(UPDATE_DEBOUNCE + Duration::from_millis(100));
@@ -5881,7 +5876,7 @@ mod tests {
outline_panel.update_in(cx, |outline_panel, window, cx| {
outline_panel.select_next(&SelectNext, window, cx);
outline_panel.open_selected_entry(&OpenSelectedEntry, window, cx);
outline_panel.open(&Open, window, cx);
});
cx.executor()
.advance_clock(UPDATE_DEBOUNCE + Duration::from_millis(100));
@@ -5902,7 +5897,7 @@ mod tests {
});
outline_panel.update_in(cx, |outline_panel, window, cx| {
outline_panel.open_selected_entry(&OpenSelectedEntry, window, cx);
outline_panel.open(&Open, window, cx);
});
cx.executor()
.advance_clock(UPDATE_DEBOUNCE + Duration::from_millis(100));

View File

@@ -792,48 +792,10 @@ fn create_new_session(
this.update(cx, |_, cx| {
cx.subscribe(
&session,
move |this: &mut DapStore, session, event: &SessionStateEvent, cx| match event {
move |this: &mut DapStore, _, event: &SessionStateEvent, cx| match event {
SessionStateEvent::Shutdown => {
this.shutdown_session(session_id, cx).detach_and_log_err(cx);
}
SessionStateEvent::Restart => {
let Some((config, binary)) = session.read_with(cx, |session, _| {
session
.configuration()
.map(|config| (config, session.binary().clone()))
}) else {
log::error!("Failed to get debug config from session");
return;
};
let mut curr_session = session;
while let Some(parent_id) = curr_session.read(cx).parent_id() {
if let Some(parent_session) = this.sessions.get(&parent_id).cloned() {
curr_session = parent_session;
} else {
log::error!("Failed to get parent session from parent session id");
break;
}
}
let session_id = curr_session.read(cx).session_id();
let task = curr_session.update(cx, |session, cx| session.shutdown(cx));
cx.spawn(async move |this, cx| {
task.await;
this.update(cx, |this, cx| {
this.sessions.remove(&session_id);
this.new_session(binary, config, None, cx)
})?
.1
.await?;
anyhow::Ok(())
})
.detach_and_log_err(cx);
}
},
)
.detach();

View File

@@ -397,7 +397,6 @@ impl LocalMode {
self.definition.initialize_args.clone().unwrap_or(json!({})),
&mut raw.configuration,
);
// Of relevance: https://github.com/microsoft/vscode/issues/4902#issuecomment-368583522
let launch = match raw.request {
dap::StartDebuggingRequestArgumentsRequest::Launch => self.request(
@@ -685,9 +684,8 @@ pub enum SessionEvent {
Threads,
}
pub(super) enum SessionStateEvent {
pub(crate) enum SessionStateEvent {
Shutdown,
Restart,
}
impl EventEmitter<SessionEvent> for Session {}
@@ -1364,18 +1362,6 @@ impl Session {
&self.loaded_sources
}
fn fallback_to_manual_restart(
&mut self,
res: Result<()>,
cx: &mut Context<Self>,
) -> Option<()> {
if res.log_err().is_none() {
cx.emit(SessionStateEvent::Restart);
return None;
}
Some(())
}
fn empty_response(&mut self, res: Result<()>, _cx: &mut Context<Self>) -> Option<()> {
res.log_err()?;
Some(())
@@ -1435,17 +1421,26 @@ impl Session {
}
pub fn restart(&mut self, args: Option<Value>, cx: &mut Context<Self>) {
if self.capabilities.supports_restart_request.unwrap_or(false) && !self.is_terminated() {
if self.capabilities.supports_restart_request.unwrap_or(false) {
self.request(
RestartCommand {
raw: args.unwrap_or(Value::Null),
},
Self::fallback_to_manual_restart,
Self::empty_response,
cx,
)
.detach();
} else {
cx.emit(SessionStateEvent::Restart);
self.request(
DisconnectCommand {
restart: Some(false),
terminate_debuggee: Some(true),
suspend_debuggee: Some(false),
},
Self::empty_response,
cx,
)
.detach();
}
}
@@ -1480,14 +1475,8 @@ impl Session {
cx.emit(SessionStateEvent::Shutdown);
let debug_client = self.adapter_client();
cx.background_spawn(async move {
let _ = task.await;
if let Some(client) = debug_client {
client.shutdown().await.log_err();
}
})
}

View File

@@ -1216,7 +1216,7 @@ impl TextDimension for TextSummary {
impl<'a> sum_tree::Dimension<'a, ChunkSummary> for usize {
fn zero(_cx: &()) -> Self {
0
Default::default()
}
fn add_summary(&mut self, summary: &'a ChunkSummary, _: &()) {

35
crates/rpc/src/llm.rs Normal file
View File

@@ -0,0 +1,35 @@
use serde::{Deserialize, Serialize};
use strum::{Display, EnumIter, EnumString};
pub const EXPIRED_LLM_TOKEN_HEADER_NAME: &str = "x-zed-expired-token";
pub const MAX_LLM_MONTHLY_SPEND_REACHED_HEADER_NAME: &str = "x-zed-llm-max-monthly-spend-reached";
#[derive(
Debug, PartialEq, Eq, Hash, Clone, Copy, Serialize, Deserialize, EnumString, EnumIter, Display,
)]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum LanguageModelProvider {
Anthropic,
OpenAi,
Google,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct LanguageModel {
pub provider: LanguageModelProvider,
pub name: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ListModelsResponse {
pub models: Vec<LanguageModel>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct PerformCompletionParams {
pub provider: LanguageModelProvider,
pub model: String,
pub provider_request: Box<serde_json::value::RawValue>,
}

View File

@@ -1,12 +1,14 @@
pub mod auth;
mod conn;
mod extension;
mod llm;
mod message_stream;
mod notification;
mod peer;
pub use conn::Connection;
pub use extension::*;
pub use llm::*;
pub use notification::*;
pub use peer::*;
pub use proto;

View File

@@ -372,7 +372,13 @@ where
"Must call `seek`, `next` or `prev` before calling this method"
);
}
}
impl<'a, T, D> Cursor<'a, T, D>
where
T: Item,
D: Dimension<'a, T::Summary>,
{
#[track_caller]
pub fn seek<Target>(
&mut self,

View File

@@ -98,6 +98,62 @@ impl DebugRequestDisposition {
}
}
}
/// Represents the configuration for the debug adapter
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct DebugAdapterConfig {
/// Name of the debug task
pub label: String,
/// The type of adapter you want to use
pub adapter: String,
/// The type of request that should be called on the debug adapter
pub request: DebugRequestDisposition,
/// Additional initialization arguments to be sent on DAP initialization
pub initialize_args: Option<serde_json::Value>,
/// Optional TCP connection information
///
/// If provided, this will be used to connect to the debug adapter instead of
/// spawning a new process. This is useful for connecting to a debug adapter
/// that is already running or is started by another process.
pub tcp_connection: Option<TCPHost>,
/// What Locator to use to configure the debug task
pub locator: Option<String>,
/// Whether to tell the debug adapter to stop on entry
pub stop_on_entry: Option<bool>,
}
impl From<DebugTaskDefinition> for DebugAdapterConfig {
fn from(def: DebugTaskDefinition) -> Self {
Self {
label: def.label,
adapter: def.adapter,
request: DebugRequestDisposition::UserConfigured(def.request),
initialize_args: def.initialize_args,
tcp_connection: def.tcp_connection,
locator: def.locator,
stop_on_entry: def.stop_on_entry,
}
}
}
impl TryFrom<DebugAdapterConfig> for DebugTaskDefinition {
type Error = ();
fn try_from(def: DebugAdapterConfig) -> Result<Self, Self::Error> {
let request = match def.request {
DebugRequestDisposition::UserConfigured(debug_request_type) => debug_request_type,
DebugRequestDisposition::ReverseRequest(_) => return Err(()),
};
Ok(Self {
label: def.label,
adapter: def.adapter,
request,
initialize_args: def.initialize_args,
tcp_connection: def.tcp_connection,
locator: def.locator,
stop_on_entry: def.stop_on_entry,
})
}
}
impl TryFrom<TaskTemplate> for DebugTaskDefinition {
type Error = ();

View File

@@ -16,8 +16,8 @@ use std::path::PathBuf;
use std::str::FromStr;
pub use debug_format::{
AttachConfig, DebugConnectionType, DebugRequestDisposition, DebugRequestType,
DebugTaskDefinition, DebugTaskFile, LaunchConfig, TCPHost,
AttachConfig, DebugAdapterConfig, DebugConnectionType, DebugRequestDisposition,
DebugRequestType, DebugTaskDefinition, DebugTaskFile, LaunchConfig, TCPHost,
};
pub use task_template::{
DebugArgs, DebugArgsRequest, HideStrategy, RevealStrategy, TaskModal, TaskTemplate,

View File

@@ -73,11 +73,11 @@ impl Tab {
self
}
pub fn content_height(cx: &App) -> Pixels {
pub fn content_height(cx: &mut App) -> Pixels {
DynamicSpacing::Base32.px(cx) - px(1.)
}
pub fn container_height(cx: &App) -> Pixels {
pub fn container_height(cx: &mut App) -> Pixels {
DynamicSpacing::Base32.px(cx)
}
}

View File

@@ -1,4 +1,6 @@
use editor::{Bias, Direction, Editor, display_map::ToDisplayPoint, movement, scroll::Autoscroll};
use editor::{
Anchor, Bias, Direction, Editor, display_map::ToDisplayPoint, movement, scroll::Autoscroll,
};
use gpui::{Context, Window, actions};
use crate::{Vim, state::Mode};
@@ -23,60 +25,68 @@ impl Vim {
) {
let count = Vim::take_count(cx).unwrap_or(1);
Vim::take_forced_motion(cx);
if self.change_list.is_empty() {
return;
}
let prev = self.change_list_position.unwrap_or(self.change_list.len());
let next = if direction == Direction::Prev {
prev.saturating_sub(count)
} else {
(prev + count).min(self.change_list.len() - 1)
};
self.change_list_position = Some(next);
let Some(selections) = self.change_list.get(next).cloned() else {
return;
};
self.update_editor(window, cx, |_, editor, window, cx| {
if let Some(selections) = editor
.change_list
.next_change(count, direction)
.map(|s| s.to_vec())
{
editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
let map = s.display_map();
s.select_display_ranges(selections.iter().map(|a| {
let point = a.to_display_point(&map);
point..point
}))
})
};
editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
let map = s.display_map();
s.select_display_ranges(selections.into_iter().map(|a| {
let point = a.to_display_point(&map);
point..point
}))
})
});
}
pub(crate) fn push_to_change_list(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let Some((new_positions, buffer)) = self.update_editor(window, cx, |vim, editor, _, cx| {
let Some((map, selections, buffer)) = self.update_editor(window, cx, |_, editor, _, cx| {
let (map, selections) = editor.selections.all_adjusted_display(cx);
let buffer = editor.buffer().clone();
let pop_state = editor
.change_list
.last()
.map(|previous| {
previous.len() == selections.len()
&& previous.iter().enumerate().all(|(ix, p)| {
p.to_display_point(&map).row() == selections[ix].head().row()
})
})
.unwrap_or(false);
let new_positions = selections
.into_iter()
.map(|s| {
let point = if vim.mode == Mode::Insert {
movement::saturating_left(&map, s.head())
} else {
s.head()
};
map.display_point_to_anchor(point, Bias::Left)
})
.collect::<Vec<_>>();
editor
.change_list
.push_to_change_list(pop_state, new_positions.clone());
(new_positions, buffer)
(map, selections, buffer)
}) else {
return;
};
let pop_state = self
.change_list
.last()
.map(|previous| {
previous.len() == selections.len()
&& previous.iter().enumerate().all(|(ix, p)| {
p.to_display_point(&map).row() == selections[ix].head().row()
})
})
.unwrap_or(false);
let new_positions: Vec<Anchor> = selections
.into_iter()
.map(|s| {
let point = if self.mode == Mode::Insert {
movement::saturating_left(&map, s.head())
} else {
s.head()
};
map.display_point_to_anchor(point, Bias::Left)
})
.collect();
self.change_list_position.take();
if pop_state {
self.change_list.pop();
}
self.change_list.push(new_positions.clone());
self.set_mark(".".to_string(), new_positions, &buffer, window, cx)
}
}

View File

@@ -323,6 +323,8 @@ pub(crate) struct Vim {
pub(crate) replacements: Vec<(Range<editor::Anchor>, String)>,
pub(crate) stored_visual_mode: Option<(Mode, Vec<bool>)>,
pub(crate) change_list: Vec<Vec<Anchor>>,
pub(crate) change_list_position: Option<usize>,
pub(crate) current_tx: Option<TransactionId>,
pub(crate) current_anchor: Option<Selection<Anchor>>,
@@ -368,6 +370,8 @@ impl Vim {
replacements: Vec::new(),
stored_visual_mode: None,
change_list: Vec::new(),
change_list_position: None,
current_tx: None,
current_anchor: None,
undo_modes: HashMap::default(),

View File

@@ -168,39 +168,14 @@ If you are defining shortcuts in your personal keymap, you can opt into the key
If you'd like a given binding to do nothing in a given context you can use
`null` as the action. This is useful if you hit the keybinding by accident and
want to disable it, or if you want to type the character that would be typed by
the sequence, or if you want to disable multikey bindings starting with that key.
the sequence (for example to disable the builtin `alt-t` binding), or if you
want to disable multikey bindings starting with that key.
```json
[
{
"context": "Workspace",
"bindings": {
"cmd-r": null // cmd-r will do nothing when the Workspace context is active
}
}
]
```
"bindings": { "cmd-k": null }
```
A `null` binding follows the same precedence rules as normal actions. So disables all bindings that would match further up in the tree too. If you'd like a binding that matches further up in the tree to take precedence over a lower binding, you need to rebind it to the action you want in the context you want.
This is useful for preventing Zed from falling back to a default keybinding when the action you specified is conditional and propagates. For example, `buffer_search::DeployReplace` only triggers when the search bar is not in view. If the search bar is in view, it would propagate and trigger the default action set for that binding, such as opening the right dock. To prevent this from happening:
```json
[
{
"context": "Workspace",
"bindings": {
"cmd-r": null // cmd-r will do nothing when the search bar is in view
}
},
{
"context": "Workspace",
"bindings": {
"cmd-r": "buffer_search::DeployReplace" // cmd-r will deploy replace when the search bar is not in view
}
}
]
```
A `null` has the same precedence rules as normal actions. So disables all bindings that would match further up in the tree too. If you'd like a binding that matches further up in the tree to take precedence over a lower binding, you need to rebind it to the action you want in the context you want.
### Remapping keys