From ba807a3c46d279947f2f475391008e4f42a87492 Mon Sep 17 00:00:00 2001
From: Afief Abdurrahman <91366510+mafiefa02@users.noreply.github.com>
Date: Tue, 9 Dec 2025 12:38:29 +0700
Subject: [PATCH 01/48] languages: Initialize Tailwind's options with
`includeLanguages` (#43978)
Since [this
PR](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1014),
the `tailwindCSS.userLanguages` option has been deprecated, and it is
recommended to use `tailwindCSS.includeLanguages` instead. Using
`tailwindCSS.userLanguages` triggers the warning shown below in the
`tailwindcss-language-server` logs.
Release Notes:
- Fixed a warning indicating the deprecation of
`tailwindCSS.userLanguages` by initializing the options with
`tailwindCSS.includeLanguages`.
---------
Co-authored-by: Smit Barmase
---
crates/languages/src/tailwind.rs | 19 +++++++++++--------
1 file changed, 11 insertions(+), 8 deletions(-)
diff --git a/crates/languages/src/tailwind.rs b/crates/languages/src/tailwind.rs
index 6375b70c427399579d6c557879b6f357eb87bd3f..7e23c4ba5255c0413904797d1f8094e67834fa6a 100644
--- a/crates/languages/src/tailwind.rs
+++ b/crates/languages/src/tailwind.rs
@@ -140,13 +140,6 @@ impl LspAdapter for TailwindLspAdapter {
) -> Result> {
Ok(Some(json!({
"provideFormatter": true,
- "userLanguages": {
- "html": "html",
- "css": "css",
- "javascript": "javascript",
- "typescript": "typescript",
- "typescriptreact": "typescriptreact",
- },
})))
}
@@ -167,8 +160,18 @@ impl LspAdapter for TailwindLspAdapter {
tailwind_user_settings["emmetCompletions"] = Value::Bool(true);
}
+ if tailwind_user_settings.get("includeLanguages").is_none() {
+ tailwind_user_settings["includeLanguages"] = json!({
+ "html": "html",
+ "css": "css",
+ "javascript": "javascript",
+ "typescript": "typescript",
+ "typescriptreact": "typescriptreact",
+ });
+ }
+
Ok(json!({
- "tailwindCSS": tailwind_user_settings,
+ "tailwindCSS": tailwind_user_settings
}))
}
From dc5f54eaf94568ac0c77a9bddd51d1983811d1ae Mon Sep 17 00:00:00 2001
From: Mikayla Maki
Date: Mon, 8 Dec 2025 23:15:50 -0800
Subject: [PATCH 02/48] Backout inline assistant changes (#44454)
Release Notes:
- N/A
---
crates/feature_flags/src/flags.rs | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/crates/feature_flags/src/flags.rs b/crates/feature_flags/src/flags.rs
index fe11a7b5eaa162a90ae8ba3f691ca804ab64db2d..61d9a34e38de546c79a2dbb5f889e2fddad38480 100644
--- a/crates/feature_flags/src/flags.rs
+++ b/crates/feature_flags/src/flags.rs
@@ -16,4 +16,8 @@ pub struct InlineAssistantV2FeatureFlag;
impl FeatureFlag for InlineAssistantV2FeatureFlag {
const NAME: &'static str = "inline-assistant-v2";
+
+ fn enabled_for_staff() -> bool {
+ false
+ }
}
From 0b4f72e54926ec446f659e4af127f13180825a35 Mon Sep 17 00:00:00 2001
From: Kirill Bulatov
Date: Tue, 9 Dec 2025 09:50:10 +0200
Subject: [PATCH 03/48] Tidy up single-file worktrees' opening errors (#44455)
Part of https://github.com/zed-industries/zed/issues/44370
Also log when fail to open the project item.
Release Notes:
- N/A
---
crates/project/src/buffer_store.rs | 18 +++-
crates/project/src/invalid_item_view.rs | 118 ----------------------
crates/workspace/src/invalid_item_view.rs | 1 +
crates/workspace/src/workspace.rs | 1 +
4 files changed, 17 insertions(+), 121 deletions(-)
delete mode 100644 crates/project/src/invalid_item_view.rs
diff --git a/crates/project/src/buffer_store.rs b/crates/project/src/buffer_store.rs
index daafa014b28f62b04ece0f391c714d6ea699326c..f4a0d45bc86c39be595439bfe1aebb2533b62783 100644
--- a/crates/project/src/buffer_store.rs
+++ b/crates/project/src/buffer_store.rs
@@ -620,9 +620,21 @@ impl LocalBufferStore {
let load_file = worktree.update(cx, |worktree, cx| worktree.load_file(path.as_ref(), cx));
cx.spawn(async move |this, cx| {
let path = path.clone();
- let buffer = match load_file.await.with_context(|| {
- format!("Could not open path: {}", path.display(PathStyle::local()))
- }) {
+ let single_file_path = cx.update(|cx| {
+ if worktree.read(cx).is_single_file() {
+ Some(worktree.read(cx).abs_path())
+ } else {
+ None
+ }
+ })?;
+ let path_string = single_file_path
+ .as_ref()
+ .map(|path| path.to_string_lossy())
+ .unwrap_or_else(|| path.display(PathStyle::local()));
+ let buffer = match load_file
+ .await
+ .with_context(|| format!("Opening path \"{path_string}\""))
+ {
Ok(loaded) => {
let reservation = cx.reserve_entity::()?;
let buffer_id = BufferId::from(reservation.entity_id().as_non_zero_u64());
diff --git a/crates/project/src/invalid_item_view.rs b/crates/project/src/invalid_item_view.rs
deleted file mode 100644
index fdcdd16a69ce73d8471f8387d55cf91576f114af..0000000000000000000000000000000000000000
--- a/crates/project/src/invalid_item_view.rs
+++ /dev/null
@@ -1,118 +0,0 @@
-use std::{path::Path, sync::Arc};
-
-use gpui::{EventEmitter, FocusHandle, Focusable};
-use ui::{
- App, Button, ButtonCommon, ButtonStyle, Clickable, Context, FluentBuilder, InteractiveElement,
- KeyBinding, Label, LabelCommon, LabelSize, ParentElement, Render, SharedString, Styled as _,
- Window, h_flex, v_flex,
-};
-use zed_actions::workspace::OpenWithSystem;
-
-use crate::Item;
-
-/// A view to display when a certain buffer fails to open.
-#[derive(Debug)]
-pub struct InvalidItemView {
- /// Which path was attempted to open.
- pub abs_path: Arc,
- /// An error message, happened when opening the buffer.
- pub error: SharedString,
- is_local: bool,
- focus_handle: FocusHandle,
-}
-
-impl InvalidItemView {
- pub fn new(
- abs_path: &Path,
- is_local: bool,
- e: &anyhow::Error,
- _: &mut Window,
- cx: &mut App,
- ) -> Self {
- Self {
- is_local,
- abs_path: Arc::from(abs_path),
- error: format!("{}", e.root_cause()).into(),
- focus_handle: cx.focus_handle(),
- }
- }
-}
-
-impl Item for InvalidItemView {
- type Event = ();
-
- fn tab_content_text(&self, mut detail: usize, _: &App) -> SharedString {
- // Ensure we always render at least the filename.
- detail += 1;
-
- let path = self.abs_path.as_ref();
-
- let mut prefix = path;
- while detail > 0 {
- if let Some(parent) = prefix.parent() {
- prefix = parent;
- detail -= 1;
- } else {
- break;
- }
- }
-
- let path = if detail > 0 {
- path
- } else {
- path.strip_prefix(prefix).unwrap_or(path)
- };
-
- SharedString::new(path.to_string_lossy())
- }
-}
-
-impl EventEmitter<()> for InvalidItemView {}
-
-impl Focusable for InvalidItemView {
- fn focus_handle(&self, _: &App) -> FocusHandle {
- self.focus_handle.clone()
- }
-}
-
-impl Render for InvalidItemView {
- fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl gpui::IntoElement {
- let abs_path = self.abs_path.clone();
- v_flex()
- .size_full()
- .track_focus(&self.focus_handle(cx))
- .flex_none()
- .justify_center()
- .overflow_hidden()
- .key_context("InvalidBuffer")
- .child(
- h_flex().size_full().justify_center().child(
- v_flex()
- .justify_center()
- .gap_2()
- .child(h_flex().justify_center().child("Could not open file"))
- .child(
- h_flex()
- .justify_center()
- .child(Label::new(self.error.clone()).size(LabelSize::Small)),
- )
- .when(self.is_local, |contents| {
- contents.child(
- h_flex().justify_center().child(
- Button::new("open-with-system", "Open in Default App")
- .on_click(move |_, _, cx| {
- cx.open_with_system(&abs_path);
- })
- .style(ButtonStyle::Outlined)
- .key_binding(KeyBinding::for_action(
- &OpenWithSystem,
- window,
- cx,
- )),
- ),
- )
- }),
- ),
- )
- }
-}
diff --git a/crates/workspace/src/invalid_item_view.rs b/crates/workspace/src/invalid_item_view.rs
index eb6c8f3299838c1a01777885009fa67271b924d7..08242a1ed0c86bb465c85f79a2047b89f9dc86d2 100644
--- a/crates/workspace/src/invalid_item_view.rs
+++ b/crates/workspace/src/invalid_item_view.rs
@@ -11,6 +11,7 @@ use zed_actions::workspace::OpenWithSystem;
use crate::Item;
/// A view to display when a certain buffer/image/other item fails to open.
+#[derive(Debug)]
pub struct InvalidItemView {
/// Which path was attempted to open.
pub abs_path: Arc,
diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs
index 43dd4bd701fdc8c0cf394043daf35d08d6c05328..cc3ba7577ae6a0d8af889bcde174a00f185dd502 100644
--- a/crates/workspace/src/workspace.rs
+++ b/crates/workspace/src/workspace.rs
@@ -675,6 +675,7 @@ impl ProjectItemRegistry {
Ok((project_entry_id, build_workspace_item))
}
Err(e) => {
+ log::warn!("Failed to open a project item: {e:#}");
if e.error_code() == ErrorCode::Internal {
if let Some(abs_path) =
entry_abs_path.as_deref().filter(|_| is_file)
From 4e75f0f3ab86a6140533fcad83595d4f5e3fcd67 Mon Sep 17 00:00:00 2001
From: Jason Lee
Date: Tue, 9 Dec 2025 16:08:59 +0800
Subject: [PATCH 04/48] gpui: Implement `From` for ElementId (#44447)
Release Notes:
- N/A
## Before
```rs
div()
.id(SharedString::from(format!("process-entry-{ix}-command")))
```
## After
```rs
div()
.id(format!("process-entry-{ix}-command"))
```
---
crates/agent_ui/src/acp/thread_view.rs | 2 +-
crates/agent_ui/src/agent_configuration.rs | 2 +-
.../configure_context_server_tools_modal.rs | 2 +-
.../src/agent_configuration/manage_profiles_modal.rs | 4 ++--
crates/agent_ui/src/profile_selector.rs | 2 +-
crates/debugger_ui/src/attach_modal.rs | 4 ++--
crates/debugger_ui/src/new_process_modal.rs | 2 +-
crates/debugger_ui/src/session/running.rs | 9 +++------
crates/git_ui/src/branch_picker.rs | 2 +-
crates/git_ui/src/picker_prompt.rs | 2 +-
crates/git_ui/src/stash_picker.rs | 2 +-
crates/git_ui/src/worktree_picker.rs | 2 +-
crates/gpui/examples/painting.rs | 6 +++---
crates/gpui/examples/window.rs | 6 +++---
crates/gpui/src/window.rs | 12 ++++++++++++
crates/livekit_client/examples/test_app.rs | 2 +-
crates/tasks_ui/src/modal.rs | 6 +++---
crates/title_bar/src/application_menu.rs | 8 ++++----
18 files changed, 42 insertions(+), 33 deletions(-)
diff --git a/crates/agent_ui/src/acp/thread_view.rs b/crates/agent_ui/src/acp/thread_view.rs
index 7b2954c2edafa6b610efe654c8c1368f04a374b6..36fb7e9097488f8070b740a63ed67ee74445602a 100644
--- a/crates/agent_ui/src/acp/thread_view.rs
+++ b/crates/agent_ui/src/acp/thread_view.rs
@@ -3509,7 +3509,7 @@ impl AcpThreadView {
(method.id.0.clone(), method.name.clone())
};
- Button::new(SharedString::from(method_id.clone()), name)
+ Button::new(method_id.clone(), name)
.label_size(LabelSize::Small)
.map(|this| {
if ix == 0 {
diff --git a/crates/agent_ui/src/agent_configuration.rs b/crates/agent_ui/src/agent_configuration.rs
index 3e4654af6e7b47055611cf81886cc0a93436a264..aa4cbc8e5b261d1953a91fb090e7ecd28b4e3a31 100644
--- a/crates/agent_ui/src/agent_configuration.rs
+++ b/crates/agent_ui/src/agent_configuration.rs
@@ -838,7 +838,7 @@ impl AgentConfiguration {
.min_w_0()
.child(
h_flex()
- .id(SharedString::from(format!("tooltip-{}", item_id)))
+ .id(format!("tooltip-{}", item_id))
.h_full()
.w_3()
.mr_2()
diff --git a/crates/agent_ui/src/agent_configuration/configure_context_server_tools_modal.rs b/crates/agent_ui/src/agent_configuration/configure_context_server_tools_modal.rs
index 3573c8b67ee81ef9cd1decacefb52017dabdb178..5115e2f70c0ae87cdd3ca3901a64aed09de68b0f 100644
--- a/crates/agent_ui/src/agent_configuration/configure_context_server_tools_modal.rs
+++ b/crates/agent_ui/src/agent_configuration/configure_context_server_tools_modal.rs
@@ -87,7 +87,7 @@ impl ConfigureContextServerToolsModal {
v_flex()
.child(
h_flex()
- .id(SharedString::from(format!("tool-header-{}", index)))
+ .id(format!("tool-header-{}", index))
.py_1()
.pl_1()
.pr_2()
diff --git a/crates/agent_ui/src/agent_configuration/manage_profiles_modal.rs b/crates/agent_ui/src/agent_configuration/manage_profiles_modal.rs
index 7e03dc46b704c22b4665bbce0f3b818134b56634..2f17349c3d1da1cf68a3ab513ccad434a115087b 100644
--- a/crates/agent_ui/src/agent_configuration/manage_profiles_modal.rs
+++ b/crates/agent_ui/src/agent_configuration/manage_profiles_modal.rs
@@ -422,7 +422,7 @@ impl ManageProfilesModal {
let is_focused = profile.navigation.focus_handle.contains_focused(window, cx);
div()
- .id(SharedString::from(format!("profile-{}", profile.id)))
+ .id(format!("profile-{}", profile.id))
.track_focus(&profile.navigation.focus_handle)
.on_action({
let profile_id = profile.id.clone();
@@ -431,7 +431,7 @@ impl ManageProfilesModal {
})
})
.child(
- ListItem::new(SharedString::from(format!("profile-{}", profile.id)))
+ ListItem::new(format!("profile-{}", profile.id))
.toggle_state(is_focused)
.inset(true)
.spacing(ListItemSpacing::Sparse)
diff --git a/crates/agent_ui/src/profile_selector.rs b/crates/agent_ui/src/profile_selector.rs
index c1949d22e268e8744db7834a58d1a3303fa4e236..0182be0912d3b8a8a046371ce725e7d21a0ddb58 100644
--- a/crates/agent_ui/src/profile_selector.rs
+++ b/crates/agent_ui/src/profile_selector.rs
@@ -542,7 +542,7 @@ impl PickerDelegate for ProfilePickerDelegate {
let is_active = active_id == candidate.id;
Some(
- ListItem::new(SharedString::from(candidate.id.0.clone()))
+ ListItem::new(candidate.id.0.clone())
.inset(true)
.spacing(ListItemSpacing::Sparse)
.toggle_state(selected)
diff --git a/crates/debugger_ui/src/attach_modal.rs b/crates/debugger_ui/src/attach_modal.rs
index 64146169f53cfe44c3bdcb59b93e78d0f9223abd..6e537ae0c6e1db7418596cf48b51ca22df30be57 100644
--- a/crates/debugger_ui/src/attach_modal.rs
+++ b/crates/debugger_ui/src/attach_modal.rs
@@ -317,7 +317,7 @@ impl PickerDelegate for AttachModalDelegate {
let candidate = self.candidates.get(hit.candidate_id)?;
Some(
- ListItem::new(SharedString::from(format!("process-entry-{ix}")))
+ ListItem::new(format!("process-entry-{ix}"))
.inset(true)
.spacing(ListItemSpacing::Sparse)
.toggle_state(selected)
@@ -327,7 +327,7 @@ impl PickerDelegate for AttachModalDelegate {
.child(Label::new(format!("{} {}", candidate.name, candidate.pid)))
.child(
div()
- .id(SharedString::from(format!("process-entry-{ix}-command")))
+ .id(format!("process-entry-{ix}-command"))
.tooltip(Tooltip::text(
candidate
.command
diff --git a/crates/debugger_ui/src/new_process_modal.rs b/crates/debugger_ui/src/new_process_modal.rs
index 24f6cc8f341519425b33433c3324f8afe7401ab7..8aaa61aad6380752a7bdd62ee35635ebb6d160e4 100644
--- a/crates/debugger_ui/src/new_process_modal.rs
+++ b/crates/debugger_ui/src/new_process_modal.rs
@@ -1519,7 +1519,7 @@ impl PickerDelegate for DebugDelegate {
});
Some(
- ListItem::new(SharedString::from(format!("debug-scenario-selection-{ix}")))
+ ListItem::new(format!("debug-scenario-selection-{ix}"))
.inset(true)
.start_slot::(icon)
.spacing(ListItemSpacing::Sparse)
diff --git a/crates/debugger_ui/src/session/running.rs b/crates/debugger_ui/src/session/running.rs
index bc99d6ac8e42b0a706df4a09177ae2103d5939e2..66e9dd7b434e628898add7056b15c1789e32519c 100644
--- a/crates/debugger_ui/src/session/running.rs
+++ b/crates/debugger_ui/src/session/running.rs
@@ -286,10 +286,10 @@ impl Item for SubView {
impl Render for SubView {
fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement {
v_flex()
- .id(SharedString::from(format!(
+ .id(format!(
"subview-container-{}",
self.kind.to_shared_string()
- )))
+ ))
.on_hover(cx.listener(|this, hovered, _, cx| {
this.hovered = *hovered;
cx.notify();
@@ -484,10 +484,7 @@ pub(crate) fn new_debugger_pane(
let deemphasized = !pane.has_focus(window, cx);
let item_ = item.boxed_clone();
div()
- .id(SharedString::from(format!(
- "debugger_tab_{}",
- item.item_id().as_u64()
- )))
+ .id(format!("debugger_tab_{}", item.item_id().as_u64()))
.p_1()
.rounded_md()
.cursor_pointer()
diff --git a/crates/git_ui/src/branch_picker.rs b/crates/git_ui/src/branch_picker.rs
index e198fa092e0cc2d0e8b77ba954fd743512915c75..90b5c4bb284112c8a13ad406da2b7424e982298a 100644
--- a/crates/git_ui/src/branch_picker.rs
+++ b/crates/git_ui/src/branch_picker.rs
@@ -911,7 +911,7 @@ impl PickerDelegate for BranchListDelegate {
});
Some(
- ListItem::new(SharedString::from(format!("vcs-menu-{ix}")))
+ ListItem::new(format!("vcs-menu-{ix}"))
.inset(true)
.spacing(ListItemSpacing::Sparse)
.toggle_state(selected)
diff --git a/crates/git_ui/src/picker_prompt.rs b/crates/git_ui/src/picker_prompt.rs
index 6161c62af571f3a90c3110d63cc26ea3a7e032ae..14daedda61ecc71cebe8f7778fee2f8193e65a73 100644
--- a/crates/git_ui/src/picker_prompt.rs
+++ b/crates/git_ui/src/picker_prompt.rs
@@ -220,7 +220,7 @@ impl PickerDelegate for PickerPromptDelegate {
let shortened_option = util::truncate_and_trailoff(&hit.string, self.max_match_length);
Some(
- ListItem::new(SharedString::from(format!("picker-prompt-menu-{ix}")))
+ ListItem::new(format!("picker-prompt-menu-{ix}"))
.inset(true)
.spacing(ListItemSpacing::Sparse)
.toggle_state(selected)
diff --git a/crates/git_ui/src/stash_picker.rs b/crates/git_ui/src/stash_picker.rs
index fd81176a127e6032ebb84f1c8afdb6f61a5aa9b8..6d0a9d291e4a8c7096c525b9b401e54e599b0b53 100644
--- a/crates/git_ui/src/stash_picker.rs
+++ b/crates/git_ui/src/stash_picker.rs
@@ -464,7 +464,7 @@ impl PickerDelegate for StashListDelegate {
);
Some(
- ListItem::new(SharedString::from(format!("stash-{ix}")))
+ ListItem::new(format!("stash-{ix}"))
.inset(true)
.spacing(ListItemSpacing::Sparse)
.toggle_state(selected)
diff --git a/crates/git_ui/src/worktree_picker.rs b/crates/git_ui/src/worktree_picker.rs
index d1231b51e3a37db2b3ee2316e866fcbdbe70d459..f6b3e47dec386d906e55e555600a93059d0766d0 100644
--- a/crates/git_ui/src/worktree_picker.rs
+++ b/crates/git_ui/src/worktree_picker.rs
@@ -665,7 +665,7 @@ impl PickerDelegate for WorktreeListDelegate {
};
Some(
- ListItem::new(SharedString::from(format!("worktree-menu-{ix}")))
+ ListItem::new(format!("worktree-menu-{ix}"))
.inset(true)
.spacing(ListItemSpacing::Sparse)
.toggle_state(selected)
diff --git a/crates/gpui/examples/painting.rs b/crates/gpui/examples/painting.rs
index e7055cbdbbd781523edbc851d143bf56a551728f..9f15d12f469fa6ec5c7be52d30a63b30163ff254 100644
--- a/crates/gpui/examples/painting.rs
+++ b/crates/gpui/examples/painting.rs
@@ -1,7 +1,7 @@
use gpui::{
Application, Background, Bounds, ColorSpace, Context, MouseDownEvent, Path, PathBuilder,
- PathStyle, Pixels, Point, Render, SharedString, StrokeOptions, Window, WindowOptions, canvas,
- div, linear_color_stop, linear_gradient, point, prelude::*, px, quad, rgb, size,
+ PathStyle, Pixels, Point, Render, StrokeOptions, Window, WindowOptions, canvas, div,
+ linear_color_stop, linear_gradient, point, prelude::*, px, quad, rgb, size,
};
struct PaintingViewer {
@@ -309,7 +309,7 @@ fn button(
on_click: impl Fn(&mut PaintingViewer, &mut Context) + 'static,
) -> impl IntoElement {
div()
- .id(SharedString::from(text.to_string()))
+ .id(text.to_string())
.child(text.to_string())
.bg(gpui::black())
.text_color(gpui::white())
diff --git a/crates/gpui/examples/window.rs b/crates/gpui/examples/window.rs
index 4445f24e4ec0f2809109964fd34610cad1299e90..06003c4663ee5711283a85684c25b9f5d8c5b743 100644
--- a/crates/gpui/examples/window.rs
+++ b/crates/gpui/examples/window.rs
@@ -1,6 +1,6 @@
use gpui::{
- App, Application, Bounds, Context, KeyBinding, PromptButton, PromptLevel, SharedString, Timer,
- Window, WindowBounds, WindowKind, WindowOptions, actions, div, prelude::*, px, rgb, size,
+ App, Application, Bounds, Context, KeyBinding, PromptButton, PromptLevel, Timer, Window,
+ WindowBounds, WindowKind, WindowOptions, actions, div, prelude::*, px, rgb, size,
};
struct SubWindow {
@@ -9,7 +9,7 @@ struct SubWindow {
fn button(text: &str, on_click: impl Fn(&mut Window, &mut App) + 'static) -> impl IntoElement {
div()
- .id(SharedString::from(text.to_string()))
+ .id(text.to_string())
.flex_none()
.px_2()
.bg(rgb(0xf7f7f7))
diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs
index 2d525adb8f82a96c24ee3f524030782a7de3577c..69bf583057fdca4e0b3a71fc552c37c3319123ec 100644
--- a/crates/gpui/src/window.rs
+++ b/crates/gpui/src/window.rs
@@ -5084,6 +5084,18 @@ impl From for ElementId {
}
}
+impl From for ElementId {
+ fn from(name: String) -> Self {
+ ElementId::Name(name.into())
+ }
+}
+
+impl From> for ElementId {
+ fn from(name: Arc) -> Self {
+ ElementId::Name(name.into())
+ }
+}
+
impl From> for ElementId {
fn from(path: Arc) -> Self {
ElementId::Path(path)
diff --git a/crates/livekit_client/examples/test_app.rs b/crates/livekit_client/examples/test_app.rs
index 7b9b0183a087d1618e1a177a5ed09fe455c4fcec..a4d815aa9be6a84df95083ae979691c109a668cb 100644
--- a/crates/livekit_client/examples/test_app.rs
+++ b/crates/livekit_client/examples/test_app.rs
@@ -378,7 +378,7 @@ impl Render for LivekitWindow {
.when_some(state.audio_output_stream.as_ref(), |el, state| {
el.child(
button()
- .id(SharedString::from(identity.0.clone()))
+ .id(identity.0.clone())
.child(if state.0.is_enabled() {
"Deafen"
} else {
diff --git a/crates/tasks_ui/src/modal.rs b/crates/tasks_ui/src/modal.rs
index 0497512b762fd141e8bc727b66354f7fbcef7925..644f82285b26f02a6011d59141b94de14a0e2bbf 100644
--- a/crates/tasks_ui/src/modal.rs
+++ b/crates/tasks_ui/src/modal.rs
@@ -5,8 +5,8 @@ use editor::Editor;
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{
Action, AnyElement, App, AppContext as _, Context, DismissEvent, Entity, EventEmitter,
- Focusable, InteractiveElement, ParentElement, Render, SharedString, Styled, Subscription, Task,
- WeakEntity, Window, rems,
+ Focusable, InteractiveElement, ParentElement, Render, Styled, Subscription, Task, WeakEntity,
+ Window, rems,
};
use itertools::Itertools;
use picker::{Picker, PickerDelegate, highlighted_match_with_paths::HighlightedMatch};
@@ -526,7 +526,7 @@ impl PickerDelegate for TasksModalDelegate {
};
Some(
- ListItem::new(SharedString::from(format!("tasks-modal-{ix}")))
+ ListItem::new(format!("tasks-modal-{ix}"))
.inset(true)
.start_slot::(icon)
.end_slot::(
diff --git a/crates/title_bar/src/application_menu.rs b/crates/title_bar/src/application_menu.rs
index 01a12260ad03284d77dfda19fdf2286cf6196ca8..817b73c45ecd2df4a76e9a67f425b2b459c0c026 100644
--- a/crates/title_bar/src/application_menu.rs
+++ b/crates/title_bar/src/application_menu.rs
@@ -151,10 +151,10 @@ impl ApplicationMenu {
// Application menu must have same ids as first menu item in standard menu
div()
- .id(SharedString::from(format!("{}-menu-item", menu_name)))
+ .id(format!("{}-menu-item", menu_name))
.occlude()
.child(
- PopoverMenu::new(SharedString::from(format!("{}-menu-popover", menu_name)))
+ PopoverMenu::new(format!("{}-menu-popover", menu_name))
.menu(move |window, cx| {
Self::build_menu_from_items(entry.clone(), window, cx).into()
})
@@ -184,10 +184,10 @@ impl ApplicationMenu {
.collect();
div()
- .id(SharedString::from(format!("{}-menu-item", menu_name)))
+ .id(format!("{}-menu-item", menu_name))
.occlude()
.child(
- PopoverMenu::new(SharedString::from(format!("{}-menu-popover", menu_name)))
+ PopoverMenu::new(format!("{}-menu-popover", menu_name))
.menu(move |window, cx| {
Self::build_menu_from_items(entry.clone(), window, cx).into()
})
From 6253b1d220d6bf2033494b458985ea3ecdd9b3b0 Mon Sep 17 00:00:00 2001
From: Lukas Wirth
Date: Tue, 9 Dec 2025 09:30:36 +0100
Subject: [PATCH 05/48] worktree: Print canonicalization error details (#44459)
cc https://github.com/zed-industries/zed/issues/24714
Release Notes:
- N/A *or* Added/Fixed/Improved ...
---
crates/worktree/src/worktree.rs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/crates/worktree/src/worktree.rs b/crates/worktree/src/worktree.rs
index 5d1baceb2cebcadb54f5b47f357470861bb5b964..4df7a93f13e3c1ff80f716141a2db727b7a5e693 100644
--- a/crates/worktree/src/worktree.rs
+++ b/crates/worktree/src/worktree.rs
@@ -3814,7 +3814,7 @@ impl BackgroundScanner {
let root_canonical_path = match &root_canonical_path {
Ok(path) => SanitizedPath::new(path),
Err(err) => {
- log::error!("failed to canonicalize root path {root_path:?}: {err}");
+ log::error!("failed to canonicalize root path {root_path:?}: {err:#}");
return true;
}
};
From 9d49c1ffda05036cca0aa0a835a493e48bdb7c5f Mon Sep 17 00:00:00 2001
From: Lena <241371603+zelenenka@users.noreply.github.com>
Date: Tue, 9 Dec 2025 10:24:06 +0100
Subject: [PATCH 06/48] Switch from labels to types in Top-Ranking issues
(#44383)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Since we've run a script to replace labels with types on the open issues
(e.g. label 'bug' → type 'Bug'), and those labels are deprecated, the
script is updated to deal with issue types only.
Other changes:
- only get top-100 search results for each section since we only look at
top-50 anyway: this way we don't need to deal with rate limiting, and
the entire script runs way faster when it doesn't need to fetch 1000+
bugs
- subtract the "-1" reactions from the "+1" reactions on a given issue
to give a slightly more accurate picture in the overall ranking (this
can further be improved by adding the distinct heart reactions but we'll
leave that for another day)
- only output the issues with a score > 0
- use Typer's built-in error handling for a missing argument
- since we're only dealing with types and not labels now, remove the
handling of potentially duplicate issues in the search results per
section
- make `Tracking` its own section since this issue type exists now
- remove the `unlabeled` section with issues of no type since all the
open issues have a type now and we intend to keep it that way for the
sake of these and other stats (and also because GitHub's REST API has
caught up with types but not with `no:type`)
- replace pygithub and custom classes with requests directly to the
GitHub API and plain data structures for a lighter footprint
- spell out the date of the update in the resulting text to avoid the
ambiguity (10/6 → October 06).
The way the script is invoked has not been changed.
Example run:
```
*Updated on December 08, 2025 06:57 AM (EST)*
## Features
1. https://github.com/zed-industries/zed/issues/11473 (679 :thumbsup:)
2. https://github.com/zed-industries/zed/issues/4642 (674 :thumbsup:)
3. https://github.com/zed-industries/zed/issues/10910 (638 :thumbsup:)
4. https://github.com/zed-industries/zed/issues/8279 (592 :thumbsup:)
5. https://github.com/zed-industries/zed/issues/5242 (581 :thumbsup:)
6. https://github.com/zed-industries/zed/issues/4355 (552 :thumbsup:)
7. https://github.com/zed-industries/zed/issues/15968 (453 :thumbsup:)
8. https://github.com/zed-industries/zed/issues/4930 (357 :thumbsup:)
9. https://github.com/zed-industries/zed/issues/5066 (345 :thumbsup:)
10. https://github.com/zed-industries/zed/issues/5120 (312 :thumbsup:)
11. https://github.com/zed-industries/zed/issues/7450 (310 :thumbsup:)
12. https://github.com/zed-industries/zed/issues/14801 (291 :thumbsup:)
13. https://github.com/zed-industries/zed/issues/10696 (276 :thumbsup:)
14. https://github.com/zed-industries/zed/issues/16965 (258 :thumbsup:)
15. https://github.com/zed-industries/zed/issues/4688 (231 :thumbsup:)
16. https://github.com/zed-industries/zed/issues/4943 (228 :thumbsup:)
17. https://github.com/zed-industries/zed/issues/9459 (223 :thumbsup:)
18. https://github.com/zed-industries/zed/issues/21538 (223 :thumbsup:)
19. https://github.com/zed-industries/zed/issues/11889 (194 :thumbsup:)
20. https://github.com/zed-industries/zed/issues/9721 (180 :thumbsup:)
21. https://github.com/zed-industries/zed/issues/5039 (172 :thumbsup:)
22. https://github.com/zed-industries/zed/issues/9662 (162 :thumbsup:)
23. https://github.com/zed-industries/zed/issues/4888 (160 :thumbsup:)
24. https://github.com/zed-industries/zed/issues/26823 (158 :thumbsup:)
25. https://github.com/zed-industries/zed/issues/21208 (151 :thumbsup:)
26. https://github.com/zed-industries/zed/issues/4991 (149 :thumbsup:)
27. https://github.com/zed-industries/zed/issues/6722 (144 :thumbsup:)
28. https://github.com/zed-industries/zed/issues/18490 (139 :thumbsup:)
29. https://github.com/zed-industries/zed/issues/10647 (138 :thumbsup:)
30. https://github.com/zed-industries/zed/issues/35803 (121 :thumbsup:)
31. https://github.com/zed-industries/zed/issues/4808 (118 :thumbsup:)
32. https://github.com/zed-industries/zed/issues/12406 (118 :thumbsup:)
33. https://github.com/zed-industries/zed/issues/37074 (118 :thumbsup:)
34. https://github.com/zed-industries/zed/issues/7121 (117 :thumbsup:)
35. https://github.com/zed-industries/zed/issues/15098 (112 :thumbsup:)
36. https://github.com/zed-industries/zed/issues/4867 (111 :thumbsup:)
37. https://github.com/zed-industries/zed/issues/4751 (108 :thumbsup:)
38. https://github.com/zed-industries/zed/issues/14473 (98 :thumbsup:)
39. https://github.com/zed-industries/zed/issues/6754 (97 :thumbsup:)
40. https://github.com/zed-industries/zed/issues/11138 (97 :thumbsup:)
41. https://github.com/zed-industries/zed/issues/17455 (90 :thumbsup:)
42. https://github.com/zed-industries/zed/issues/9922 (89 :thumbsup:)
43. https://github.com/zed-industries/zed/issues/4504 (87 :thumbsup:)
44. https://github.com/zed-industries/zed/issues/17353 (85 :thumbsup:)
45. https://github.com/zed-industries/zed/issues/4663 (82 :thumbsup:)
46. https://github.com/zed-industries/zed/issues/12039 (79 :thumbsup:)
47. https://github.com/zed-industries/zed/issues/11107 (75 :thumbsup:)
48. https://github.com/zed-industries/zed/issues/11565 (73 :thumbsup:)
49. https://github.com/zed-industries/zed/issues/22373 (72 :thumbsup:)
50. https://github.com/zed-industries/zed/issues/11023 (71 :thumbsup:)
## Bugs
1. https://github.com/zed-industries/zed/issues/7992 (457 :thumbsup:)
2. https://github.com/zed-industries/zed/issues/12589 (113 :thumbsup:)
3. https://github.com/zed-industries/zed/issues/12176 (105 :thumbsup:)
4. https://github.com/zed-industries/zed/issues/14053 (96 :thumbsup:)
5. https://github.com/zed-industries/zed/issues/18698 (90 :thumbsup:)
6. https://github.com/zed-industries/zed/issues/8043 (73 :thumbsup:)
7. https://github.com/zed-industries/zed/issues/7465 (65 :thumbsup:)
8. https://github.com/zed-industries/zed/issues/9403 (56 :thumbsup:)
9. https://github.com/zed-industries/zed/issues/9789 (55 :thumbsup:)
10. https://github.com/zed-industries/zed/issues/30313 (52 :thumbsup:)
11. https://github.com/zed-industries/zed/issues/13564 (47 :thumbsup:)
12. https://github.com/zed-industries/zed/issues/18673 (47 :thumbsup:)
13. https://github.com/zed-industries/zed/issues/43025 (44 :thumbsup:)
14. https://github.com/zed-industries/zed/issues/15166 (43 :thumbsup:)
15. https://github.com/zed-industries/zed/issues/14074 (41 :thumbsup:)
16. https://github.com/zed-industries/zed/issues/38109 (39 :thumbsup:)
17. https://github.com/zed-industries/zed/issues/21076 (38 :thumbsup:)
18. https://github.com/zed-industries/zed/issues/32792 (38 :thumbsup:)
19. https://github.com/zed-industries/zed/issues/26875 (36 :thumbsup:)
20. https://github.com/zed-industries/zed/issues/21146 (35 :thumbsup:)
21. https://github.com/zed-industries/zed/issues/39163 (35 :thumbsup:)
22. https://github.com/zed-industries/zed/issues/13838 (32 :thumbsup:)
23. https://github.com/zed-industries/zed/issues/16727 (32 :thumbsup:)
24. https://github.com/zed-industries/zed/issues/9057 (31 :thumbsup:)
25. https://github.com/zed-industries/zed/issues/38151 (31 :thumbsup:)
26. https://github.com/zed-industries/zed/issues/38750 (30 :thumbsup:)
27. https://github.com/zed-industries/zed/issues/8352 (29 :thumbsup:)
28. https://github.com/zed-industries/zed/issues/11744 (29 :thumbsup:)
29. https://github.com/zed-industries/zed/issues/20559 (29 :thumbsup:)
30. https://github.com/zed-industries/zed/issues/23640 (29 :thumbsup:)
31. https://github.com/zed-industries/zed/issues/11104 (27 :thumbsup:)
32. https://github.com/zed-industries/zed/issues/13461 (27 :thumbsup:)
33. https://github.com/zed-industries/zed/issues/13286 (25 :thumbsup:)
34. https://github.com/zed-industries/zed/issues/29962 (25 :thumbsup:)
35. https://github.com/zed-industries/zed/issues/14833 (23 :thumbsup:)
36. https://github.com/zed-industries/zed/issues/15409 (23 :thumbsup:)
37. https://github.com/zed-industries/zed/issues/11127 (22 :thumbsup:)
38. https://github.com/zed-industries/zed/issues/12835 (22 :thumbsup:)
39. https://github.com/zed-industries/zed/issues/31351 (22 :thumbsup:)
40. https://github.com/zed-industries/zed/issues/33942 (22 :thumbsup:)
41. https://github.com/zed-industries/zed/issues/7086 (21 :thumbsup:)
42. https://github.com/zed-industries/zed/issues/13176 (20 :thumbsup:)
43. https://github.com/zed-industries/zed/issues/14222 (20 :thumbsup:)
44. https://github.com/zed-industries/zed/issues/29757 (20 :thumbsup:)
45. https://github.com/zed-industries/zed/issues/35122 (20 :thumbsup:)
46. https://github.com/zed-industries/zed/issues/29807 (19 :thumbsup:)
47. https://github.com/zed-industries/zed/issues/4701 (18 :thumbsup:)
48. https://github.com/zed-industries/zed/issues/35770 (18 :thumbsup:)
49. https://github.com/zed-industries/zed/issues/37734 (18 :thumbsup:)
50. https://github.com/zed-industries/zed/issues/4434 (17 :thumbsup:)
## Tracking issues
1. https://github.com/zed-industries/zed/issues/7808 (298 :thumbsup:)
2. https://github.com/zed-industries/zed/issues/24878 (101 :thumbsup:)
3. https://github.com/zed-industries/zed/issues/7371 (60 :thumbsup:)
4. https://github.com/zed-industries/zed/issues/26916 (51 :thumbsup:)
5. https://github.com/zed-industries/zed/issues/31102 (41 :thumbsup:)
6. https://github.com/zed-industries/zed/issues/25469 (30 :thumbsup:)
7. https://github.com/zed-industries/zed/issues/10906 (18 :thumbsup:)
8. https://github.com/zed-industries/zed/issues/9778 (11 :thumbsup:)
9. https://github.com/zed-industries/zed/issues/23930 (10 :thumbsup:)
10. https://github.com/zed-industries/zed/issues/23914 (8 :thumbsup:)
11. https://github.com/zed-industries/zed/issues/18078 (7 :thumbsup:)
12. https://github.com/zed-industries/zed/issues/25560 (6 :thumbsup:)
## Crashes
1. https://github.com/zed-industries/zed/issues/13190 (33 :thumbsup:)
2. https://github.com/zed-industries/zed/issues/32318 (15 :thumbsup:)
3. https://github.com/zed-industries/zed/issues/39097 (14 :thumbsup:)
4. https://github.com/zed-industries/zed/issues/31149 (11 :thumbsup:)
5. https://github.com/zed-industries/zed/issues/36139 (10 :thumbsup:)
6. https://github.com/zed-industries/zed/issues/39890 (10 :thumbsup:)
7. https://github.com/zed-industries/zed/issues/16120 (9 :thumbsup:)
8. https://github.com/zed-industries/zed/issues/20970 (5 :thumbsup:)
9. https://github.com/zed-industries/zed/issues/28385 (5 :thumbsup:)
10. https://github.com/zed-industries/zed/issues/27270 (4 :thumbsup:)
11. https://github.com/zed-industries/zed/issues/30466 (4 :thumbsup:)
12. https://github.com/zed-industries/zed/issues/37593 (4 :thumbsup:)
13. https://github.com/zed-industries/zed/issues/27751 (3 :thumbsup:)
14. https://github.com/zed-industries/zed/issues/29467 (3 :thumbsup:)
15. https://github.com/zed-industries/zed/issues/39806 (3 :thumbsup:)
16. https://github.com/zed-industries/zed/issues/40998 (3 :thumbsup:)
17. https://github.com/zed-industries/zed/issues/10992 (2 :thumbsup:)
18. https://github.com/zed-industries/zed/issues/31461 (2 :thumbsup:)
19. https://github.com/zed-industries/zed/issues/37291 (2 :thumbsup:)
20. https://github.com/zed-industries/zed/issues/38275 (2 :thumbsup:)
21. https://github.com/zed-industries/zed/issues/43547 (2 :thumbsup:)
22. https://github.com/zed-industries/zed/issues/20014 (1 :thumbsup:)
23. https://github.com/zed-industries/zed/issues/30993 (1 :thumbsup:)
24. https://github.com/zed-industries/zed/issues/31498 (1 :thumbsup:)
25. https://github.com/zed-industries/zed/issues/31829 (1 :thumbsup:)
26. https://github.com/zed-industries/zed/issues/32280 (1 :thumbsup:)
27. https://github.com/zed-industries/zed/issues/36036 (1 :thumbsup:)
28. https://github.com/zed-industries/zed/issues/37918 (1 :thumbsup:)
29. https://github.com/zed-industries/zed/issues/39269 (1 :thumbsup:)
30. https://github.com/zed-industries/zed/issues/42825 (1 :thumbsup:)
31. https://github.com/zed-industries/zed/issues/43522 (1 :thumbsup:)
32. https://github.com/zed-industries/zed/issues/43774 (1 :thumbsup:)
## Windows
1. https://github.com/zed-industries/zed/issues/12288 (36 :thumbsup:)
2. https://github.com/zed-industries/zed/issues/20559 (29 :thumbsup:)
3. https://github.com/zed-industries/zed/issues/12013 (15 :thumbsup:)
4. https://github.com/zed-industries/zed/issues/38682 (8 :thumbsup:)
5. https://github.com/zed-industries/zed/issues/36241 (7 :thumbsup:)
6. https://github.com/zed-industries/zed/issues/28497 (3 :thumbsup:)
7. https://github.com/zed-industries/zed/issues/33748 (3 :thumbsup:)
8. https://github.com/zed-industries/zed/issues/38348 (3 :thumbsup:)
9. https://github.com/zed-industries/zed/issues/41649 (3 :thumbsup:)
10. https://github.com/zed-industries/zed/issues/41734 (3 :thumbsup:)
11. https://github.com/zed-industries/zed/issues/42873 (3 :thumbsup:)
12. https://github.com/zed-industries/zed/issues/36318 (2 :thumbsup:)
13. https://github.com/zed-industries/zed/issues/38886 (2 :thumbsup:)
14. https://github.com/zed-industries/zed/issues/39038 (2 :thumbsup:)
15. https://github.com/zed-industries/zed/issues/39056 (2 :thumbsup:)
16. https://github.com/zed-industries/zed/issues/39189 (2 :thumbsup:)
17. https://github.com/zed-industries/zed/issues/39473 (2 :thumbsup:)
18. https://github.com/zed-industries/zed/issues/39764 (2 :thumbsup:)
19. https://github.com/zed-industries/zed/issues/40430 (2 :thumbsup:)
20. https://github.com/zed-industries/zed/issues/43051 (2 :thumbsup:)
21. https://github.com/zed-industries/zed/issues/18765 (1 :thumbsup:)
22. https://github.com/zed-industries/zed/issues/35174 (1 :thumbsup:)
23. https://github.com/zed-industries/zed/issues/35958 (1 :thumbsup:)
24. https://github.com/zed-industries/zed/issues/36193 (1 :thumbsup:)
25. https://github.com/zed-industries/zed/issues/36849 (1 :thumbsup:)
26. https://github.com/zed-industries/zed/issues/38760 (1 :thumbsup:)
27. https://github.com/zed-industries/zed/issues/39346 (1 :thumbsup:)
28. https://github.com/zed-industries/zed/issues/39435 (1 :thumbsup:)
29. https://github.com/zed-industries/zed/issues/39453 (1 :thumbsup:)
30. https://github.com/zed-industries/zed/issues/39927 (1 :thumbsup:)
31. https://github.com/zed-industries/zed/issues/40209 (1 :thumbsup:)
32. https://github.com/zed-industries/zed/issues/40277 (1 :thumbsup:)
33. https://github.com/zed-industries/zed/issues/40370 (1 :thumbsup:)
34. https://github.com/zed-industries/zed/issues/40392 (1 :thumbsup:)
35. https://github.com/zed-industries/zed/issues/40475 (1 :thumbsup:)
36. https://github.com/zed-industries/zed/issues/40585 (1 :thumbsup:)
37. https://github.com/zed-industries/zed/issues/40647 (1 :thumbsup:)
38. https://github.com/zed-industries/zed/issues/40954 (1 :thumbsup:)
39. https://github.com/zed-industries/zed/issues/42050 (1 :thumbsup:)
40. https://github.com/zed-industries/zed/issues/42366 (1 :thumbsup:)
41. https://github.com/zed-industries/zed/issues/42731 (1 :thumbsup:)
42. https://github.com/zed-industries/zed/issues/42861 (1 :thumbsup:)
43. https://github.com/zed-industries/zed/issues/43522 (1 :thumbsup:)
## Meta issues
1. https://github.com/zed-industries/zed/issues/24804 (10 :thumbsup:)
2. https://github.com/zed-industries/zed/issues/36730 (3 :thumbsup:)
```
Release Notes:
- N/A
---------
Co-authored-by: Joseph T. Lyons
---
script/update_top_ranking_issues/main.py | 277 +++++++---------
.../update_top_ranking_issues/pyproject.toml | 3 +-
script/update_top_ranking_issues/uv.lock | 302 ++++++------------
3 files changed, 207 insertions(+), 375 deletions(-)
diff --git a/script/update_top_ranking_issues/main.py b/script/update_top_ranking_issues/main.py
index c6ea1a6cde6ea7641d48dddfec3cf26c3b5175bb..a8728e94dfab67546b1d9ad3c0339323c8f0114f 100644
--- a/script/update_top_ranking_issues/main.py
+++ b/script/update_top_ranking_issues/main.py
@@ -1,29 +1,24 @@
import os
-from datetime import datetime, timedelta
-from typing import Optional
+from datetime import date, datetime, timedelta
+from typing import Any, Optional
+import requests
import typer
-from github import Github
-from github.Issue import Issue
-from github.Repository import Repository
from pytz import timezone
from typer import Typer
app: Typer = typer.Typer()
-DATETIME_FORMAT: str = "%m/%d/%Y %I:%M %p"
-ISSUES_PER_LABEL: int = 50
+AMERICA_NEW_YORK_TIMEZONE = "America/New_York"
+DATETIME_FORMAT: str = "%B %d, %Y %I:%M %p"
+ISSUES_PER_SECTION: int = 50
+ISSUES_TO_FETCH: int = 100
+REPO_OWNER = "zed-industries"
+REPO_NAME = "zed"
+GITHUB_API_BASE_URL = "https://api.github.com"
-class IssueData:
- def __init__(self, issue: Issue) -> None:
- self.title = issue.title
- self.url: str = issue.html_url
- self.like_count: int = issue._rawData["reactions"]["+1"] # type: ignore [attr-defined]
- self.creation_datetime: str = issue.created_at.strftime(DATETIME_FORMAT)
- # TODO: Change script to support storing labels here, rather than directly in the script
- self.labels: set[str] = {label["name"] for label in issue._rawData["labels"]} # type: ignore [attr-defined]
- self._issue = issue
+EXCLUDE_LABEL = "ignore top-ranking issues"
@app.command()
@@ -32,181 +27,135 @@ def main(
issue_reference_number: Optional[int] = None,
query_day_interval: Optional[int] = None,
) -> None:
- start_time: datetime = datetime.now()
-
- start_date: datetime | None = None
+ script_start_time: datetime = datetime.now()
+ start_date: date | None = None
if query_day_interval:
- tz = timezone("america/new_york")
- current_time = datetime.now(tz).replace(
- hour=0, minute=0, second=0, microsecond=0
- )
- start_date = current_time - timedelta(days=query_day_interval)
+ tz = timezone(AMERICA_NEW_YORK_TIMEZONE)
+ today = datetime.now(tz).date()
+ start_date = today - timedelta(days=query_day_interval)
- # GitHub Workflow will pass in the token as an environment variable,
+ # GitHub Workflow will pass in the token as an argument,
# but we can place it in our env when running the script locally, for convenience
- github_token = github_token or os.getenv("GITHUB_ACCESS_TOKEN")
-
- with Github(github_token, per_page=100) as github:
- remaining_requests_before: int = github.rate_limiting[0]
- print(f"Remaining requests before: {remaining_requests_before}")
-
- repo_name: str = "zed-industries/zed"
- repository: Repository = github.get_repo(repo_name)
-
- label_to_issue_data: dict[str, list[IssueData]] = get_issue_maps(
- github, repository, start_date
+ token = github_token or os.getenv("GITHUB_ACCESS_TOKEN")
+ if not token:
+ raise typer.BadParameter(
+ "GitHub token is required. Pass --github-token or set GITHUB_ACCESS_TOKEN env var."
)
- issue_text: str = get_issue_text(label_to_issue_data)
-
- if issue_reference_number:
- top_ranking_issues_issue: Issue = repository.get_issue(issue_reference_number)
- top_ranking_issues_issue.edit(body=issue_text)
- else:
- print(issue_text)
-
- remaining_requests_after: int = github.rate_limiting[0]
- print(f"Remaining requests after: {remaining_requests_after}")
- print(f"Requests used: {remaining_requests_before - remaining_requests_after}")
-
- run_duration: timedelta = datetime.now() - start_time
- print(run_duration)
-
-
-def get_issue_maps(
- github: Github,
- repository: Repository,
- start_date: datetime | None = None,
-) -> dict[str, list[IssueData]]:
- label_to_issue_data: dict[str, list[IssueData]] = get_label_to_issue_data(
- github,
- repository,
- start_date,
- )
-
- # Create a new dictionary with labels ordered by the summation the of likes on the associated issues
- labels = list(label_to_issue_data.keys())
-
- labels.sort(
- key=lambda label: sum(
- issue_data.like_count for issue_data in label_to_issue_data[label]
- ),
- reverse=True,
- )
+ headers = {
+ "Authorization": f"token {token}",
+ "Accept": "application/vnd.github+json",
+ }
- label_to_issue_data = {label: label_to_issue_data[label] for label in labels}
+ section_to_issues = get_section_to_issues(headers, start_date)
+ issue_text: str = create_issue_text(section_to_issues)
- return label_to_issue_data
+ if issue_reference_number:
+ update_reference_issue(headers, issue_reference_number, issue_text)
+ else:
+ print(issue_text)
+ run_duration: timedelta = datetime.now() - script_start_time
+ print(f"Ran for {run_duration}")
-def get_label_to_issue_data(
- github: Github,
- repository: Repository,
- start_date: datetime | None = None,
-) -> dict[str, list[IssueData]]:
- common_queries = [
- f"repo:{repository.full_name}",
- "is:open",
- "is:issue",
- '-label:"ignore top-ranking issues"',
- "sort:reactions-+1-desc",
- ]
- date_query: str | None = (
- f"created:>={start_date.strftime('%Y-%m-%d')}" if start_date else None
- )
+def get_section_to_issues(
+ headers: dict[str, str], start_date: date | None = None
+) -> dict[str, list[dict[str, Any]]]:
+ """Fetch top-ranked issues for each section from GitHub."""
- if date_query:
- common_queries.append(date_query)
-
- common_query = " ".join(common_queries)
-
- # Because PyGithub doesn't seem to support logical operators `AND` and `OR`
- # that GitHub issue queries can use, we use lists as values, rather than
- # using `(label:bug OR type:Bug)`. This is not as efficient, as we might
- # query the same issue multiple times. Issues that are potentially queried
- # multiple times are deduplicated in the `label_to_issues` dictionary. If
- # PyGithub ever supports logical operators, we should definitely make the
- # switch.
- section_queries: dict[str, list[str]] = {
- "bug": ["label:bug", "type:Bug"],
- "crash": ["label:crash", "type:Crash"],
- "feature": ["label:feature", "type:Feature"],
- "meta": ["type:Meta"],
- "windows": ["label:windows"],
- "unlabeled": ["no:label no:type"],
+ section_filters = {
+ "Bugs": "type:Bug",
+ "Crashes": "type:Crash",
+ "Features": "type:Feature",
+ "Tracking issues": "type:Tracking",
+ "Meta issues": "type:Meta",
+ "Windows": 'label:"platform:windows"',
}
- label_to_issue_data: dict[str, list[IssueData]] = {}
-
- for section, queries in section_queries.items():
- unique_issues = set()
-
- for query in queries:
- query: str = f"{common_query} {query}"
- issues = github.search_issues(query)
-
- for issue in issues:
- unique_issues.add(issue)
-
- if len(unique_issues) <= 0:
+ section_to_issues: dict[str, list[dict[str, Any]]] = {}
+ for section, search_qualifier in section_filters.items():
+ query_parts = [
+ f"repo:{REPO_OWNER}/{REPO_NAME}",
+ "is:issue",
+ "is:open",
+ f'-label:"{EXCLUDE_LABEL}"',
+ search_qualifier,
+ ]
+
+ if start_date:
+ query_parts.append(f"created:<={start_date.strftime('%Y-%m-%d')}")
+
+ query = " ".join(query_parts)
+ url = f"{GITHUB_API_BASE_URL}/search/issues"
+ params = {
+ "q": query,
+ "sort": "reactions-+1",
+ "order": "desc",
+ "per_page": ISSUES_TO_FETCH, # this will work as long as it's ≤ 100
+ }
+
+ # we are only fetching one page on purpose
+ response = requests.get(url, headers=headers, params=params)
+ response.raise_for_status()
+ items = response.json()["items"]
+
+ issues: list[dict[str, Any]] = []
+ for item in items:
+ reactions = item["reactions"]
+ score = reactions["+1"] - reactions["-1"]
+ if score > 0:
+ issues.append({
+ "url": item["html_url"],
+ "score": score,
+ "created_at": item["created_at"],
+ })
+
+ if not issues:
continue
- issue_data: list[IssueData] = [IssueData(issue) for issue in unique_issues]
- issue_data.sort(
- key=lambda issue_data: (
- -issue_data.like_count,
- issue_data.creation_datetime,
- )
- )
-
- label_to_issue_data[section] = issue_data[0:ISSUES_PER_LABEL]
-
- return label_to_issue_data
-
+ issues.sort(key=lambda x: (-x["score"], x["created_at"]))
+ section_to_issues[section] = issues[:ISSUES_PER_SECTION]
-def get_issue_text(
- label_to_issue_data: dict[str, list[IssueData]],
-) -> str:
- tz = timezone("america/new_york")
- current_datetime: str = datetime.now(tz).strftime(f"{DATETIME_FORMAT} (%Z)")
-
- highest_ranking_issues_lines: list[str] = get_highest_ranking_issues_lines(
- label_to_issue_data
+ # Sort sections by total score (highest total first)
+ section_to_issues = dict(
+ sorted(
+ section_to_issues.items(),
+ key=lambda item: sum(issue["score"] for issue in item[1]),
+ reverse=True,
+ )
)
+ return section_to_issues
- issue_text_lines: list[str] = [
- f"*Updated on {current_datetime}*",
- *highest_ranking_issues_lines,
- "\n---\n",
- "*For details on how this issue is generated, [see the script](https://github.com/zed-industries/zed/blob/main/script/update_top_ranking_issues/main.py)*",
- ]
- return "\n".join(issue_text_lines)
+def update_reference_issue(
+ headers: dict[str, str], issue_number: int, body: str
+) -> None:
+ url = f"{GITHUB_API_BASE_URL}/repos/{REPO_OWNER}/{REPO_NAME}/issues/{issue_number}"
+ response = requests.patch(url, headers=headers, json={"body": body})
+ response.raise_for_status()
-def get_highest_ranking_issues_lines(
- label_to_issue_data: dict[str, list[IssueData]],
-) -> list[str]:
- highest_ranking_issues_lines: list[str] = []
+def create_issue_text(section_to_issues: dict[str, list[dict[str, Any]]]) -> str:
+ tz = timezone(AMERICA_NEW_YORK_TIMEZONE)
+ current_datetime: str = datetime.now(tz).strftime(f"{DATETIME_FORMAT} (%Z)")
- if label_to_issue_data:
- for label, issue_data in label_to_issue_data.items():
- highest_ranking_issues_lines.append(f"\n## {label}\n")
+ lines: list[str] = [f"*Updated on {current_datetime}*"]
- for i, issue_data in enumerate(issue_data):
- markdown_bullet_point: str = (
- f"{issue_data.url} ({issue_data.like_count} :thumbsup:)"
- )
+ for section, issues in section_to_issues.items():
+ lines.append(f"\n## {section}\n")
+ for i, issue in enumerate(issues):
+ lines.append(f"{i + 1}. {issue['url']} ({issue['score']} :thumbsup:)")
- markdown_bullet_point = f"{i + 1}. {markdown_bullet_point}"
- highest_ranking_issues_lines.append(markdown_bullet_point)
+ lines.append("\n---\n")
+ lines.append(
+ "*For details on how this issue is generated, "
+ "[see the script](https://github.com/zed-industries/zed/blob/main/script/update_top_ranking_issues/main.py)*"
+ )
- return highest_ranking_issues_lines
+ return "\n".join(lines)
if __name__ == "__main__":
app()
-
-# TODO: Sort label output into core and non core sections
diff --git a/script/update_top_ranking_issues/pyproject.toml b/script/update_top_ranking_issues/pyproject.toml
index ebd283850a1875835c74f1020904f99d96cc694d..aa3f8cc7ff55952d146dbf34c8db424de96e8e04 100644
--- a/script/update_top_ranking_issues/pyproject.toml
+++ b/script/update_top_ranking_issues/pyproject.toml
@@ -5,9 +5,10 @@ readme = "README.md"
requires-python = ">=3.13"
dependencies = [
"mypy>=1.15.0",
- "pygithub>=2.6.1",
"pytz>=2025.1",
+ "requests>=2.32.0",
"ruff>=0.9.7",
"typer>=0.15.1",
"types-pytz>=2025.1.0.20250204",
+ "types-requests>=2.32.0",
]
diff --git a/script/update_top_ranking_issues/uv.lock b/script/update_top_ranking_issues/uv.lock
index 062890b179f443130a7dd2211d55c4d94d7ad998..174f4e677fb2dcd73a31dfd2897d0b9d55a4c88e 100644
--- a/script/update_top_ranking_issues/uv.lock
+++ b/script/update_top_ranking_issues/uv.lock
@@ -1,60 +1,38 @@
version = 1
-revision = 1
+revision = 3
requires-python = ">=3.13"
[[package]]
name = "certifi"
version = "2024.8.30"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/b0/ee/9b19140fe824b367c04c5e1b369942dd754c4c5462d5674002f75c4dedc1/certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9", size = 168507 }
+sdist = { url = "https://files.pythonhosted.org/packages/b0/ee/9b19140fe824b367c04c5e1b369942dd754c4c5462d5674002f75c4dedc1/certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9", size = 168507, upload-time = "2024-08-30T01:55:04.365Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/12/90/3c9ff0512038035f59d279fddeb79f5f1eccd8859f06d6163c58798b9487/certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", size = 167321 },
-]
-
-[[package]]
-name = "cffi"
-version = "1.17.1"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "pycparser" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621 }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989 },
- { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802 },
- { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792 },
- { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893 },
- { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810 },
- { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200 },
- { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447 },
- { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358 },
- { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469 },
- { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475 },
- { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009 },
+ { url = "https://files.pythonhosted.org/packages/12/90/3c9ff0512038035f59d279fddeb79f5f1eccd8859f06d6163c58798b9487/certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", size = 167321, upload-time = "2024-08-30T01:55:02.591Z" },
]
[[package]]
name = "charset-normalizer"
version = "3.4.0"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/f2/4f/e1808dc01273379acc506d18f1504eb2d299bd4131743b9fc54d7be4df1e/charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e", size = 106620 }
+sdist = { url = "https://files.pythonhosted.org/packages/f2/4f/e1808dc01273379acc506d18f1504eb2d299bd4131743b9fc54d7be4df1e/charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e", size = 106620, upload-time = "2024-10-09T07:40:20.413Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/f3/89/68a4c86f1a0002810a27f12e9a7b22feb198c59b2f05231349fbce5c06f4/charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114", size = 194617 },
- { url = "https://files.pythonhosted.org/packages/4f/cd/8947fe425e2ab0aa57aceb7807af13a0e4162cd21eee42ef5b053447edf5/charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed", size = 125310 },
- { url = "https://files.pythonhosted.org/packages/5b/f0/b5263e8668a4ee9becc2b451ed909e9c27058337fda5b8c49588183c267a/charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250", size = 119126 },
- { url = "https://files.pythonhosted.org/packages/ff/6e/e445afe4f7fda27a533f3234b627b3e515a1b9429bc981c9a5e2aa5d97b6/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920", size = 139342 },
- { url = "https://files.pythonhosted.org/packages/a1/b2/4af9993b532d93270538ad4926c8e37dc29f2111c36f9c629840c57cd9b3/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64", size = 149383 },
- { url = "https://files.pythonhosted.org/packages/fb/6f/4e78c3b97686b871db9be6f31d64e9264e889f8c9d7ab33c771f847f79b7/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23", size = 142214 },
- { url = "https://files.pythonhosted.org/packages/2b/c9/1c8fe3ce05d30c87eff498592c89015b19fade13df42850aafae09e94f35/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc", size = 144104 },
- { url = "https://files.pythonhosted.org/packages/ee/68/efad5dcb306bf37db7db338338e7bb8ebd8cf38ee5bbd5ceaaaa46f257e6/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d", size = 146255 },
- { url = "https://files.pythonhosted.org/packages/0c/75/1ed813c3ffd200b1f3e71121c95da3f79e6d2a96120163443b3ad1057505/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88", size = 140251 },
- { url = "https://files.pythonhosted.org/packages/7d/0d/6f32255c1979653b448d3c709583557a4d24ff97ac4f3a5be156b2e6a210/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90", size = 148474 },
- { url = "https://files.pythonhosted.org/packages/ac/a0/c1b5298de4670d997101fef95b97ac440e8c8d8b4efa5a4d1ef44af82f0d/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b", size = 151849 },
- { url = "https://files.pythonhosted.org/packages/04/4f/b3961ba0c664989ba63e30595a3ed0875d6790ff26671e2aae2fdc28a399/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d", size = 149781 },
- { url = "https://files.pythonhosted.org/packages/d8/90/6af4cd042066a4adad58ae25648a12c09c879efa4849c705719ba1b23d8c/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482", size = 144970 },
- { url = "https://files.pythonhosted.org/packages/cc/67/e5e7e0cbfefc4ca79025238b43cdf8a2037854195b37d6417f3d0895c4c2/charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67", size = 94973 },
- { url = "https://files.pythonhosted.org/packages/65/97/fc9bbc54ee13d33dc54a7fcf17b26368b18505500fc01e228c27b5222d80/charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b", size = 102308 },
- { url = "https://files.pythonhosted.org/packages/bf/9b/08c0432272d77b04803958a4598a51e2a4b51c06640af8b8f0f908c18bf2/charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079", size = 49446 },
+ { url = "https://files.pythonhosted.org/packages/f3/89/68a4c86f1a0002810a27f12e9a7b22feb198c59b2f05231349fbce5c06f4/charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114", size = 194617, upload-time = "2024-10-09T07:39:07.317Z" },
+ { url = "https://files.pythonhosted.org/packages/4f/cd/8947fe425e2ab0aa57aceb7807af13a0e4162cd21eee42ef5b053447edf5/charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed", size = 125310, upload-time = "2024-10-09T07:39:08.353Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/f0/b5263e8668a4ee9becc2b451ed909e9c27058337fda5b8c49588183c267a/charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250", size = 119126, upload-time = "2024-10-09T07:39:09.327Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/6e/e445afe4f7fda27a533f3234b627b3e515a1b9429bc981c9a5e2aa5d97b6/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920", size = 139342, upload-time = "2024-10-09T07:39:10.322Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/b2/4af9993b532d93270538ad4926c8e37dc29f2111c36f9c629840c57cd9b3/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64", size = 149383, upload-time = "2024-10-09T07:39:12.042Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/6f/4e78c3b97686b871db9be6f31d64e9264e889f8c9d7ab33c771f847f79b7/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23", size = 142214, upload-time = "2024-10-09T07:39:13.059Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/c9/1c8fe3ce05d30c87eff498592c89015b19fade13df42850aafae09e94f35/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc", size = 144104, upload-time = "2024-10-09T07:39:14.815Z" },
+ { url = "https://files.pythonhosted.org/packages/ee/68/efad5dcb306bf37db7db338338e7bb8ebd8cf38ee5bbd5ceaaaa46f257e6/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d", size = 146255, upload-time = "2024-10-09T07:39:15.868Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/75/1ed813c3ffd200b1f3e71121c95da3f79e6d2a96120163443b3ad1057505/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88", size = 140251, upload-time = "2024-10-09T07:39:16.995Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/0d/6f32255c1979653b448d3c709583557a4d24ff97ac4f3a5be156b2e6a210/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90", size = 148474, upload-time = "2024-10-09T07:39:18.021Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/a0/c1b5298de4670d997101fef95b97ac440e8c8d8b4efa5a4d1ef44af82f0d/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b", size = 151849, upload-time = "2024-10-09T07:39:19.243Z" },
+ { url = "https://files.pythonhosted.org/packages/04/4f/b3961ba0c664989ba63e30595a3ed0875d6790ff26671e2aae2fdc28a399/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d", size = 149781, upload-time = "2024-10-09T07:39:20.397Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/90/6af4cd042066a4adad58ae25648a12c09c879efa4849c705719ba1b23d8c/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482", size = 144970, upload-time = "2024-10-09T07:39:21.452Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/67/e5e7e0cbfefc4ca79025238b43cdf8a2037854195b37d6417f3d0895c4c2/charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67", size = 94973, upload-time = "2024-10-09T07:39:22.509Z" },
+ { url = "https://files.pythonhosted.org/packages/65/97/fc9bbc54ee13d33dc54a7fcf17b26368b18505500fc01e228c27b5222d80/charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b", size = 102308, upload-time = "2024-10-09T07:39:23.524Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/9b/08c0432272d77b04803958a4598a51e2a4b51c06640af8b8f0f908c18bf2/charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079", size = 49446, upload-time = "2024-10-09T07:40:19.383Z" },
]
[[package]]
@@ -64,68 +42,27 @@ source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 }
+sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121, upload-time = "2023-08-17T17:29:11.868Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941 },
+ { url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941, upload-time = "2023-08-17T17:29:10.08Z" },
]
[[package]]
name = "colorama"
version = "0.4.6"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
-]
-
-[[package]]
-name = "cryptography"
-version = "43.0.1"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/de/ba/0664727028b37e249e73879348cc46d45c5c1a2a2e81e8166462953c5755/cryptography-43.0.1.tar.gz", hash = "sha256:203e92a75716d8cfb491dc47c79e17d0d9207ccffcbcb35f598fbe463ae3444d", size = 686927 }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/58/28/b92c98a04ba762f8cdeb54eba5c4c84e63cac037a7c5e70117d337b15ad6/cryptography-43.0.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:8385d98f6a3bf8bb2d65a73e17ed87a3ba84f6991c155691c51112075f9ffc5d", size = 6223222 },
- { url = "https://files.pythonhosted.org/packages/33/13/1193774705783ba364121aa2a60132fa31a668b8ababd5edfa1662354ccd/cryptography-43.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27e613d7077ac613e399270253259d9d53872aaf657471473ebfc9a52935c062", size = 3794751 },
- { url = "https://files.pythonhosted.org/packages/5e/4b/39bb3c4c8cfb3e94e736b8d8859ce5c81536e91a1033b1d26770c4249000/cryptography-43.0.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68aaecc4178e90719e95298515979814bda0cbada1256a4485414860bd7ab962", size = 3981827 },
- { url = "https://files.pythonhosted.org/packages/ce/dc/1471d4d56608e1013237af334b8a4c35d53895694fbb73882d1c4fd3f55e/cryptography-43.0.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:de41fd81a41e53267cb020bb3a7212861da53a7d39f863585d13ea11049cf277", size = 3780034 },
- { url = "https://files.pythonhosted.org/packages/ad/43/7a9920135b0d5437cc2f8f529fa757431eb6a7736ddfadfdee1cc5890800/cryptography-43.0.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f98bf604c82c416bc829e490c700ca1553eafdf2912a91e23a79d97d9801372a", size = 3993407 },
- { url = "https://files.pythonhosted.org/packages/cc/42/9ab8467af6c0b76f3d9b8f01d1cf25b9c9f3f2151f4acfab888d21c55a72/cryptography-43.0.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:61ec41068b7b74268fa86e3e9e12b9f0c21fcf65434571dbb13d954bceb08042", size = 3886457 },
- { url = "https://files.pythonhosted.org/packages/a4/65/430509e31700286ec02868a2457d2111d03ccefc20349d24e58d171ae0a7/cryptography-43.0.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:014f58110f53237ace6a408b5beb6c427b64e084eb451ef25a28308270086494", size = 4081499 },
- { url = "https://files.pythonhosted.org/packages/bb/18/a04b6467e6e09df8c73b91dcee8878f4a438a43a3603dc3cd6f8003b92d8/cryptography-43.0.1-cp37-abi3-win32.whl", hash = "sha256:2bd51274dcd59f09dd952afb696bf9c61a7a49dfc764c04dd33ef7a6b502a1e2", size = 2616504 },
- { url = "https://files.pythonhosted.org/packages/cc/73/0eacbdc437202edcbdc07f3576ed8fb8b0ab79d27bf2c5d822d758a72faa/cryptography-43.0.1-cp37-abi3-win_amd64.whl", hash = "sha256:666ae11966643886c2987b3b721899d250855718d6d9ce41b521252a17985f4d", size = 3067456 },
- { url = "https://files.pythonhosted.org/packages/8a/b6/bc54b371f02cffd35ff8dc6baba88304d7cf8e83632566b4b42e00383e03/cryptography-43.0.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:ac119bb76b9faa00f48128b7f5679e1d8d437365c5d26f1c2c3f0da4ce1b553d", size = 6225263 },
- { url = "https://files.pythonhosted.org/packages/00/0e/8217e348a1fa417ec4c78cd3cdf24154f5e76fd7597343a35bd403650dfd/cryptography-43.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bbcce1a551e262dfbafb6e6252f1ae36a248e615ca44ba302df077a846a8806", size = 3794368 },
- { url = "https://files.pythonhosted.org/packages/3d/ed/38b6be7254d8f7251fde8054af597ee8afa14f911da67a9410a45f602fc3/cryptography-43.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58d4e9129985185a06d849aa6df265bdd5a74ca6e1b736a77959b498e0505b85", size = 3981750 },
- { url = "https://files.pythonhosted.org/packages/64/f3/b7946c3887cf7436f002f4cbb1e6aec77b8d299b86be48eeadfefb937c4b/cryptography-43.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d03a475165f3134f773d1388aeb19c2d25ba88b6a9733c5c590b9ff7bbfa2e0c", size = 3778925 },
- { url = "https://files.pythonhosted.org/packages/ac/7e/ebda4dd4ae098a0990753efbb4b50954f1d03003846b943ea85070782da7/cryptography-43.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:511f4273808ab590912a93ddb4e3914dfd8a388fed883361b02dea3791f292e1", size = 3993152 },
- { url = "https://files.pythonhosted.org/packages/43/f6/feebbd78a3e341e3913846a3bb2c29d0b09b1b3af1573c6baabc2533e147/cryptography-43.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:80eda8b3e173f0f247f711eef62be51b599b5d425c429b5d4ca6a05e9e856baa", size = 3886392 },
- { url = "https://files.pythonhosted.org/packages/bd/4c/ab0b9407d5247576290b4fd8abd06b7f51bd414f04eef0f2800675512d61/cryptography-43.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38926c50cff6f533f8a2dae3d7f19541432610d114a70808f0926d5aaa7121e4", size = 4082606 },
- { url = "https://files.pythonhosted.org/packages/05/36/e532a671998d6fcfdb9122da16434347a58a6bae9465e527e450e0bc60a5/cryptography-43.0.1-cp39-abi3-win32.whl", hash = "sha256:a575913fb06e05e6b4b814d7f7468c2c660e8bb16d8d5a1faf9b33ccc569dd47", size = 2617948 },
- { url = "https://files.pythonhosted.org/packages/b3/c6/c09cee6968add5ff868525c3815e5dccc0e3c6e89eec58dc9135d3c40e88/cryptography-43.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:d75601ad10b059ec832e78823b348bfa1a59f6b8d545db3a24fd44362a1564cb", size = 3070445 },
-]
-
-[[package]]
-name = "deprecated"
-version = "1.2.14"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "wrapt" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/92/14/1e41f504a246fc224d2ac264c227975427a85caf37c3979979edb9b1b232/Deprecated-1.2.14.tar.gz", hash = "sha256:e5323eb936458dccc2582dc6f9c322c852a775a27065ff2b0c4970b9d53d01b3", size = 2974416 }
+sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/20/8d/778b7d51b981a96554f29136cd59ca7880bf58094338085bcf2a979a0e6a/Deprecated-1.2.14-py2.py3-none-any.whl", hash = "sha256:6fac8b097794a90302bdbb17b9b815e732d3c4720583ff1b198499d78470466c", size = 9561 },
+ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
]
[[package]]
name = "idna"
version = "3.10"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 }
+sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 },
+ { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" },
]
[[package]]
@@ -135,18 +72,18 @@ source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "mdurl" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 }
+sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 },
+ { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" },
]
[[package]]
name = "mdurl"
version = "0.1.2"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 }
+sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 },
+ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" },
]
[[package]]
@@ -157,102 +94,42 @@ dependencies = [
{ name = "mypy-extensions" },
{ name = "typing-extensions" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/ce/43/d5e49a86afa64bd3839ea0d5b9c7103487007d728e1293f52525d6d5486a/mypy-1.15.0.tar.gz", hash = "sha256:404534629d51d3efea5c800ee7c42b72a6554d6c400e6a79eafe15d11341fd43", size = 3239717 }
+sdist = { url = "https://files.pythonhosted.org/packages/ce/43/d5e49a86afa64bd3839ea0d5b9c7103487007d728e1293f52525d6d5486a/mypy-1.15.0.tar.gz", hash = "sha256:404534629d51d3efea5c800ee7c42b72a6554d6c400e6a79eafe15d11341fd43", size = 3239717, upload-time = "2025-02-05T03:50:34.655Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/6a/9b/fd2e05d6ffff24d912f150b87db9e364fa8282045c875654ce7e32fffa66/mypy-1.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93faf3fdb04768d44bf28693293f3904bbb555d076b781ad2530214ee53e3445", size = 10788592 },
- { url = "https://files.pythonhosted.org/packages/74/37/b246d711c28a03ead1fd906bbc7106659aed7c089d55fe40dd58db812628/mypy-1.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:811aeccadfb730024c5d3e326b2fbe9249bb7413553f15499a4050f7c30e801d", size = 9753611 },
- { url = "https://files.pythonhosted.org/packages/a6/ac/395808a92e10cfdac8003c3de9a2ab6dc7cde6c0d2a4df3df1b815ffd067/mypy-1.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98b7b9b9aedb65fe628c62a6dc57f6d5088ef2dfca37903a7d9ee374d03acca5", size = 11438443 },
- { url = "https://files.pythonhosted.org/packages/d2/8b/801aa06445d2de3895f59e476f38f3f8d610ef5d6908245f07d002676cbf/mypy-1.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c43a7682e24b4f576d93072216bf56eeff70d9140241f9edec0c104d0c515036", size = 12402541 },
- { url = "https://files.pythonhosted.org/packages/c7/67/5a4268782eb77344cc613a4cf23540928e41f018a9a1ec4c6882baf20ab8/mypy-1.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:baefc32840a9f00babd83251560e0ae1573e2f9d1b067719479bfb0e987c6357", size = 12494348 },
- { url = "https://files.pythonhosted.org/packages/83/3e/57bb447f7bbbfaabf1712d96f9df142624a386d98fb026a761532526057e/mypy-1.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:b9378e2c00146c44793c98b8d5a61039a048e31f429fb0eb546d93f4b000bedf", size = 9373648 },
- { url = "https://files.pythonhosted.org/packages/09/4e/a7d65c7322c510de2c409ff3828b03354a7c43f5a8ed458a7a131b41c7b9/mypy-1.15.0-py3-none-any.whl", hash = "sha256:5469affef548bd1895d86d3bf10ce2b44e33d86923c29e4d675b3e323437ea3e", size = 2221777 },
+ { url = "https://files.pythonhosted.org/packages/6a/9b/fd2e05d6ffff24d912f150b87db9e364fa8282045c875654ce7e32fffa66/mypy-1.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93faf3fdb04768d44bf28693293f3904bbb555d076b781ad2530214ee53e3445", size = 10788592, upload-time = "2025-02-05T03:48:55.789Z" },
+ { url = "https://files.pythonhosted.org/packages/74/37/b246d711c28a03ead1fd906bbc7106659aed7c089d55fe40dd58db812628/mypy-1.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:811aeccadfb730024c5d3e326b2fbe9249bb7413553f15499a4050f7c30e801d", size = 9753611, upload-time = "2025-02-05T03:48:44.581Z" },
+ { url = "https://files.pythonhosted.org/packages/a6/ac/395808a92e10cfdac8003c3de9a2ab6dc7cde6c0d2a4df3df1b815ffd067/mypy-1.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98b7b9b9aedb65fe628c62a6dc57f6d5088ef2dfca37903a7d9ee374d03acca5", size = 11438443, upload-time = "2025-02-05T03:49:25.514Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/8b/801aa06445d2de3895f59e476f38f3f8d610ef5d6908245f07d002676cbf/mypy-1.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c43a7682e24b4f576d93072216bf56eeff70d9140241f9edec0c104d0c515036", size = 12402541, upload-time = "2025-02-05T03:49:57.623Z" },
+ { url = "https://files.pythonhosted.org/packages/c7/67/5a4268782eb77344cc613a4cf23540928e41f018a9a1ec4c6882baf20ab8/mypy-1.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:baefc32840a9f00babd83251560e0ae1573e2f9d1b067719479bfb0e987c6357", size = 12494348, upload-time = "2025-02-05T03:48:52.361Z" },
+ { url = "https://files.pythonhosted.org/packages/83/3e/57bb447f7bbbfaabf1712d96f9df142624a386d98fb026a761532526057e/mypy-1.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:b9378e2c00146c44793c98b8d5a61039a048e31f429fb0eb546d93f4b000bedf", size = 9373648, upload-time = "2025-02-05T03:49:11.395Z" },
+ { url = "https://files.pythonhosted.org/packages/09/4e/a7d65c7322c510de2c409ff3828b03354a7c43f5a8ed458a7a131b41c7b9/mypy-1.15.0-py3-none-any.whl", hash = "sha256:5469affef548bd1895d86d3bf10ce2b44e33d86923c29e4d675b3e323437ea3e", size = 2221777, upload-time = "2025-02-05T03:50:08.348Z" },
]
[[package]]
name = "mypy-extensions"
version = "1.0.0"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433 }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 },
-]
-
-[[package]]
-name = "pycparser"
-version = "2.22"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736 }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552 },
-]
-
-[[package]]
-name = "pygithub"
-version = "2.6.1"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "deprecated" },
- { name = "pyjwt", extra = ["crypto"] },
- { name = "pynacl" },
- { name = "requests" },
- { name = "typing-extensions" },
- { name = "urllib3" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/c0/88/e08ab18dc74b2916f48703ed1a797d57cb64eca0e23b0a9254e13cfe3911/pygithub-2.6.1.tar.gz", hash = "sha256:b5c035392991cca63959e9453286b41b54d83bf2de2daa7d7ff7e4312cebf3bf", size = 3659473 }
+sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433, upload-time = "2023-02-04T12:11:27.157Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/ac/fc/a444cd19ccc8c4946a512f3827ed0b3565c88488719d800d54a75d541c0b/PyGithub-2.6.1-py3-none-any.whl", hash = "sha256:6f2fa6d076ccae475f9fc392cc6cdbd54db985d4f69b8833a28397de75ed6ca3", size = 410451 },
+ { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695, upload-time = "2023-02-04T12:11:25.002Z" },
]
[[package]]
name = "pygments"
version = "2.18.0"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/8e/62/8336eff65bcbc8e4cb5d05b55faf041285951b6e80f33e2bff2024788f31/pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199", size = 4891905 }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/f7/3f/01c8b82017c199075f8f788d0d906b9ffbbc5a47dc9918a945e13d5a2bda/pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a", size = 1205513 },
-]
-
-[[package]]
-name = "pyjwt"
-version = "2.9.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/fb/68/ce067f09fca4abeca8771fe667d89cc347d1e99da3e093112ac329c6020e/pyjwt-2.9.0.tar.gz", hash = "sha256:7e1e5b56cc735432a7369cbfa0efe50fa113ebecdc04ae6922deba8b84582d0c", size = 78825 }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/79/84/0fdf9b18ba31d69877bd39c9cd6052b47f3761e9910c15de788e519f079f/PyJWT-2.9.0-py3-none-any.whl", hash = "sha256:3b02fb0f44517787776cf48f2ae25d8e14f300e6d7545a4315cee571a415e850", size = 22344 },
-]
-
-[package.optional-dependencies]
-crypto = [
- { name = "cryptography" },
-]
-
-[[package]]
-name = "pynacl"
-version = "1.5.0"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "cffi" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/a7/22/27582568be639dfe22ddb3902225f91f2f17ceff88ce80e4db396c8986da/PyNaCl-1.5.0.tar.gz", hash = "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba", size = 3392854 }
+sdist = { url = "https://files.pythonhosted.org/packages/8e/62/8336eff65bcbc8e4cb5d05b55faf041285951b6e80f33e2bff2024788f31/pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199", size = 4891905, upload-time = "2024-05-04T13:42:02.013Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/ce/75/0b8ede18506041c0bf23ac4d8e2971b4161cd6ce630b177d0a08eb0d8857/PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1", size = 349920 },
- { url = "https://files.pythonhosted.org/packages/59/bb/fddf10acd09637327a97ef89d2a9d621328850a72f1fdc8c08bdf72e385f/PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92", size = 601722 },
- { url = "https://files.pythonhosted.org/packages/5d/70/87a065c37cca41a75f2ce113a5a2c2aa7533be648b184ade58971b5f7ccc/PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394", size = 680087 },
- { url = "https://files.pythonhosted.org/packages/ee/87/f1bb6a595f14a327e8285b9eb54d41fef76c585a0edef0a45f6fc95de125/PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d", size = 856678 },
- { url = "https://files.pythonhosted.org/packages/66/28/ca86676b69bf9f90e710571b67450508484388bfce09acf8a46f0b8c785f/PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858", size = 1133660 },
- { url = "https://files.pythonhosted.org/packages/3d/85/c262db650e86812585e2bc59e497a8f59948a005325a11bbbc9ecd3fe26b/PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b", size = 663824 },
- { url = "https://files.pythonhosted.org/packages/fd/1a/cc308a884bd299b651f1633acb978e8596c71c33ca85e9dc9fa33a5399b9/PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff", size = 1117912 },
- { url = "https://files.pythonhosted.org/packages/25/2d/b7df6ddb0c2a33afdb358f8af6ea3b8c4d1196ca45497dd37a56f0c122be/PyNaCl-1.5.0-cp36-abi3-win32.whl", hash = "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543", size = 204624 },
- { url = "https://files.pythonhosted.org/packages/5e/22/d3db169895faaf3e2eda892f005f433a62db2decbcfbc2f61e6517adfa87/PyNaCl-1.5.0-cp36-abi3-win_amd64.whl", hash = "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93", size = 212141 },
+ { url = "https://files.pythonhosted.org/packages/f7/3f/01c8b82017c199075f8f788d0d906b9ffbbc5a47dc9918a945e13d5a2bda/pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a", size = 1205513, upload-time = "2024-05-04T13:41:57.345Z" },
]
[[package]]
name = "pytz"
version = "2025.1"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/5f/57/df1c9157c8d5a05117e455d66fd7cf6dbc46974f832b1058ed4856785d8a/pytz-2025.1.tar.gz", hash = "sha256:c2db42be2a2518b28e65f9207c4d05e6ff547d1efa4086469ef855e4ab70178e", size = 319617 }
+sdist = { url = "https://files.pythonhosted.org/packages/5f/57/df1c9157c8d5a05117e455d66fd7cf6dbc46974f832b1058ed4856785d8a/pytz-2025.1.tar.gz", hash = "sha256:c2db42be2a2518b28e65f9207c4d05e6ff547d1efa4086469ef855e4ab70178e", size = 319617, upload-time = "2025-01-31T01:54:48.615Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/eb/38/ac33370d784287baa1c3d538978b5e2ea064d4c1b93ffbd12826c190dd10/pytz-2025.1-py2.py3-none-any.whl", hash = "sha256:89dd22dca55b46eac6eda23b2d72721bf1bdfef212645d81513ef5d03038de57", size = 507930 },
+ { url = "https://files.pythonhosted.org/packages/eb/38/ac33370d784287baa1c3d538978b5e2ea064d4c1b93ffbd12826c190dd10/pytz-2025.1-py2.py3-none-any.whl", hash = "sha256:89dd22dca55b46eac6eda23b2d72721bf1bdfef212645d81513ef5d03038de57", size = 507930, upload-time = "2025-01-31T01:54:45.634Z" },
]
[[package]]
@@ -265,9 +142,9 @@ dependencies = [
{ name = "idna" },
{ name = "urllib3" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 }
+sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218, upload-time = "2024-05-29T15:37:49.536Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 },
+ { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928, upload-time = "2024-05-29T15:37:47.027Z" },
]
[[package]]
@@ -278,43 +155,43 @@ dependencies = [
{ name = "markdown-it-py" },
{ name = "pygments" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/aa/9e/1784d15b057b0075e5136445aaea92d23955aad2c93eaede673718a40d95/rich-13.9.2.tar.gz", hash = "sha256:51a2c62057461aaf7152b4d611168f93a9fc73068f8ded2790f29fe2b5366d0c", size = 222843 }
+sdist = { url = "https://files.pythonhosted.org/packages/aa/9e/1784d15b057b0075e5136445aaea92d23955aad2c93eaede673718a40d95/rich-13.9.2.tar.gz", hash = "sha256:51a2c62057461aaf7152b4d611168f93a9fc73068f8ded2790f29fe2b5366d0c", size = 222843, upload-time = "2024-10-04T11:50:31.453Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/67/91/5474b84e505a6ccc295b2d322d90ff6aa0746745717839ee0c5fb4fdcceb/rich-13.9.2-py3-none-any.whl", hash = "sha256:8c82a3d3f8dcfe9e734771313e606b39d8247bb6b826e196f4914b333b743cf1", size = 242117 },
+ { url = "https://files.pythonhosted.org/packages/67/91/5474b84e505a6ccc295b2d322d90ff6aa0746745717839ee0c5fb4fdcceb/rich-13.9.2-py3-none-any.whl", hash = "sha256:8c82a3d3f8dcfe9e734771313e606b39d8247bb6b826e196f4914b333b743cf1", size = 242117, upload-time = "2024-10-04T11:50:29.123Z" },
]
[[package]]
name = "ruff"
version = "0.9.7"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/39/8b/a86c300359861b186f18359adf4437ac8e4c52e42daa9eedc731ef9d5b53/ruff-0.9.7.tar.gz", hash = "sha256:643757633417907510157b206e490c3aa11cab0c087c912f60e07fbafa87a4c6", size = 3669813 }
+sdist = { url = "https://files.pythonhosted.org/packages/39/8b/a86c300359861b186f18359adf4437ac8e4c52e42daa9eedc731ef9d5b53/ruff-0.9.7.tar.gz", hash = "sha256:643757633417907510157b206e490c3aa11cab0c087c912f60e07fbafa87a4c6", size = 3669813, upload-time = "2025-02-20T13:26:52.111Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/b1/f3/3a1d22973291226df4b4e2ff70196b926b6f910c488479adb0eeb42a0d7f/ruff-0.9.7-py3-none-linux_armv6l.whl", hash = "sha256:99d50def47305fe6f233eb8dabfd60047578ca87c9dcb235c9723ab1175180f4", size = 11774588 },
- { url = "https://files.pythonhosted.org/packages/8e/c9/b881f4157b9b884f2994fd08ee92ae3663fb24e34b0372ac3af999aa7fc6/ruff-0.9.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d59105ae9c44152c3d40a9c40d6331a7acd1cdf5ef404fbe31178a77b174ea66", size = 11746848 },
- { url = "https://files.pythonhosted.org/packages/14/89/2f546c133f73886ed50a3d449e6bf4af27d92d2f960a43a93d89353f0945/ruff-0.9.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f313b5800483770bd540cddac7c90fc46f895f427b7820f18fe1822697f1fec9", size = 11177525 },
- { url = "https://files.pythonhosted.org/packages/d7/93/6b98f2c12bf28ab9def59c50c9c49508519c5b5cfecca6de871cf01237f6/ruff-0.9.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:042ae32b41343888f59c0a4148f103208bf6b21c90118d51dc93a68366f4e903", size = 11996580 },
- { url = "https://files.pythonhosted.org/packages/8e/3f/b3fcaf4f6d875e679ac2b71a72f6691a8128ea3cb7be07cbb249f477c061/ruff-0.9.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:87862589373b33cc484b10831004e5e5ec47dc10d2b41ba770e837d4f429d721", size = 11525674 },
- { url = "https://files.pythonhosted.org/packages/f0/48/33fbf18defb74d624535d5d22adcb09a64c9bbabfa755bc666189a6b2210/ruff-0.9.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a17e1e01bee0926d351a1ee9bc15c445beae888f90069a6192a07a84af544b6b", size = 12739151 },
- { url = "https://files.pythonhosted.org/packages/63/b5/7e161080c5e19fa69495cbab7c00975ef8a90f3679caa6164921d7f52f4a/ruff-0.9.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:7c1f880ac5b2cbebd58b8ebde57069a374865c73f3bf41f05fe7a179c1c8ef22", size = 13416128 },
- { url = "https://files.pythonhosted.org/packages/4e/c8/b5e7d61fb1c1b26f271ac301ff6d9de5e4d9a9a63f67d732fa8f200f0c88/ruff-0.9.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e63fc20143c291cab2841dbb8260e96bafbe1ba13fd3d60d28be2c71e312da49", size = 12870858 },
- { url = "https://files.pythonhosted.org/packages/da/cb/2a1a8e4e291a54d28259f8fc6a674cd5b8833e93852c7ef5de436d6ed729/ruff-0.9.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:91ff963baed3e9a6a4eba2a02f4ca8eaa6eba1cc0521aec0987da8d62f53cbef", size = 14786046 },
- { url = "https://files.pythonhosted.org/packages/ca/6c/c8f8a313be1943f333f376d79724260da5701426c0905762e3ddb389e3f4/ruff-0.9.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88362e3227c82f63eaebf0b2eff5b88990280fb1ecf7105523883ba8c3aaf6fb", size = 12550834 },
- { url = "https://files.pythonhosted.org/packages/9d/ad/f70cf5e8e7c52a25e166bdc84c082163c9c6f82a073f654c321b4dff9660/ruff-0.9.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0372c5a90349f00212270421fe91874b866fd3626eb3b397ede06cd385f6f7e0", size = 11961307 },
- { url = "https://files.pythonhosted.org/packages/52/d5/4f303ea94a5f4f454daf4d02671b1fbfe2a318b5fcd009f957466f936c50/ruff-0.9.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d76b8ab60e99e6424cd9d3d923274a1324aefce04f8ea537136b8398bbae0a62", size = 11612039 },
- { url = "https://files.pythonhosted.org/packages/eb/c8/bd12a23a75603c704ce86723be0648ba3d4ecc2af07eecd2e9fa112f7e19/ruff-0.9.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:0c439bdfc8983e1336577f00e09a4e7a78944fe01e4ea7fe616d00c3ec69a3d0", size = 12168177 },
- { url = "https://files.pythonhosted.org/packages/cc/57/d648d4f73400fef047d62d464d1a14591f2e6b3d4a15e93e23a53c20705d/ruff-0.9.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:115d1f15e8fdd445a7b4dc9a30abae22de3f6bcabeb503964904471691ef7606", size = 12610122 },
- { url = "https://files.pythonhosted.org/packages/49/79/acbc1edd03ac0e2a04ae2593555dbc9990b34090a9729a0c4c0cf20fb595/ruff-0.9.7-py3-none-win32.whl", hash = "sha256:e9ece95b7de5923cbf38893f066ed2872be2f2f477ba94f826c8defdd6ec6b7d", size = 9988751 },
- { url = "https://files.pythonhosted.org/packages/6d/95/67153a838c6b6ba7a2401241fd8a00cd8c627a8e4a0491b8d853dedeffe0/ruff-0.9.7-py3-none-win_amd64.whl", hash = "sha256:3770fe52b9d691a15f0b87ada29c45324b2ace8f01200fb0c14845e499eb0c2c", size = 11002987 },
- { url = "https://files.pythonhosted.org/packages/63/6a/aca01554949f3a401991dc32fe22837baeaccb8a0d868256cbb26a029778/ruff-0.9.7-py3-none-win_arm64.whl", hash = "sha256:b075a700b2533feb7a01130ff656a4ec0d5f340bb540ad98759b8401c32c2037", size = 10177763 },
+ { url = "https://files.pythonhosted.org/packages/b1/f3/3a1d22973291226df4b4e2ff70196b926b6f910c488479adb0eeb42a0d7f/ruff-0.9.7-py3-none-linux_armv6l.whl", hash = "sha256:99d50def47305fe6f233eb8dabfd60047578ca87c9dcb235c9723ab1175180f4", size = 11774588, upload-time = "2025-02-20T13:25:52.253Z" },
+ { url = "https://files.pythonhosted.org/packages/8e/c9/b881f4157b9b884f2994fd08ee92ae3663fb24e34b0372ac3af999aa7fc6/ruff-0.9.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d59105ae9c44152c3d40a9c40d6331a7acd1cdf5ef404fbe31178a77b174ea66", size = 11746848, upload-time = "2025-02-20T13:25:57.279Z" },
+ { url = "https://files.pythonhosted.org/packages/14/89/2f546c133f73886ed50a3d449e6bf4af27d92d2f960a43a93d89353f0945/ruff-0.9.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f313b5800483770bd540cddac7c90fc46f895f427b7820f18fe1822697f1fec9", size = 11177525, upload-time = "2025-02-20T13:26:00.007Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/93/6b98f2c12bf28ab9def59c50c9c49508519c5b5cfecca6de871cf01237f6/ruff-0.9.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:042ae32b41343888f59c0a4148f103208bf6b21c90118d51dc93a68366f4e903", size = 11996580, upload-time = "2025-02-20T13:26:03.274Z" },
+ { url = "https://files.pythonhosted.org/packages/8e/3f/b3fcaf4f6d875e679ac2b71a72f6691a8128ea3cb7be07cbb249f477c061/ruff-0.9.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:87862589373b33cc484b10831004e5e5ec47dc10d2b41ba770e837d4f429d721", size = 11525674, upload-time = "2025-02-20T13:26:06.073Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/48/33fbf18defb74d624535d5d22adcb09a64c9bbabfa755bc666189a6b2210/ruff-0.9.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a17e1e01bee0926d351a1ee9bc15c445beae888f90069a6192a07a84af544b6b", size = 12739151, upload-time = "2025-02-20T13:26:08.964Z" },
+ { url = "https://files.pythonhosted.org/packages/63/b5/7e161080c5e19fa69495cbab7c00975ef8a90f3679caa6164921d7f52f4a/ruff-0.9.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:7c1f880ac5b2cbebd58b8ebde57069a374865c73f3bf41f05fe7a179c1c8ef22", size = 13416128, upload-time = "2025-02-20T13:26:12.54Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/c8/b5e7d61fb1c1b26f271ac301ff6d9de5e4d9a9a63f67d732fa8f200f0c88/ruff-0.9.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e63fc20143c291cab2841dbb8260e96bafbe1ba13fd3d60d28be2c71e312da49", size = 12870858, upload-time = "2025-02-20T13:26:16.794Z" },
+ { url = "https://files.pythonhosted.org/packages/da/cb/2a1a8e4e291a54d28259f8fc6a674cd5b8833e93852c7ef5de436d6ed729/ruff-0.9.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:91ff963baed3e9a6a4eba2a02f4ca8eaa6eba1cc0521aec0987da8d62f53cbef", size = 14786046, upload-time = "2025-02-20T13:26:19.85Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/6c/c8f8a313be1943f333f376d79724260da5701426c0905762e3ddb389e3f4/ruff-0.9.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88362e3227c82f63eaebf0b2eff5b88990280fb1ecf7105523883ba8c3aaf6fb", size = 12550834, upload-time = "2025-02-20T13:26:23.082Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/ad/f70cf5e8e7c52a25e166bdc84c082163c9c6f82a073f654c321b4dff9660/ruff-0.9.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0372c5a90349f00212270421fe91874b866fd3626eb3b397ede06cd385f6f7e0", size = 11961307, upload-time = "2025-02-20T13:26:26.738Z" },
+ { url = "https://files.pythonhosted.org/packages/52/d5/4f303ea94a5f4f454daf4d02671b1fbfe2a318b5fcd009f957466f936c50/ruff-0.9.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d76b8ab60e99e6424cd9d3d923274a1324aefce04f8ea537136b8398bbae0a62", size = 11612039, upload-time = "2025-02-20T13:26:30.26Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/c8/bd12a23a75603c704ce86723be0648ba3d4ecc2af07eecd2e9fa112f7e19/ruff-0.9.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:0c439bdfc8983e1336577f00e09a4e7a78944fe01e4ea7fe616d00c3ec69a3d0", size = 12168177, upload-time = "2025-02-20T13:26:33.452Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/57/d648d4f73400fef047d62d464d1a14591f2e6b3d4a15e93e23a53c20705d/ruff-0.9.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:115d1f15e8fdd445a7b4dc9a30abae22de3f6bcabeb503964904471691ef7606", size = 12610122, upload-time = "2025-02-20T13:26:37.365Z" },
+ { url = "https://files.pythonhosted.org/packages/49/79/acbc1edd03ac0e2a04ae2593555dbc9990b34090a9729a0c4c0cf20fb595/ruff-0.9.7-py3-none-win32.whl", hash = "sha256:e9ece95b7de5923cbf38893f066ed2872be2f2f477ba94f826c8defdd6ec6b7d", size = 9988751, upload-time = "2025-02-20T13:26:40.366Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/95/67153a838c6b6ba7a2401241fd8a00cd8c627a8e4a0491b8d853dedeffe0/ruff-0.9.7-py3-none-win_amd64.whl", hash = "sha256:3770fe52b9d691a15f0b87ada29c45324b2ace8f01200fb0c14845e499eb0c2c", size = 11002987, upload-time = "2025-02-20T13:26:43.762Z" },
+ { url = "https://files.pythonhosted.org/packages/63/6a/aca01554949f3a401991dc32fe22837baeaccb8a0d868256cbb26a029778/ruff-0.9.7-py3-none-win_arm64.whl", hash = "sha256:b075a700b2533feb7a01130ff656a4ec0d5f340bb540ad98759b8401c32c2037", size = 10177763, upload-time = "2025-02-20T13:26:48.92Z" },
]
[[package]]
name = "shellingham"
version = "1.5.4"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310 }
+sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755 },
+ { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" },
]
[[package]]
@@ -327,27 +204,39 @@ dependencies = [
{ name = "shellingham" },
{ name = "typing-extensions" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/cb/ce/dca7b219718afd37a0068f4f2530a727c2b74a8b6e8e0c0080a4c0de4fcd/typer-0.15.1.tar.gz", hash = "sha256:a0588c0a7fa68a1978a069818657778f86abe6ff5ea6abf472f940a08bfe4f0a", size = 99789 }
+sdist = { url = "https://files.pythonhosted.org/packages/cb/ce/dca7b219718afd37a0068f4f2530a727c2b74a8b6e8e0c0080a4c0de4fcd/typer-0.15.1.tar.gz", hash = "sha256:a0588c0a7fa68a1978a069818657778f86abe6ff5ea6abf472f940a08bfe4f0a", size = 99789, upload-time = "2024-12-04T17:44:58.956Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/d0/cc/0a838ba5ca64dc832aa43f727bd586309846b0ffb2ce52422543e6075e8a/typer-0.15.1-py3-none-any.whl", hash = "sha256:7994fb7b8155b64d3402518560648446072864beefd44aa2dc36972a5972e847", size = 44908 },
+ { url = "https://files.pythonhosted.org/packages/d0/cc/0a838ba5ca64dc832aa43f727bd586309846b0ffb2ce52422543e6075e8a/typer-0.15.1-py3-none-any.whl", hash = "sha256:7994fb7b8155b64d3402518560648446072864beefd44aa2dc36972a5972e847", size = 44908, upload-time = "2024-12-04T17:44:57.291Z" },
]
[[package]]
name = "types-pytz"
version = "2025.1.0.20250204"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/b3/d2/2190c54d53c04491ad72a1df019c5dfa692e6ab6c2dba1be7b6c9d530e30/types_pytz-2025.1.0.20250204.tar.gz", hash = "sha256:00f750132769f1c65a4f7240bc84f13985b4da774bd17dfbe5d9cd442746bd49", size = 10352 }
+sdist = { url = "https://files.pythonhosted.org/packages/b3/d2/2190c54d53c04491ad72a1df019c5dfa692e6ab6c2dba1be7b6c9d530e30/types_pytz-2025.1.0.20250204.tar.gz", hash = "sha256:00f750132769f1c65a4f7240bc84f13985b4da774bd17dfbe5d9cd442746bd49", size = 10352, upload-time = "2025-02-04T02:39:05.553Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/be/50/65ffad73746f1d8b15992c030e0fd22965fd5ae2c0206dc28873343b3230/types_pytz-2025.1.0.20250204-py3-none-any.whl", hash = "sha256:32ca4a35430e8b94f6603b35beb7f56c32260ddddd4f4bb305fdf8f92358b87e", size = 10059, upload-time = "2025-02-04T02:39:03.899Z" },
+]
+
+[[package]]
+name = "types-requests"
+version = "2.32.4.20250913"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "urllib3" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/36/27/489922f4505975b11de2b5ad07b4fe1dca0bca9be81a703f26c5f3acfce5/types_requests-2.32.4.20250913.tar.gz", hash = "sha256:abd6d4f9ce3a9383f269775a9835a4c24e5cd6b9f647d64f88aa4613c33def5d", size = 23113, upload-time = "2025-09-13T02:40:02.309Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/be/50/65ffad73746f1d8b15992c030e0fd22965fd5ae2c0206dc28873343b3230/types_pytz-2025.1.0.20250204-py3-none-any.whl", hash = "sha256:32ca4a35430e8b94f6603b35beb7f56c32260ddddd4f4bb305fdf8f92358b87e", size = 10059 },
+ { url = "https://files.pythonhosted.org/packages/2a/20/9a227ea57c1285986c4cf78400d0a91615d25b24e257fd9e2969606bdfae/types_requests-2.32.4.20250913-py3-none-any.whl", hash = "sha256:78c9c1fffebbe0fa487a418e0fa5252017e9c60d1a2da394077f1780f655d7e1", size = 20658, upload-time = "2025-09-13T02:40:01.115Z" },
]
[[package]]
name = "typing-extensions"
version = "4.12.2"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 }
+sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321, upload-time = "2024-06-07T18:52:15.995Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 },
+ { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438, upload-time = "2024-06-07T18:52:13.582Z" },
]
[[package]]
@@ -356,37 +245,30 @@ version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "mypy" },
- { name = "pygithub" },
{ name = "pytz" },
+ { name = "requests" },
{ name = "ruff" },
{ name = "typer" },
{ name = "types-pytz" },
+ { name = "types-requests" },
]
[package.metadata]
requires-dist = [
{ name = "mypy", specifier = ">=1.15.0" },
- { name = "pygithub", specifier = ">=2.6.1" },
{ name = "pytz", specifier = ">=2025.1" },
+ { name = "requests", specifier = ">=2.32.0" },
{ name = "ruff", specifier = ">=0.9.7" },
{ name = "typer", specifier = ">=0.15.1" },
{ name = "types-pytz", specifier = ">=2025.1.0.20250204" },
+ { name = "types-requests", specifier = ">=2.32.0" },
]
[[package]]
name = "urllib3"
version = "2.2.3"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/ed/63/22ba4ebfe7430b76388e7cd448d5478814d3032121827c12a2cc287e2260/urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9", size = 300677 }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/ce/d9/5f4c13cecde62396b0d3fe530a50ccea91e7dfc1ccf0e09c228841bb5ba8/urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac", size = 126338 },
-]
-
-[[package]]
-name = "wrapt"
-version = "1.16.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/95/4c/063a912e20bcef7124e0df97282a8af3ff3e4b603ce84c481d6d7346be0a/wrapt-1.16.0.tar.gz", hash = "sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d", size = 53972 }
+sdist = { url = "https://files.pythonhosted.org/packages/ed/63/22ba4ebfe7430b76388e7cd448d5478814d3032121827c12a2cc287e2260/urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9", size = 300677, upload-time = "2024-09-12T10:52:18.401Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/ff/21/abdedb4cdf6ff41ebf01a74087740a709e2edb146490e4d9beea054b0b7a/wrapt-1.16.0-py3-none-any.whl", hash = "sha256:6906c4100a8fcbf2fa735f6059214bb13b97f75b1a61777fcf6432121ef12ef1", size = 23362 },
+ { url = "https://files.pythonhosted.org/packages/ce/d9/5f4c13cecde62396b0d3fe530a50ccea91e7dfc1ccf0e09c228841bb5ba8/urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac", size = 126338, upload-time = "2024-09-12T10:52:16.589Z" },
]
From 2b02b603174e5a8913384d1ee88a219f70955ae4 Mon Sep 17 00:00:00 2001
From: Lena <241371603+zelenenka@users.noreply.github.com>
Date: Tue, 9 Dec 2025 10:44:30 +0100
Subject: [PATCH 07/48] Fix a search filter in top-ranking issues script
(#44468)
Release Notes:
- N/A
---
script/update_top_ranking_issues/main.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/script/update_top_ranking_issues/main.py b/script/update_top_ranking_issues/main.py
index a8728e94dfab67546b1d9ad3c0339323c8f0114f..336c00497a4dd99e43fcea2fac1a6d763d8acded 100644
--- a/script/update_top_ranking_issues/main.py
+++ b/script/update_top_ranking_issues/main.py
@@ -85,7 +85,7 @@ def get_section_to_issues(
]
if start_date:
- query_parts.append(f"created:<={start_date.strftime('%Y-%m-%d')}")
+ query_parts.append(f"created:>={start_date.strftime('%Y-%m-%d')}")
query = " ".join(query_parts)
url = f"{GITHUB_API_BASE_URL}/search/issues"
From 660234fed25802f49fe512407e655635f357cf9d Mon Sep 17 00:00:00 2001
From: Finn Evers
Date: Tue, 9 Dec 2025 12:49:33 +0100
Subject: [PATCH 08/48] docs: Improve documentation for updating an extension
(#44475)
Release Notes:
- N/A
---
docs/src/extensions/developing-extensions.md | 22 ++++++++------------
1 file changed, 9 insertions(+), 13 deletions(-)
diff --git a/docs/src/extensions/developing-extensions.md b/docs/src/extensions/developing-extensions.md
index 539cbe3d3044afe30b6ced4f3ceb61f537ebde75..dc8a69329176c8dbb7f9785913ae4b7aac6fb230 100644
--- a/docs/src/extensions/developing-extensions.md
+++ b/docs/src/extensions/developing-extensions.md
@@ -109,18 +109,6 @@ git submodule init
git submodule update
```
-## Update Your Extension
-
-When developing/updating your extension, you will likely need to update its content from its submodule in the extensions repository.
-To quickly fetch the latest code for only specific extension (and avoid updating all others), use the specific path:
-
-```sh
-# From the root of the repository:
-git submodule update --remote extensions/your-extension-name
-```
-
-> Note: If you need to update all submodules (e.g., if multiple extensions have changed, or for a full clean build), you can run `git submodule update` without a path, but this will take longer.
-
## Extension License Requirements
As of October 1st, 2025, extension repositories must include a license.
@@ -177,7 +165,15 @@ To update an extension, open a PR to [the `zed-industries/extensions` repo](http
In your PR do the following:
-1. Update the extension's submodule to the commit of the new version.
+1. Update the extension's submodule to the commit of the new version. For this, you can run
+
+```sh
+# From the root of the repository:
+git submodule update --remote extensions/your-extension-name
+```
+
+to update your extension to the latest commit available in your remote repository.
+
2. Update the `version` field for the extension in `extensions.toml`
- Make sure the `version` matches the one set in `extension.toml` at the particular commit.
From b79d92d1c63eed66a102e93f2e7e376133205081 Mon Sep 17 00:00:00 2001
From: Lukas Wirth
Date: Tue, 9 Dec 2025 13:22:57 +0100
Subject: [PATCH 09/48] language_extension: Handle prefixed WASI windows paths
in extension spawning (#44477)
Closes https://github.com/zed-industries/zed/issues/12013
Release Notes:
- Fixed some wasm language extensions failing to spawn on windows
---
.../src/extension_lsp_adapter.rs | 56 ++++++++++++++++++-
1 file changed, 53 insertions(+), 3 deletions(-)
diff --git a/crates/language_extension/src/extension_lsp_adapter.rs b/crates/language_extension/src/extension_lsp_adapter.rs
index 64fa12074b0344702888d87e6af8975049c3adc1..269aac1f2a065815077f9e2cce6b2215ac59bb92 100644
--- a/crates/language_extension/src/extension_lsp_adapter.rs
+++ b/crates/language_extension/src/extension_lsp_adapter.rs
@@ -1,5 +1,5 @@
use std::ops::Range;
-use std::path::PathBuf;
+use std::path::{Path, PathBuf};
use std::sync::Arc;
use anyhow::{Context as _, Result};
@@ -174,7 +174,32 @@ impl DynLspInstaller for ExtensionLspAdapter {
)
.await?;
- let path = self.extension.path_from_extension(command.command.as_ref());
+ // on windows, extensions might produce weird paths
+ // that start with a leading slash due to WASI
+ // requiring that for PWD and friends so account for
+ // that here and try to transform those paths back
+ // to windows paths
+ //
+ // if we don't do this, std will interpret the path as relative,
+ // which changes join behavior
+ let command_path: &Path = if cfg!(windows)
+ && let Some(command) = command.command.to_str()
+ {
+ let mut chars = command.chars();
+ if chars.next().is_some_and(|c| c == '/')
+ && chars.next().is_some_and(|c| c.is_ascii_alphabetic())
+ && chars.next().is_some_and(|c| c == ':')
+ && chars.next().is_some_and(|c| c == '\\' || c == '/')
+ {
+ // looks like a windows path with a leading slash, so strip it
+ command.strip_prefix('/').unwrap().as_ref()
+ } else {
+ command.as_ref()
+ }
+ } else {
+ command.command.as_ref()
+ };
+ let path = self.extension.path_from_extension(command_path);
// TODO: This should now be done via the `zed::make_file_executable` function in
// Zed extension API, but we're leaving these existing usages in place temporarily
@@ -193,7 +218,32 @@ impl DynLspInstaller for ExtensionLspAdapter {
Ok(LanguageServerBinary {
path,
- arguments: command.args.into_iter().map(|arg| arg.into()).collect(),
+ arguments: command
+ .args
+ .into_iter()
+ .map(|arg| {
+ // on windows, extensions might produce weird paths
+ // that start with a leading slash due to WASI
+ // requiring that for PWD and friends so account for
+ // that here and try to transform those paths back
+ // to windows paths
+ if cfg!(windows) {
+ let mut chars = arg.chars();
+ if chars.next().is_some_and(|c| c == '/')
+ && chars.next().is_some_and(|c| c.is_ascii_alphabetic())
+ && chars.next().is_some_and(|c| c == ':')
+ && chars.next().is_some_and(|c| c == '\\' || c == '/')
+ {
+ // looks like a windows path with a leading slash, so strip it
+ arg.strip_prefix('/').unwrap().into()
+ } else {
+ arg.into()
+ }
+ } else {
+ arg.into()
+ }
+ })
+ .collect(),
env: Some(command.env.into_iter().collect()),
})
})
From abf90cc274da0a40e479cef5981bbc9da4381abb Mon Sep 17 00:00:00 2001
From: Mustaque Ahmed
Date: Tue, 9 Dec 2025 18:27:23 +0530
Subject: [PATCH 10/48] language: Add auto-surround for Plain Text, JSON, and
JSONC (#42631)
**Summary**
When users selected text and pressed opening brackets (`(`, `[`, `{`),
the text was deleted instead of being wrapped.
- Added bracket pairs: `()`, `[]`, `{}`, `""`, `''` with `surround =
true`
- Added `surround = true` to existing bracket pairs
- Added `()` bracket pair
**Production Build Fix** (`crates/languages/src/lib.rs`)
- Fixed bug where `brackets` config was stripped in non-`load-grammars`
builds
- Preserved `brackets: config.brackets` in production mode
Closes #41186
**Screen recording**
https://github.com/user-attachments/assets/22067fe7-d5c4-4a72-a93d-8dbaae640168
Release Notes:
- N/A *or* Added/Fixed/Improved ...
---------
Co-authored-by: Smit Barmase
---
crates/language/src/language.rs | 40 ++++++++++++++++++++++++++
crates/languages/src/json/config.toml | 7 +++--
crates/languages/src/jsonc/config.toml | 7 +++--
3 files changed, 48 insertions(+), 6 deletions(-)
diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs
index 90a55592864a637a2692ffac6e30451827a479ea..a6a76dc70269777eb3acda79bd3fb32865c4b7ee 100644
--- a/crates/language/src/language.rs
+++ b/crates/language/src/language.rs
@@ -136,6 +136,46 @@ pub static PLAIN_TEXT: LazyLock> = LazyLock::new(|| {
path_suffixes: vec!["txt".to_owned()],
first_line_pattern: None,
},
+ brackets: BracketPairConfig {
+ pairs: vec![
+ BracketPair {
+ start: "(".to_string(),
+ end: ")".to_string(),
+ close: true,
+ surround: true,
+ newline: false,
+ },
+ BracketPair {
+ start: "[".to_string(),
+ end: "]".to_string(),
+ close: true,
+ surround: true,
+ newline: false,
+ },
+ BracketPair {
+ start: "{".to_string(),
+ end: "}".to_string(),
+ close: true,
+ surround: true,
+ newline: false,
+ },
+ BracketPair {
+ start: "\"".to_string(),
+ end: "\"".to_string(),
+ close: true,
+ surround: true,
+ newline: false,
+ },
+ BracketPair {
+ start: "'".to_string(),
+ end: "'".to_string(),
+ close: true,
+ surround: true,
+ newline: false,
+ },
+ ],
+ disabled_scopes_by_bracket_ix: Default::default(),
+ },
..Default::default()
},
None,
diff --git a/crates/languages/src/json/config.toml b/crates/languages/src/json/config.toml
index 8caa46c8a45076557d5f6c897fc1a5ad11ffa6ac..d21412d7bd2ed8f5ce1062728b9418ed891fa260 100644
--- a/crates/languages/src/json/config.toml
+++ b/crates/languages/src/json/config.toml
@@ -4,9 +4,10 @@ path_suffixes = ["json", "flake.lock"]
line_comments = ["// "]
autoclose_before = ",]}"
brackets = [
- { start = "{", end = "}", close = true, newline = true },
- { start = "[", end = "]", close = true, newline = true },
- { start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] },
+ { start = "{", end = "}", close = true, surround = true, newline = true },
+ { start = "[", end = "]", close = true, surround = true, newline = true },
+ { start = "(", end = ")", close = true, surround = true, newline = false },
+ { start = "\"", end = "\"", close = true, surround = true, newline = false, not_in = ["string"] },
]
tab_size = 2
prettier_parser_name = "json"
diff --git a/crates/languages/src/jsonc/config.toml b/crates/languages/src/jsonc/config.toml
index fb86cb394aaa74ffb6670e9719c0fe892f02a5c1..cb7ad38ec78fbe4a9b04816400375cefa444f055 100644
--- a/crates/languages/src/jsonc/config.toml
+++ b/crates/languages/src/jsonc/config.toml
@@ -4,9 +4,10 @@ path_suffixes = ["jsonc", "bun.lock", "tsconfig.json", "pyrightconfig.json"]
line_comments = ["// "]
autoclose_before = ",]}"
brackets = [
- { start = "{", end = "}", close = true, newline = true },
- { start = "[", end = "]", close = true, newline = true },
- { start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] },
+ { start = "{", end = "}", close = true, surround = true, newline = true },
+ { start = "[", end = "]", close = true, surround = true, newline = true },
+ { start = "(", end = ")", close = true, surround = true, newline = false },
+ { start = "\"", end = "\"", close = true, surround = true, newline = false, not_in = ["string"] },
]
tab_size = 2
prettier_parser_name = "jsonc"
From 4d0cada8f44e934cd4ce729133373d1f4c30a013 Mon Sep 17 00:00:00 2001
From: Xiaobo Liu
Date: Tue, 9 Dec 2025 21:47:45 +0800
Subject: [PATCH 11/48] git_ui: Hide breakpoints in commit views (#44484)
Release Notes:
- Improved commit view to not show breakpoints on hover
Signed-off-by: Xiaobo Liu
---
crates/git_ui/src/commit_view.rs | 1 +
1 file changed, 1 insertion(+)
diff --git a/crates/git_ui/src/commit_view.rs b/crates/git_ui/src/commit_view.rs
index c637ea674f7e58954c186e1557df251d0d22d36b..238b0cbf52fdb4312178b868be4b22986ea946c3 100644
--- a/crates/git_ui/src/commit_view.rs
+++ b/crates/git_ui/src/commit_view.rs
@@ -152,6 +152,7 @@ impl CommitView {
Editor::for_multibuffer(multibuffer.clone(), Some(project.clone()), window, cx);
editor.disable_inline_diagnostics();
+ editor.set_show_breakpoints(false, cx);
editor.set_expand_all_diff_hunks(cx);
editor
From f05ee8a24df5e350271b487543beaca2e4bdad2a Mon Sep 17 00:00:00 2001
From: Aaron Feickert <66188213+AaronFeickert@users.noreply.github.com>
Date: Tue, 9 Dec 2025 07:55:01 -0600
Subject: [PATCH 12/48] Fix menu capitalization (#44450)
This PR fixes fixes capitalization of two menu items for consistency
elsewhere in the application.
Release Notes:
- N/A
---
crates/zed/src/zed/app_menus.rs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/crates/zed/src/zed/app_menus.rs b/crates/zed/src/zed/app_menus.rs
index e4355636c74e1bfd126c3b74057827f9a1fa9c0e..a7961ac6d4cb663353af1e4e0d1fe66cf43a80a3 100644
--- a/crates/zed/src/zed/app_menus.rs
+++ b/crates/zed/src/zed/app_menus.rs
@@ -169,7 +169,7 @@ pub fn app_menus(cx: &mut App) -> Vec {
MenuItem::os_action("Paste", editor::actions::Paste, OsAction::Paste),
MenuItem::separator(),
MenuItem::action("Find", search::buffer_search::Deploy::find()),
- MenuItem::action("Find In Project", workspace::DeploySearch::find()),
+ MenuItem::action("Find in Project", workspace::DeploySearch::find()),
MenuItem::separator(),
MenuItem::action(
"Toggle Line Comment",
@@ -280,7 +280,7 @@ pub fn app_menus(cx: &mut App) -> Vec {
MenuItem::separator(),
MenuItem::action("Toggle Breakpoint", editor::actions::ToggleBreakpoint),
MenuItem::action("Edit Breakpoint", editor::actions::EditLogBreakpoint),
- MenuItem::action("Clear all Breakpoints", debugger_ui::ClearAllBreakpoints),
+ MenuItem::action("Clear All Breakpoints", debugger_ui::ClearAllBreakpoints),
],
},
Menu {
From 1471105643c49549576d61a089ca916053d3c2b0 Mon Sep 17 00:00:00 2001
From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
Date: Tue, 9 Dec 2025 16:13:52 +0100
Subject: [PATCH 13/48] edit_prediction: Remove duplicate definition of
interpolate_edits (#44485)
Release Notes:
- N/A
---
Cargo.lock | 1 +
crates/edit_prediction/src/prediction.rs | 53 ++-----------------
crates/edit_prediction_types/Cargo.toml | 1 +
.../src/edit_prediction_types.rs | 6 +--
4 files changed, 9 insertions(+), 52 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 9de96dfe48ccd211c94539e541ac55da3de8ac63..5790df07111b0752321c9d29da61a16839236275 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -5247,6 +5247,7 @@ dependencies = [
"client",
"gpui",
"language",
+ "text",
]
[[package]]
diff --git a/crates/edit_prediction/src/prediction.rs b/crates/edit_prediction/src/prediction.rs
index d169cf26e1dc4477554bfe8821ff5eae083a6124..8aa2a8218568a99404cc9aceff36b84127700152 100644
--- a/crates/edit_prediction/src/prediction.rs
+++ b/crates/edit_prediction/src/prediction.rs
@@ -6,8 +6,9 @@ use std::{
};
use cloud_llm_client::EditPredictionRejectReason;
+use edit_prediction_types::interpolate_edits;
use gpui::{AsyncApp, Entity, SharedString};
-use language::{Anchor, Buffer, BufferSnapshot, EditPreview, OffsetRangeExt, TextBufferSnapshot};
+use language::{Anchor, Buffer, BufferSnapshot, EditPreview, TextBufferSnapshot};
use serde::Serialize;
#[derive(Clone, Default, Debug, PartialEq, Eq, Hash)]
@@ -53,7 +54,7 @@ impl EditPredictionResult {
.read_with(cx, |buffer, cx| {
let new_snapshot = buffer.snapshot();
let edits: Arc<[_]> =
- interpolate_edits(&edited_buffer_snapshot, &new_snapshot, edits)?.into();
+ interpolate_edits(&edited_buffer_snapshot, &new_snapshot, &edits)?.into();
Some((edits.clone(), new_snapshot, buffer.preview_edits(edits, cx)))
})
@@ -109,7 +110,7 @@ impl EditPrediction {
&self,
new_snapshot: &TextBufferSnapshot,
) -> Option, Arc)>> {
- interpolate_edits(&self.snapshot, new_snapshot, self.edits.clone())
+ interpolate_edits(&self.snapshot, new_snapshot, &self.edits)
}
pub fn targets_buffer(&self, buffer: &Buffer) -> bool {
@@ -130,52 +131,6 @@ impl std::fmt::Debug for EditPrediction {
}
}
-pub fn interpolate_edits(
- old_snapshot: &TextBufferSnapshot,
- new_snapshot: &TextBufferSnapshot,
- current_edits: Arc<[(Range, Arc)]>,
-) -> Option, Arc)>> {
- let mut edits = Vec::new();
-
- let mut model_edits = current_edits.iter().peekable();
- for user_edit in new_snapshot.edits_since::(&old_snapshot.version) {
- while let Some((model_old_range, _)) = model_edits.peek() {
- let model_old_range = model_old_range.to_offset(old_snapshot);
- if model_old_range.end < user_edit.old.start {
- let (model_old_range, model_new_text) = model_edits.next().unwrap();
- edits.push((model_old_range.clone(), model_new_text.clone()));
- } else {
- break;
- }
- }
-
- if let Some((model_old_range, model_new_text)) = model_edits.peek() {
- let model_old_offset_range = model_old_range.to_offset(old_snapshot);
- if user_edit.old == model_old_offset_range {
- let user_new_text = new_snapshot
- .text_for_range(user_edit.new.clone())
- .collect::();
-
- if let Some(model_suffix) = model_new_text.strip_prefix(&user_new_text) {
- if !model_suffix.is_empty() {
- let anchor = old_snapshot.anchor_after(user_edit.old.end);
- edits.push((anchor..anchor, model_suffix.into()));
- }
-
- model_edits.next();
- continue;
- }
- }
- }
-
- return None;
- }
-
- edits.extend(model_edits.cloned());
-
- if edits.is_empty() { None } else { Some(edits) }
-}
-
#[cfg(test)]
mod tests {
use super::*;
diff --git a/crates/edit_prediction_types/Cargo.toml b/crates/edit_prediction_types/Cargo.toml
index ebc09680e1dcf99dc21e1714eca6a9db337f4a90..00a8577911af0afd012535fd324a68af8fd70391 100644
--- a/crates/edit_prediction_types/Cargo.toml
+++ b/crates/edit_prediction_types/Cargo.toml
@@ -15,3 +15,4 @@ path = "src/edit_prediction_types.rs"
client.workspace = true
gpui.workspace = true
language.workspace = true
+text.workspace = true
diff --git a/crates/edit_prediction_types/src/edit_prediction_types.rs b/crates/edit_prediction_types/src/edit_prediction_types.rs
index 1f63b8626d15dfd3e2cba78aacb50505186da01c..fbcb3c4c00edbc5fb77f04d1fcaaf4b6129c43db 100644
--- a/crates/edit_prediction_types/src/edit_prediction_types.rs
+++ b/crates/edit_prediction_types/src/edit_prediction_types.rs
@@ -2,7 +2,7 @@ use std::{ops::Range, sync::Arc};
use client::EditPredictionUsage;
use gpui::{App, Context, Entity, SharedString};
-use language::{Anchor, Buffer, BufferSnapshot, OffsetRangeExt};
+use language::{Anchor, Buffer, OffsetRangeExt};
// TODO: Find a better home for `Direction`.
//
@@ -252,8 +252,8 @@ where
/// Returns edits updated based on user edits since the old snapshot. None is returned if any user
/// edit is not a prefix of a predicted insertion.
pub fn interpolate_edits(
- old_snapshot: &BufferSnapshot,
- new_snapshot: &BufferSnapshot,
+ old_snapshot: &text::BufferSnapshot,
+ new_snapshot: &text::BufferSnapshot,
current_edits: &[(Range, Arc)],
) -> Option, Arc)>> {
let mut edits = Vec::new();
From a524071dd9b3b60aec6e21125d28d90347992135 Mon Sep 17 00:00:00 2001
From: Nia
Date: Tue, 9 Dec 2025 17:00:13 +0100
Subject: [PATCH 14/48] gpui: Try to notify when GPU init fails (#44487)
Hopefully addresses #43575. cc @cole-miller
Release Notes:
- GPU initialization errors are more reliably reported
---------
Co-authored-by: Cole Miller
---
crates/gpui/src/platform/linux/platform.rs | 44 +++++++++++++++++++
.../gpui/src/platform/linux/wayland/client.rs | 9 ++--
crates/gpui/src/platform/linux/x11/client.rs | 6 +--
crates/zed/src/main.rs | 1 +
4 files changed, 53 insertions(+), 7 deletions(-)
diff --git a/crates/gpui/src/platform/linux/platform.rs b/crates/gpui/src/platform/linux/platform.rs
index 21468370c499058af207a7e7b02ca1bbd7563ec1..f5056741df016cfe88c83379d7b1afd85b9900ca 100644
--- a/crates/gpui/src/platform/linux/platform.rs
+++ b/crates/gpui/src/platform/linux/platform.rs
@@ -43,6 +43,50 @@ pub(crate) const KEYRING_LABEL: &str = "zed-github-account";
const FILE_PICKER_PORTAL_MISSING: &str =
"Couldn't open file picker due to missing xdg-desktop-portal implementation.";
+#[cfg(any(feature = "x11", feature = "wayland"))]
+pub trait ResultExt {
+ type Ok;
+
+ fn notify_err(self, msg: &'static str) -> Self::Ok;
+}
+
+#[cfg(any(feature = "x11", feature = "wayland"))]
+impl ResultExt for anyhow::Result {
+ type Ok = T;
+
+ fn notify_err(self, msg: &'static str) -> T {
+ match self {
+ Ok(v) => v,
+ Err(e) => {
+ use ashpd::desktop::notification::{Notification, NotificationProxy, Priority};
+ use futures::executor::block_on;
+
+ let proxy = block_on(NotificationProxy::new()).expect(msg);
+
+ let notification_id = "dev.zed.Oops";
+ block_on(
+ proxy.add_notification(
+ notification_id,
+ Notification::new("Zed failed to launch")
+ .body(Some(
+ format!(
+ "{e:?}. See https://zed.dev/docs/linux for troubleshooting steps."
+ )
+ .as_str(),
+ ))
+ .priority(Priority::High)
+ .icon(ashpd::desktop::Icon::with_names(&[
+ "dialog-question-symbolic",
+ ])),
+ )
+ ).expect(msg);
+
+ panic!("{msg}");
+ }
+ }
+ }
+}
+
pub trait LinuxClient {
fn compositor_name(&self) -> &'static str;
fn with_common(&self, f: impl FnOnce(&mut LinuxCommon) -> R) -> R;
diff --git a/crates/gpui/src/platform/linux/wayland/client.rs b/crates/gpui/src/platform/linux/wayland/client.rs
index 2879925495e41fd37ea075f20a0de0b19625694e..1a7011c582ab162c8ed6c7277d3dd1f5b8c60239 100644
--- a/crates/gpui/src/platform/linux/wayland/client.rs
+++ b/crates/gpui/src/platform/linux/wayland/client.rs
@@ -17,7 +17,7 @@ use collections::HashMap;
use filedescriptor::Pipe;
use http_client::Url;
use smallvec::SmallVec;
-use util::ResultExt;
+use util::ResultExt as _;
use wayland_backend::client::ObjectId;
use wayland_backend::protocol::WEnum;
use wayland_client::event_created_child;
@@ -76,8 +76,8 @@ use crate::{
FileDropEvent, ForegroundExecutor, KeyDownEvent, KeyUpEvent, Keystroke, LinuxCommon,
LinuxKeyboardLayout, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent,
MouseExitEvent, MouseMoveEvent, MouseUpEvent, NavigationDirection, Pixels, PlatformDisplay,
- PlatformInput, PlatformKeyboardLayout, Point, SCROLL_LINES, ScrollDelta, ScrollWheelEvent,
- Size, TouchPhase, WindowParams, point, px, size,
+ PlatformInput, PlatformKeyboardLayout, Point, ResultExt as _, SCROLL_LINES, ScrollDelta,
+ ScrollWheelEvent, Size, TouchPhase, WindowParams, point, px, size,
};
use crate::{
LinuxDispatcher, RunnableVariant, TaskTiming,
@@ -531,7 +531,8 @@ impl WaylandClient {
})
.unwrap();
- let gpu_context = BladeContext::new().expect("Unable to init GPU context");
+ // This could be unified with the notification handling in zed/main:fail_to_open_window.
+ let gpu_context = BladeContext::new().notify_err("Unable to init GPU context");
let seat = seat.unwrap();
let globals = Globals::new(
diff --git a/crates/gpui/src/platform/linux/x11/client.rs b/crates/gpui/src/platform/linux/x11/client.rs
index 32f50cdf5d9d9439909c7ecaf35df0d75a9c9eae..aa16dc7ad1d9030665ace646ba2ac295df8c27b3 100644
--- a/crates/gpui/src/platform/linux/x11/client.rs
+++ b/crates/gpui/src/platform/linux/x11/client.rs
@@ -1,4 +1,4 @@
-use crate::{Capslock, LinuxDispatcher, RunnableVariant, TaskTiming, xcb_flush};
+use crate::{Capslock, LinuxDispatcher, ResultExt as _, RunnableVariant, TaskTiming, xcb_flush};
use anyhow::{Context as _, anyhow};
use ashpd::WindowIdentifier;
use calloop::{
@@ -18,7 +18,7 @@ use std::{
rc::{Rc, Weak},
time::{Duration, Instant},
};
-use util::ResultExt;
+use util::ResultExt as _;
use x11rb::{
connection::{Connection, RequestConnection},
@@ -437,7 +437,7 @@ impl X11Client {
.to_string();
let keyboard_layout = LinuxKeyboardLayout::new(layout_name.into());
- let gpu_context = BladeContext::new().context("Unable to init GPU context")?;
+ let gpu_context = BladeContext::new().notify_err("Unable to init GPU context");
let resource_database = x11rb::resource_manager::new_from_default(&xcb_connection)
.context("Failed to create resource database")?;
diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs
index 7751e6cb0118e3590488600ca2601645d6657fb7..0e82b3323b36f4845b584b33f65e440963801a9f 100644
--- a/crates/zed/src/main.rs
+++ b/crates/zed/src/main.rs
@@ -130,6 +130,7 @@ fn fail_to_open_window(e: anyhow::Error, _cx: &mut App) {
process::exit(1);
}
+ // Maybe unify this with gpui::platform::linux::platform::ResultExt::notify_err(..)?
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
{
use ashpd::desktop::notification::{Notification, NotificationProxy, Priority};
From d5a437d22f4c7a981a5bb8baff3166d4b06846b2 Mon Sep 17 00:00:00 2001
From: Pablo Aguiar
Date: Tue, 9 Dec 2025 17:15:14 +0100
Subject: [PATCH 15/48] editor: Add rotation commands for selections and lines
(#41236)
Introduces RotateSelectionsForward and RotateSelectionsBackward actions
that rotate content in a circular fashion across multiple cursors.
Behavior based on context:
- With selections: rotates the selected text at each cursor position
(e.g., x=1, y=2, z=3 becomes x=3, y=1, z=2)
- With just cursors: rotates entire lines at cursor positions (e.g.,
three lines cycle to line3, line1, line2)
Selections are preserved after rotation, allowing repeated cycling.
Useful for quickly rearranging values, lines, or arguments.
For more examples and use cases, please refer to #5315.
I'm eager to read your thoughts and make any adjustments or improvements
to any aspect of this change.
Closes #5315
Release Notes:
- Added `RotateSelectionsForward` and `RotateSelectionsBackward` actions
that rotate content in a circular fashion across multiple cursors
---
crates/editor/src/actions.rs | 4 +
crates/editor/src/editor.rs | 162 ++++++++++++++++++++++++++++++
crates/editor/src/editor_tests.rs | 110 ++++++++++++++++++++
crates/editor/src/element.rs | 2 +
4 files changed, 278 insertions(+)
diff --git a/crates/editor/src/actions.rs b/crates/editor/src/actions.rs
index 7d6f486974d1ef7e792bd79997aebd332c2336f4..fb058eb8d7c5ad72a2b2656c3ce943871a623163 100644
--- a/crates/editor/src/actions.rs
+++ b/crates/editor/src/actions.rs
@@ -680,6 +680,10 @@ actions!(
ReloadFile,
/// Rewraps text to fit within the preferred line length.
Rewrap,
+ /// Rotates selections or lines backward.
+ RotateSelectionsBackward,
+ /// Rotates selections or lines forward.
+ RotateSelectionsForward,
/// Runs flycheck diagnostics.
RunFlycheck,
/// Scrolls the cursor to the bottom of the viewport.
diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs
index d173c1cb4aac782283a3832b5e411a0a44cc1f23..023e4931d33be86c70b90a8cd62aa5692c25c9d9 100644
--- a/crates/editor/src/editor.rs
+++ b/crates/editor/src/editor.rs
@@ -11516,6 +11516,168 @@ impl Editor {
self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
}
+ pub fn rotate_selections_forward(
+ &mut self,
+ _: &RotateSelectionsForward,
+ window: &mut Window,
+ cx: &mut Context,
+ ) {
+ self.rotate_selections(window, cx, false)
+ }
+
+ pub fn rotate_selections_backward(
+ &mut self,
+ _: &RotateSelectionsBackward,
+ window: &mut Window,
+ cx: &mut Context,
+ ) {
+ self.rotate_selections(window, cx, true)
+ }
+
+ fn rotate_selections(&mut self, window: &mut Window, cx: &mut Context, reverse: bool) {
+ self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
+ let display_snapshot = self.display_snapshot(cx);
+ let selections = self.selections.all::(&display_snapshot);
+
+ if selections.len() < 2 {
+ return;
+ }
+
+ let (edits, new_selections) = {
+ let buffer = self.buffer.read(cx).read(cx);
+ let has_selections = selections.iter().any(|s| !s.is_empty());
+ if has_selections {
+ let mut selected_texts: Vec = selections
+ .iter()
+ .map(|selection| {
+ buffer
+ .text_for_range(selection.start..selection.end)
+ .collect()
+ })
+ .collect();
+
+ if reverse {
+ selected_texts.rotate_left(1);
+ } else {
+ selected_texts.rotate_right(1);
+ }
+
+ let mut offset_delta: i64 = 0;
+ let mut new_selections = Vec::new();
+ let edits: Vec<_> = selections
+ .iter()
+ .zip(selected_texts.iter())
+ .map(|(selection, new_text)| {
+ let old_len = (selection.end.0 - selection.start.0) as i64;
+ let new_len = new_text.len() as i64;
+ let adjusted_start =
+ MultiBufferOffset((selection.start.0 as i64 + offset_delta) as usize);
+ let adjusted_end =
+ MultiBufferOffset((adjusted_start.0 as i64 + new_len) as usize);
+
+ new_selections.push(Selection {
+ id: selection.id,
+ start: adjusted_start,
+ end: adjusted_end,
+ reversed: selection.reversed,
+ goal: selection.goal,
+ });
+
+ offset_delta += new_len - old_len;
+ (selection.start..selection.end, new_text.clone())
+ })
+ .collect();
+ (edits, new_selections)
+ } else {
+ let mut all_rows: Vec = selections
+ .iter()
+ .map(|selection| buffer.offset_to_point(selection.start).row)
+ .collect();
+ all_rows.sort_unstable();
+ all_rows.dedup();
+
+ if all_rows.len() < 2 {
+ return;
+ }
+
+ let line_ranges: Vec> = all_rows
+ .iter()
+ .map(|&row| {
+ let start = Point::new(row, 0);
+ let end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
+ buffer.point_to_offset(start)..buffer.point_to_offset(end)
+ })
+ .collect();
+
+ let mut line_texts: Vec = line_ranges
+ .iter()
+ .map(|range| buffer.text_for_range(range.clone()).collect())
+ .collect();
+
+ if reverse {
+ line_texts.rotate_left(1);
+ } else {
+ line_texts.rotate_right(1);
+ }
+
+ let edits = line_ranges
+ .iter()
+ .zip(line_texts.iter())
+ .map(|(range, new_text)| (range.clone(), new_text.clone()))
+ .collect();
+
+ let num_rows = all_rows.len();
+ let row_to_index: std::collections::HashMap = all_rows
+ .iter()
+ .enumerate()
+ .map(|(i, &row)| (row, i))
+ .collect();
+
+ // Compute new line start offsets after rotation (handles CRLF)
+ let newline_len = line_ranges[1].start.0 - line_ranges[0].end.0;
+ let first_line_start = line_ranges[0].start.0;
+ let mut new_line_starts: Vec = vec![first_line_start];
+ for text in line_texts.iter().take(num_rows - 1) {
+ let prev_start = *new_line_starts.last().unwrap();
+ new_line_starts.push(prev_start + text.len() + newline_len);
+ }
+
+ let new_selections = selections
+ .iter()
+ .map(|selection| {
+ let point = buffer.offset_to_point(selection.start);
+ let old_index = row_to_index[&point.row];
+ let new_index = if reverse {
+ (old_index + num_rows - 1) % num_rows
+ } else {
+ (old_index + 1) % num_rows
+ };
+ let new_offset =
+ MultiBufferOffset(new_line_starts[new_index] + point.column as usize);
+ Selection {
+ id: selection.id,
+ start: new_offset,
+ end: new_offset,
+ reversed: selection.reversed,
+ goal: selection.goal,
+ }
+ })
+ .collect();
+
+ (edits, new_selections)
+ }
+ };
+
+ self.transact(window, cx, |this, window, cx| {
+ this.buffer.update(cx, |buffer, cx| {
+ buffer.edit(edits, None, cx);
+ });
+ this.change_selections(Default::default(), window, cx, |s| {
+ s.select(new_selections);
+ });
+ });
+ }
+
fn manipulate_lines(
&mut self,
window: &mut Window,
diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs
index 3bd5e6bf8f7947dfc9ac26f8ecbe9b6554151fcb..3c33519370907d3a2f53d63d9e24403c36a5e45a 100644
--- a/crates/editor/src/editor_tests.rs
+++ b/crates/editor/src/editor_tests.rs
@@ -5777,6 +5777,116 @@ fn test_duplicate_line(cx: &mut TestAppContext) {
});
}
+#[gpui::test]
+async fn test_rotate_selections(cx: &mut TestAppContext) {
+ init_test(cx, |_| {});
+
+ let mut cx = EditorTestContext::new(cx).await;
+
+ // Rotate text selections (horizontal)
+ cx.set_state("x=«1ˇ», y=«2ˇ», z=«3ˇ»");
+ cx.update_editor(|e, window, cx| {
+ e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
+ });
+ cx.assert_editor_state("x=«3ˇ», y=«1ˇ», z=«2ˇ»");
+ cx.update_editor(|e, window, cx| {
+ e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
+ });
+ cx.assert_editor_state("x=«1ˇ», y=«2ˇ», z=«3ˇ»");
+
+ // Rotate text selections (vertical)
+ cx.set_state(indoc! {"
+ x=«1ˇ»
+ y=«2ˇ»
+ z=«3ˇ»
+ "});
+ cx.update_editor(|e, window, cx| {
+ e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
+ });
+ cx.assert_editor_state(indoc! {"
+ x=«3ˇ»
+ y=«1ˇ»
+ z=«2ˇ»
+ "});
+ cx.update_editor(|e, window, cx| {
+ e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
+ });
+ cx.assert_editor_state(indoc! {"
+ x=«1ˇ»
+ y=«2ˇ»
+ z=«3ˇ»
+ "});
+
+ // Rotate text selections (vertical, different lengths)
+ cx.set_state(indoc! {"
+ x=\"«ˇ»\"
+ y=\"«aˇ»\"
+ z=\"«aaˇ»\"
+ "});
+ cx.update_editor(|e, window, cx| {
+ e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
+ });
+ cx.assert_editor_state(indoc! {"
+ x=\"«aaˇ»\"
+ y=\"«ˇ»\"
+ z=\"«aˇ»\"
+ "});
+ cx.update_editor(|e, window, cx| {
+ e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
+ });
+ cx.assert_editor_state(indoc! {"
+ x=\"«ˇ»\"
+ y=\"«aˇ»\"
+ z=\"«aaˇ»\"
+ "});
+
+ // Rotate whole lines (cursor positions preserved)
+ cx.set_state(indoc! {"
+ ˇline123
+ liˇne23
+ line3ˇ
+ "});
+ cx.update_editor(|e, window, cx| {
+ e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
+ });
+ cx.assert_editor_state(indoc! {"
+ line3ˇ
+ ˇline123
+ liˇne23
+ "});
+ cx.update_editor(|e, window, cx| {
+ e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
+ });
+ cx.assert_editor_state(indoc! {"
+ ˇline123
+ liˇne23
+ line3ˇ
+ "});
+
+ // Rotate whole lines, multiple cursors per line (positions preserved)
+ cx.set_state(indoc! {"
+ ˇliˇne123
+ ˇline23
+ ˇline3
+ "});
+ cx.update_editor(|e, window, cx| {
+ e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
+ });
+ cx.assert_editor_state(indoc! {"
+ ˇline3
+ ˇliˇne123
+ ˇline23
+ "});
+ cx.update_editor(|e, window, cx| {
+ e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
+ });
+ cx.assert_editor_state(indoc! {"
+ ˇliˇne123
+ ˇline23
+ ˇline3
+ "});
+}
+
#[gpui::test]
fn test_move_line_up_down(cx: &mut TestAppContext) {
init_test(cx, |_| {});
diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs
index 5e5749494017479b921a2bbdb2af8fb7d62c9bf4..fab51cbef29de436e447c317849ad15aa318c45d 100644
--- a/crates/editor/src/element.rs
+++ b/crates/editor/src/element.rs
@@ -253,6 +253,8 @@ impl EditorElement {
register_action(editor, window, Editor::sort_lines_case_insensitive);
register_action(editor, window, Editor::reverse_lines);
register_action(editor, window, Editor::shuffle_lines);
+ register_action(editor, window, Editor::rotate_selections_forward);
+ register_action(editor, window, Editor::rotate_selections_backward);
register_action(editor, window, Editor::convert_indentation_to_spaces);
register_action(editor, window, Editor::convert_indentation_to_tabs);
register_action(editor, window, Editor::convert_to_upper_case);
From dd57d97bb67f1bc30afe4e192082ee48c6c82dc6 Mon Sep 17 00:00:00 2001
From: Gaauwe Rombouts
Date: Tue, 9 Dec 2025 17:50:23 +0100
Subject: [PATCH 16/48] Revert "Improve TS/TSX/JS syntax highlighting for
parameters, types, and punctuation" (#44490)
Reverts zed-industries/zed#43437
Internally we noticed some regression related to removed query for
PascalCase identifiers. Reverting now to prevent this from going to
preview, still planning to land this with the necessary fixes later.
---
.../languages/src/javascript/highlights.scm | 39 +-------
crates/languages/src/jsdoc/highlights.scm | 1 -
crates/languages/src/tsx/highlights.scm | 93 +-----------------
.../languages/src/typescript/highlights.scm | 94 +------------------
4 files changed, 6 insertions(+), 221 deletions(-)
diff --git a/crates/languages/src/javascript/highlights.scm b/crates/languages/src/javascript/highlights.scm
index 378cb579f4366c4be9eb16fa87708aeb5391814a..e5b84ab68df2b32061691f469046569a6597750e 100644
--- a/crates/languages/src/javascript/highlights.scm
+++ b/crates/languages/src/javascript/highlights.scm
@@ -47,45 +47,10 @@
left: (identifier) @function
right: [(function_expression) (arrow_function)])
-; Parameters
-
-(required_parameter
- (identifier) @variable.parameter)
-
-(required_parameter
- (_
- ([
- (identifier)
- (shorthand_property_identifier_pattern)
- ]) @variable.parameter))
-
-(optional_parameter
- (identifier) @variable.parameter)
-
-(optional_parameter
- (_
- ([
- (identifier)
- (shorthand_property_identifier_pattern)
- ]) @variable.parameter))
-
-(catch_clause
- parameter: (identifier) @variable.parameter)
-
-(index_signature
- name: (identifier) @variable.parameter)
-
-(arrow_function
- parameter: (identifier) @variable.parameter)
-
; Special identifiers
-;
-(class_declaration
- (type_identifier) @type.class)
-
-(extends_clause
- value: (identifier) @type.class)
+((identifier) @type
+ (#match? @type "^[A-Z]"))
(type_identifier) @type
(predefined_type) @type.builtin
diff --git a/crates/languages/src/jsdoc/highlights.scm b/crates/languages/src/jsdoc/highlights.scm
index 581b5d8111fe25443de9951cfdddc8c277ad83ff..103d32d0bd29dae56bd456893288e86a8cf87148 100644
--- a/crates/languages/src/jsdoc/highlights.scm
+++ b/crates/languages/src/jsdoc/highlights.scm
@@ -1,3 +1,2 @@
(tag_name) @keyword.jsdoc
(type) @type.jsdoc
-(identifier) @variable.jsdoc
diff --git a/crates/languages/src/tsx/highlights.scm b/crates/languages/src/tsx/highlights.scm
index 816ae3c58634597495f943dfaeb4a7e7aab77228..ef12b3d7913e07109e32bb5bf41909511aa2b555 100644
--- a/crates/languages/src/tsx/highlights.scm
+++ b/crates/languages/src/tsx/highlights.scm
@@ -47,68 +47,13 @@
left: (identifier) @function
right: [(function_expression) (arrow_function)])
-; Parameters
-
-(required_parameter
- (identifier) @variable.parameter)
-
-(required_parameter
- (_
- ([
- (identifier)
- (shorthand_property_identifier_pattern)
- ]) @variable.parameter))
-
-(optional_parameter
- (identifier) @variable.parameter)
-
-(optional_parameter
- (_
- ([
- (identifier)
- (shorthand_property_identifier_pattern)
- ]) @variable.parameter))
-
-(catch_clause
- parameter: (identifier) @variable.parameter)
-
-(index_signature
- name: (identifier) @variable.parameter)
-
-(arrow_function
- parameter: (identifier) @variable.parameter)
-
-(type_predicate
- name: (identifier) @variable.parameter)
-
; Special identifiers
-(type_annotation) @type
+((identifier) @type
+ (#match? @type "^[A-Z]"))
(type_identifier) @type
(predefined_type) @type.builtin
-(type_alias_declaration
- (type_identifier) @type)
-
-(type_alias_declaration
- value: (_
- (type_identifier) @type))
-
-(interface_declaration
- (type_identifier) @type)
-
-(class_declaration
- (type_identifier) @type.class)
-
-(extends_clause
- value: (identifier) @type.class)
-
-(extends_type_clause
- type: (type_identifier) @type)
-
-(implements_clause
- (type_identifier) @type)
-
([
(identifier)
(shorthand_property_identifier)
@@ -286,42 +231,8 @@
"<" @punctuation.bracket
">" @punctuation.bracket)
-(type_parameters
- "<" @punctuation.bracket
- ">" @punctuation.bracket)
-
(decorator "@" @punctuation.special)
-(union_type
- ("|") @punctuation.special)
-
-(intersection_type
- ("&") @punctuation.special)
-
-(type_annotation
- (":") @punctuation.special)
-
-(index_signature
- (":") @punctuation.special)
-
-(type_predicate_annotation
- (":") @punctuation.special)
-
-(public_field_definition
- ("?") @punctuation.special)
-
-(property_signature
- ("?") @punctuation.special)
-
-(method_signature
- ("?") @punctuation.special)
-
-(optional_parameter
- ([
- "?"
- ":"
- ]) @punctuation.special)
-
; Keywords
[ "abstract"
diff --git a/crates/languages/src/typescript/highlights.scm b/crates/languages/src/typescript/highlights.scm
index 94690ca30ece07d482593085755682e3e9abba70..5e8d55581e3ae86c85ca2b845e8a07caa6444c1d 100644
--- a/crates/languages/src/typescript/highlights.scm
+++ b/crates/languages/src/typescript/highlights.scm
@@ -4,33 +4,11 @@
; Special identifiers
-(type_annotation) @type
-
+((identifier) @type
+ (#match? @type "^[A-Z]"))
(type_identifier) @type
(predefined_type) @type.builtin
-(type_alias_declaration
- (type_identifier) @type)
-
-(type_alias_declaration
- value: (_
- (type_identifier) @type))
-
-(interface_declaration
- (type_identifier) @type)
-
-(class_declaration
- (type_identifier) @type.class)
-
-(extends_clause
- value: (identifier) @type.class)
-
-(extends_type_clause
- type: (type_identifier) @type)
-
-(implements_clause
- (type_identifier) @type)
-
;; Enables ts-pretty-errors
;; The Lsp returns "snippets" of typescript, which are not valid typescript in totality,
;; but should still be highlighted
@@ -136,40 +114,6 @@
(arrow_function) @function
-; Parameters
-
-(required_parameter
- (identifier) @variable.parameter)
-
-(required_parameter
- (_
- ([
- (identifier)
- (shorthand_property_identifier_pattern)
- ]) @variable.parameter))
-
-(optional_parameter
- (identifier) @variable.parameter)
-
-(optional_parameter
- (_
- ([
- (identifier)
- (shorthand_property_identifier_pattern)
- ]) @variable.parameter))
-
-(catch_clause
- parameter: (identifier) @variable.parameter)
-
-(index_signature
- name: (identifier) @variable.parameter)
-
-(arrow_function
- parameter: (identifier) @variable.parameter)
-
-(type_predicate
- name: (identifier) @variable.parameter)
-
; Literals
(this) @variable.special
@@ -300,42 +244,8 @@
"<" @punctuation.bracket
">" @punctuation.bracket)
-(type_parameters
- "<" @punctuation.bracket
- ">" @punctuation.bracket)
-
(decorator "@" @punctuation.special)
-(union_type
- ("|") @punctuation.special)
-
-(intersection_type
- ("&") @punctuation.special)
-
-(type_annotation
- (":") @punctuation.special)
-
-(index_signature
- (":") @punctuation.special)
-
-(type_predicate_annotation
- (":") @punctuation.special)
-
-(public_field_definition
- ("?") @punctuation.special)
-
-(property_signature
- ("?") @punctuation.special)
-
-(method_signature
- ("?") @punctuation.special)
-
-(optional_parameter
- ([
- "?"
- ":"
- ]) @punctuation.special)
-
; Keywords
[
From 20fa9983ad6e0a57efe4b530b0e8bfb57b5a7585 Mon Sep 17 00:00:00 2001
From: David Kleingeld
Date: Tue, 9 Dec 2025 18:22:16 +0100
Subject: [PATCH 17/48] Revert "gpui: Update link to Ownership and data flow
section" (#44492)
While this fixes the link in the Readme it breaks the one in the docs
which is the more important one (we should probably just duplicate the
readme and not include it into gpui.rs but that is annoying).
---
crates/gpui/README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/crates/gpui/README.md b/crates/gpui/README.md
index 30847c8b8cfb0ac5c662601acbe2008b41e42ee1..ad3fd37fc55857699f5fd23cbe4f4f088ee687c8 100644
--- a/crates/gpui/README.md
+++ b/crates/gpui/README.md
@@ -11,7 +11,7 @@ GPUI is still in active development as we work on the Zed code editor, and is st
gpui = { version = "*" }
```
- - [Ownership and data flow](src/_ownership_and_data_flow.rs)
+ - [Ownership and data flow](_ownership_and_data_flow)
Everything in GPUI starts with an `Application`. You can create one with `Application::new()`, and kick off your application by passing a callback to `Application::run()`. Inside this callback, you can create a new window with `App::open_window()`, and register your first root view. See [gpui.rs](https://www.gpui.rs/) for a complete example.
From 04d920016f2a029e7546eaa0fbf0817f9ec02b1d Mon Sep 17 00:00:00 2001
From: Julia Ryan
Date: Tue, 9 Dec 2025 09:29:40 -0800
Subject: [PATCH 18/48] Remove reqwest dependency from gpui (#44424)
This was pulling in tokio which is pretty unfortunate. The solution is
to do the `reqwest::Form` to `http::Reqwest` conversion in the
reliability crate instead of our http client wrapper.
Release Notes:
- N/A
---
Cargo.lock | 1 -
crates/http_client/Cargo.toml | 1 -
crates/http_client/src/async_body.rs | 11 --------
crates/http_client/src/http_client.rs | 29 +--------------------
crates/reqwest_client/src/reqwest_client.rs | 20 --------------
crates/zed/src/reliability.rs | 15 ++++++-----
6 files changed, 10 insertions(+), 67 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 5790df07111b0752321c9d29da61a16839236275..49b9d6069ccdfadf2d5145808fd7b758f9b389bb 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -7756,7 +7756,6 @@ dependencies = [
"tempfile",
"url",
"util",
- "zed-reqwest",
]
[[package]]
diff --git a/crates/http_client/Cargo.toml b/crates/http_client/Cargo.toml
index 16600627a77f6a73fa913340f29f5a2da0875de9..177f8639ca1a5d75bd0130979f4d550e3622a1b4 100644
--- a/crates/http_client/Cargo.toml
+++ b/crates/http_client/Cargo.toml
@@ -28,7 +28,6 @@ http-body.workspace = true
http.workspace = true
log.workspace = true
parking_lot.workspace = true
-reqwest.workspace = true
serde.workspace = true
serde_json.workspace = true
serde_urlencoded.workspace = true
diff --git a/crates/http_client/src/async_body.rs b/crates/http_client/src/async_body.rs
index 6b99a54a7d941c290f2680bc2a599bc63251e24b..8fb49f218568ea36078d772a7225229f31a916c4 100644
--- a/crates/http_client/src/async_body.rs
+++ b/crates/http_client/src/async_body.rs
@@ -88,17 +88,6 @@ impl From<&'static str> for AsyncBody {
}
}
-impl TryFrom for AsyncBody {
- type Error = anyhow::Error;
-
- fn try_from(value: reqwest::Body) -> Result {
- value
- .as_bytes()
- .ok_or_else(|| anyhow::anyhow!("Underlying data is a stream"))
- .map(|bytes| Self::from_bytes(Bytes::copy_from_slice(bytes)))
- }
-}
-
impl> From> for AsyncBody {
fn from(body: Option) -> Self {
match body {
diff --git a/crates/http_client/src/http_client.rs b/crates/http_client/src/http_client.rs
index f357e01da062398d18134df6625d30b8129bf875..1182ef74ca3d59a2d59419e185ff5bd673c5d505 100644
--- a/crates/http_client/src/http_client.rs
+++ b/crates/http_client/src/http_client.rs
@@ -8,10 +8,7 @@ use derive_more::Deref;
use http::HeaderValue;
pub use http::{self, Method, Request, Response, StatusCode, Uri, request::Builder};
-use futures::{
- FutureExt as _,
- future::{self, BoxFuture},
-};
+use futures::future::BoxFuture;
use parking_lot::Mutex;
use serde::Serialize;
use std::sync::Arc;
@@ -110,14 +107,6 @@ pub trait HttpClient: 'static + Send + Sync {
fn as_fake(&self) -> &FakeHttpClient {
panic!("called as_fake on {}", type_name::())
}
-
- fn send_multipart_form<'a>(
- &'a self,
- _url: &str,
- _request: reqwest::multipart::Form,
- ) -> BoxFuture<'a, anyhow::Result>> {
- future::ready(Err(anyhow!("not implemented"))).boxed()
- }
}
/// An [`HttpClient`] that may have a proxy.
@@ -165,14 +154,6 @@ impl HttpClient for HttpClientWithProxy {
fn as_fake(&self) -> &FakeHttpClient {
self.client.as_fake()
}
-
- fn send_multipart_form<'a>(
- &'a self,
- url: &str,
- form: reqwest::multipart::Form,
- ) -> BoxFuture<'a, anyhow::Result>> {
- self.client.send_multipart_form(url, form)
- }
}
/// An [`HttpClient`] that has a base URL.
@@ -306,14 +287,6 @@ impl HttpClient for HttpClientWithUrl {
fn as_fake(&self) -> &FakeHttpClient {
self.client.as_fake()
}
-
- fn send_multipart_form<'a>(
- &'a self,
- url: &str,
- request: reqwest::multipart::Form,
- ) -> BoxFuture<'a, anyhow::Result>> {
- self.client.send_multipart_form(url, request)
- }
}
pub fn read_proxy_from_env() -> Option {
diff --git a/crates/reqwest_client/src/reqwest_client.rs b/crates/reqwest_client/src/reqwest_client.rs
index 4213a239ec813f255139a97770a74608371fb73e..8a1ee45e1cc5364600342d587e6b8c084b5d195a 100644
--- a/crates/reqwest_client/src/reqwest_client.rs
+++ b/crates/reqwest_client/src/reqwest_client.rs
@@ -270,26 +270,6 @@ impl http_client::HttpClient for ReqwestClient {
}
.boxed()
}
-
- fn send_multipart_form<'a>(
- &'a self,
- url: &str,
- form: reqwest::multipart::Form,
- ) -> futures::future::BoxFuture<'a, anyhow::Result>>
- {
- let response = self.client.post(url).multipart(form).send();
- self.handle
- .spawn(async move {
- let response = response.await?;
- let mut builder = http::response::Builder::new().status(response.status());
- for (k, v) in response.headers() {
- builder = builder.header(k, v)
- }
- Ok(builder.body(response.bytes().await?.into())?)
- })
- .map(|e| e?)
- .boxed()
- }
}
#[cfg(test)]
diff --git a/crates/zed/src/reliability.rs b/crates/zed/src/reliability.rs
index 9d2f7f5da021cda38cef5a205f2d2ec77eb2b386..da8dffa85d57162a62dd6ae0a698d975d22ee374 100644
--- a/crates/zed/src/reliability.rs
+++ b/crates/zed/src/reliability.rs
@@ -1,8 +1,8 @@
use anyhow::{Context as _, Result};
use client::{Client, telemetry::MINIDUMP_ENDPOINT};
-use futures::AsyncReadExt;
+use futures::{AsyncReadExt, TryStreamExt};
use gpui::{App, AppContext as _, SerializedThreadTaskTimings};
-use http_client::{self, HttpClient};
+use http_client::{self, AsyncBody, HttpClient, Request};
use log::info;
use project::Project;
use proto::{CrashReport, GetCrashFilesResponse};
@@ -296,11 +296,14 @@ async fn upload_minidump(
// TODO: feature-flag-context, and more of device-context like screen resolution, available ram, device model, etc
+ let stream = form
+ .into_stream()
+ .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))
+ .into_async_read();
+ let body = AsyncBody::from_reader(stream);
+ let req = Request::builder().uri(endpoint).body(body)?;
let mut response_text = String::new();
- let mut response = client
- .http_client()
- .send_multipart_form(endpoint, form)
- .await?;
+ let mut response = client.http_client().send(req).await?;
response
.body_mut()
.read_to_string(&mut response_text)
From bfab0b71e0a92cd75d309909fd247aadc3a8a7e0 Mon Sep 17 00:00:00 2001
From: Bennet Bo Fenner
Date: Tue, 9 Dec 2025 19:55:29 +0100
Subject: [PATCH 19/48] agent_ui: Fix panic in message editor (#44493)
Release Notes:
- N/A
---
crates/agent_ui/src/acp/message_editor.rs | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/crates/agent_ui/src/acp/message_editor.rs b/crates/agent_ui/src/acp/message_editor.rs
index a0aca0c51bd0afe1ea61f6a58c3585d68172ec24..bc293c98a84540c1e00d9895be2cc05b0bdd08a5 100644
--- a/crates/agent_ui/src/acp/message_editor.rs
+++ b/crates/agent_ui/src/acp/message_editor.rs
@@ -567,6 +567,14 @@ impl MessageEditor {
{
cx.stop_propagation();
+ let insertion_target = self
+ .editor
+ .read(cx)
+ .selections
+ .newest_anchor()
+ .start
+ .text_anchor;
+
let project = workspace.read(cx).project().clone();
for selection in selections {
if let (Some(file_path), Some(line_range)) =
@@ -587,8 +595,7 @@ impl MessageEditor {
let snapshot = buffer.snapshot(cx);
let (excerpt_id, _, buffer_snapshot) =
snapshot.as_singleton().unwrap();
- let start_offset = buffer_snapshot.len();
- let text_anchor = buffer_snapshot.anchor_before(start_offset);
+ let text_anchor = insertion_target.bias_left(&buffer_snapshot);
editor.insert(&mention_text, window, cx);
editor.insert(" ", window, cx);
From 5dd8561b06c8af4ee46f3aa8bcf839f208b8c7bf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Peter=20K=C3=B6nig?=
Date: Tue, 9 Dec 2025 20:54:16 +0100
Subject: [PATCH 20/48] Fix DeepSeek Reasoner tool-call handling and add
reasoning_content support (#44301)
## Closes #43887
## Release Notes:
### Problem
DeepSeek's reasoning mode API requires `reasoning_content` to be
included in assistant messages that precede tool calls. Without it, the
API returns a 400 error:
```
Missing `reasoning_content` field in the assistant message at message index 2
```
### Added/Fixed/Improved
- Add `reasoning_content` field to `RequestMessage::Assistant` in
`crates/deepseek/src/deepseek.rs`
- Accumulate thinking content from `MessageContent::Thinking` and attach
it to the next assistant/tool-call message
- Wire reasoning content through the language model provider in
`crates/language_models/src/provider/deepseek.rs`
### Testing
- Verified with DeepSeek Reasoner model using tool calls
- Confirmed reasoning content is properly included in API requests
Fixes tool-call errors when using DeepSeek's reasoning mode.
---------
Co-authored-by: Ben Brandt
---
crates/deepseek/src/deepseek.rs | 2 ++
crates/language_models/src/provider/deepseek.rs | 11 +++++++++--
2 files changed, 11 insertions(+), 2 deletions(-)
diff --git a/crates/deepseek/src/deepseek.rs b/crates/deepseek/src/deepseek.rs
index 64a1cbe5d96354260c2bf84a43ed70be7336aa7a..e978aa08048bfa4c7b7b203ce6b405ba8a0a7d0c 100644
--- a/crates/deepseek/src/deepseek.rs
+++ b/crates/deepseek/src/deepseek.rs
@@ -155,6 +155,8 @@ pub enum RequestMessage {
content: Option,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
tool_calls: Vec,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ reasoning_content: Option,
},
User {
content: String,
diff --git a/crates/language_models/src/provider/deepseek.rs b/crates/language_models/src/provider/deepseek.rs
index 4bc7164f421bfbaa075c72faff7f731c0defcdba..91b83bb9f1d0f08fe70f5e750ff8ce993a7afd7f 100644
--- a/crates/language_models/src/provider/deepseek.rs
+++ b/crates/language_models/src/provider/deepseek.rs
@@ -332,9 +332,11 @@ pub fn into_deepseek(
model: &deepseek::Model,
max_output_tokens: Option,
) -> deepseek::Request {
- let is_reasoner = *model == deepseek::Model::Reasoner;
+ let is_reasoner = model == &deepseek::Model::Reasoner;
let mut messages = Vec::new();
+ let mut current_reasoning: Option = None;
+
for message in request.messages {
for content in message.content {
match content {
@@ -343,10 +345,14 @@ pub fn into_deepseek(
Role::Assistant => deepseek::RequestMessage::Assistant {
content: Some(text),
tool_calls: Vec::new(),
+ reasoning_content: current_reasoning.take(),
},
Role::System => deepseek::RequestMessage::System { content: text },
}),
- MessageContent::Thinking { .. } => {}
+ MessageContent::Thinking { text, .. } => {
+ // Accumulate reasoning content for next assistant message
+ current_reasoning.get_or_insert_default().push_str(&text);
+ }
MessageContent::RedactedThinking(_) => {}
MessageContent::Image(_) => {}
MessageContent::ToolUse(tool_use) => {
@@ -369,6 +375,7 @@ pub fn into_deepseek(
messages.push(deepseek::RequestMessage::Assistant {
content: None,
tool_calls: vec![tool_call],
+ reasoning_content: current_reasoning.take(),
});
}
}
From 3180f4447743426cf2227ed1ebf37cf8edced5d3 Mon Sep 17 00:00:00 2001
From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
Date: Tue, 9 Dec 2025 21:37:39 +0100
Subject: [PATCH 21/48] lsp: Do not drop lsp buffer handle from editor when a
language change leads to buffer having a legit language (#44469)
Fixes a bug that led to us unnecessarily restarting a language server
when we were looking at a single file of a given language.
Release Notes:
- Fixed a bug that led to Zed sometimes starting an excessive amount of
language servers
---
crates/copilot/src/copilot.rs | 2 +-
crates/editor/src/editor.rs | 6 ++++--
crates/git_ui/src/file_diff_view.rs | 2 +-
crates/git_ui/src/text_diff_view.rs | 2 +-
crates/language/src/buffer.rs | 13 ++++++++-----
crates/multi_buffer/src/multi_buffer.rs | 6 ++++--
crates/project/src/buffer_store.rs | 2 +-
crates/project/src/git_store.rs | 2 +-
crates/project/src/lsp_store.rs | 2 +-
crates/toolchain_selector/src/active_toolchain.rs | 2 +-
10 files changed, 23 insertions(+), 16 deletions(-)
diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs
index 6fbdeff807b65d22193ba7fdcb8e990f7184f70e..4e6520906074c1384a4e500d89be43659c162718 100644
--- a/crates/copilot/src/copilot.rs
+++ b/crates/copilot/src/copilot.rs
@@ -807,7 +807,7 @@ impl Copilot {
.ok();
}
language::BufferEvent::FileHandleChanged
- | language::BufferEvent::LanguageChanged => {
+ | language::BufferEvent::LanguageChanged(_) => {
let new_language_id = id_for_language(buffer.read(cx).language());
let Ok(new_uri) = uri_for_buffer(&buffer, cx) else {
return Ok(());
diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs
index 023e4931d33be86c70b90a8cd62aa5692c25c9d9..d841bf858b8a77f502b8bfb2499118f9e714572e 100644
--- a/crates/editor/src/editor.rs
+++ b/crates/editor/src/editor.rs
@@ -22007,8 +22007,10 @@ impl Editor {
multi_buffer::Event::DiffHunksToggled => {
self.tasks_update_task = Some(self.refresh_runnables(window, cx));
}
- multi_buffer::Event::LanguageChanged(buffer_id) => {
- self.registered_buffers.remove(&buffer_id);
+ multi_buffer::Event::LanguageChanged(buffer_id, is_fresh_language) => {
+ if !is_fresh_language {
+ self.registered_buffers.remove(&buffer_id);
+ }
jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
cx.emit(EditorEvent::Reparsed(*buffer_id));
cx.notify();
diff --git a/crates/git_ui/src/file_diff_view.rs b/crates/git_ui/src/file_diff_view.rs
index e6ed8feb7f69493d3731d9d382cf9b955059fcc4..b020d7a9f3ac083f1a5adf15ca298b55063a3eb8 100644
--- a/crates/git_ui/src/file_diff_view.rs
+++ b/crates/git_ui/src/file_diff_view.rs
@@ -108,7 +108,7 @@ impl FileDiffView {
for buffer in [&old_buffer, &new_buffer] {
cx.subscribe(buffer, move |this, _, event, _| match event {
language::BufferEvent::Edited
- | language::BufferEvent::LanguageChanged
+ | language::BufferEvent::LanguageChanged(_)
| language::BufferEvent::Reparsed => {
this.buffer_changes_tx.send(()).ok();
}
diff --git a/crates/git_ui/src/text_diff_view.rs b/crates/git_ui/src/text_diff_view.rs
index 5a8f0f79592f9161ae9c7ed7f0dc2814eacc2e53..56d55415ba01f893453824be00b9eb8d6bd31a90 100644
--- a/crates/git_ui/src/text_diff_view.rs
+++ b/crates/git_ui/src/text_diff_view.rs
@@ -170,7 +170,7 @@ impl TextDiffView {
cx.subscribe(&source_buffer, move |this, _, event, _| match event {
language::BufferEvent::Edited
- | language::BufferEvent::LanguageChanged
+ | language::BufferEvent::LanguageChanged(_)
| language::BufferEvent::Reparsed => {
this.buffer_changes_tx.send(()).ok();
}
diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs
index 7166a01ef64bff9e47c70cac47910f714ae2dc39..7bf62b5aa43c60a7ecee756dd66066682ac09077 100644
--- a/crates/language/src/buffer.rs
+++ b/crates/language/src/buffer.rs
@@ -1,8 +1,8 @@
pub mod row_chunk;
use crate::{
- DebuggerTextObject, LanguageScope, Outline, OutlineConfig, RunnableCapture, RunnableTag,
- TextObject, TreeSitterOptions,
+ DebuggerTextObject, LanguageScope, Outline, OutlineConfig, PLAIN_TEXT, RunnableCapture,
+ RunnableTag, TextObject, TreeSitterOptions,
diagnostic_set::{DiagnosticEntry, DiagnosticEntryRef, DiagnosticGroup},
language_settings::{LanguageSettings, language_settings},
outline::OutlineItem,
@@ -353,7 +353,8 @@ pub enum BufferEvent {
/// The buffer is in need of a reload
ReloadNeeded,
/// The buffer's language was changed.
- LanguageChanged,
+ /// The boolean indicates whether this buffer did not have a language before, but does now.
+ LanguageChanged(bool),
/// The buffer's syntax trees were updated.
Reparsed,
/// The buffer's diagnostics were updated.
@@ -1386,10 +1387,12 @@ impl Buffer {
) {
self.non_text_state_update_count += 1;
self.syntax_map.lock().clear(&self.text);
- self.language = language;
+ let old_language = std::mem::replace(&mut self.language, language);
self.was_changed();
self.reparse(cx, may_block);
- cx.emit(BufferEvent::LanguageChanged);
+ let has_fresh_language =
+ self.language.is_some() && old_language.is_none_or(|old| old == *PLAIN_TEXT);
+ cx.emit(BufferEvent::LanguageChanged(has_fresh_language));
}
/// Assign a language registry to the buffer. This allows the buffer to retrieve
diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs
index bd163557c4f6239353e7cd5ad08a6120e20e4a3d..442abe78ee65ba91ccf8e03ab3c0ad26f3679cfc 100644
--- a/crates/multi_buffer/src/multi_buffer.rs
+++ b/crates/multi_buffer/src/multi_buffer.rs
@@ -129,7 +129,7 @@ pub enum Event {
transaction_id: TransactionId,
},
Reloaded,
- LanguageChanged(BufferId),
+ LanguageChanged(BufferId, bool),
Reparsed(BufferId),
Saved,
FileHandleChanged,
@@ -2294,7 +2294,9 @@ impl MultiBuffer {
BufferEvent::Saved => Event::Saved,
BufferEvent::FileHandleChanged => Event::FileHandleChanged,
BufferEvent::Reloaded => Event::Reloaded,
- BufferEvent::LanguageChanged => Event::LanguageChanged(buffer_id),
+ BufferEvent::LanguageChanged(has_language) => {
+ Event::LanguageChanged(buffer_id, *has_language)
+ }
BufferEvent::Reparsed => Event::Reparsed(buffer_id),
BufferEvent::DiagnosticsUpdated => Event::DiagnosticsUpdated,
BufferEvent::CapabilityChanged => {
diff --git a/crates/project/src/buffer_store.rs b/crates/project/src/buffer_store.rs
index f4a0d45bc86c39be595439bfe1aebb2533b62783..c38b898f5d79cf34563daa9bc7563f3c869d9a70 100644
--- a/crates/project/src/buffer_store.rs
+++ b/crates/project/src/buffer_store.rs
@@ -1141,7 +1141,7 @@ impl BufferStore {
})
.log_err();
}
- BufferEvent::LanguageChanged => {}
+ BufferEvent::LanguageChanged(_) => {}
_ => {}
}
}
diff --git a/crates/project/src/git_store.rs b/crates/project/src/git_store.rs
index 0b74a04e1db5c0f2b7c8934d1bbe7d38b1d1ad1b..3efbb57e0312dc7e07d0dbed69f5e096a2e52eb3 100644
--- a/crates/project/src/git_store.rs
+++ b/crates/project/src/git_store.rs
@@ -1451,7 +1451,7 @@ impl GitStore {
match event {
BufferStoreEvent::BufferAdded(buffer) => {
cx.subscribe(buffer, |this, buffer, event, cx| {
- if let BufferEvent::LanguageChanged = event {
+ if let BufferEvent::LanguageChanged(_) = event {
let buffer_id = buffer.read(cx).remote_id();
if let Some(diff_state) = this.diffs.get(&buffer_id) {
diff_state.update(cx, |diff_state, cx| {
diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs
index 1ae6d1295f37df31aac03e2019cb5510c836fb1c..6856c0ba49da63888cdd81015ca7f725ca3cb81f 100644
--- a/crates/project/src/lsp_store.rs
+++ b/crates/project/src/lsp_store.rs
@@ -219,7 +219,7 @@ struct UnifiedLanguageServer {
project_roots: HashSet>,
}
-#[derive(Clone, Hash, PartialEq, Eq)]
+#[derive(Clone, Debug, Hash, PartialEq, Eq)]
struct LanguageServerSeed {
worktree_id: WorktreeId,
name: LanguageServerName,
diff --git a/crates/toolchain_selector/src/active_toolchain.rs b/crates/toolchain_selector/src/active_toolchain.rs
index 122aa9f22b74c33dd8f148f2bf3b65f04da478a9..03c152e3fd3df0c62ab2f5c7e4a4746875ac955a 100644
--- a/crates/toolchain_selector/src/active_toolchain.rs
+++ b/crates/toolchain_selector/src/active_toolchain.rs
@@ -124,7 +124,7 @@ impl ActiveToolchain {
&buffer,
window,
|this, _, event: &BufferEvent, window, cx| {
- if matches!(event, BufferEvent::LanguageChanged) {
+ if matches!(event, BufferEvent::LanguageChanged(_)) {
this._update_toolchain_task = Self::spawn_tracker_task(window, cx);
}
},
From 736a71238755603e30fb8062444b830911fba704 Mon Sep 17 00:00:00 2001
From: David Kleingeld
Date: Wed, 10 Dec 2025 00:30:36 +0100
Subject: [PATCH 22/48] Handle response error for ashpd fixing login edgecases
(#44502)
Release Notes:
- Fixed login fallbacks on Linux
Co-authored-by: Julia Ryan
---
crates/gpui/src/platform/linux/platform.rs | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/crates/gpui/src/platform/linux/platform.rs b/crates/gpui/src/platform/linux/platform.rs
index f5056741df016cfe88c83379d7b1afd85b9900ca..51a1d5f5849d387a3f5855c12f50fce0a95d1cf4 100644
--- a/crates/gpui/src/platform/linux/platform.rs
+++ b/crates/gpui/src/platform/linux/platform.rs
@@ -649,8 +649,9 @@ pub(super) fn open_uri_internal(
.activation_token(activation_token.clone().map(ashpd::ActivationToken::from))
.send_uri(&uri)
.await
+ .and_then(|e| e.response())
{
- Ok(_) => return,
+ Ok(()) => return,
Err(e) => log::error!("Failed to open with dbus: {}", e),
}
From 4353b8ecd5da2ddcc3497e99d84fe75cf95e843d Mon Sep 17 00:00:00 2001
From: Julia Ryan
Date: Tue, 9 Dec 2025 16:42:19 -0800
Subject: [PATCH 23/48] Fix `--user-data-dir` (#44235)
Closes #40067
Release Notes:
- The `--user-data-dir` flag now works on Windows and Linux, as well as
macOS if you pass `--foreground`.
---------
Co-authored-by: Lukas Wirth
---
crates/cli/src/main.rs | 49 +++++++++++++++++++++++++++++-------------
crates/zed/src/main.rs | 2 +-
2 files changed, 35 insertions(+), 16 deletions(-)
diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs
index 7988f001dab37858d36f791fa8a184fe329c4be5..92c0ce2377b8c200b2367148226f3bd3b81f0008 100644
--- a/crates/cli/src/main.rs
+++ b/crates/cli/src/main.rs
@@ -32,7 +32,7 @@ struct Detect;
trait InstalledApp {
fn zed_version_string(&self) -> String;
- fn launch(&self, ipc_url: String) -> anyhow::Result<()>;
+ fn launch(&self, ipc_url: String, user_data_dir: Option<&str>) -> anyhow::Result<()>;
fn run_foreground(
&self,
ipc_url: String,
@@ -588,7 +588,7 @@ fn main() -> Result<()> {
if args.foreground {
app.run_foreground(url, user_data_dir.as_deref())?;
} else {
- app.launch(url)?;
+ app.launch(url, user_data_dir.as_deref())?;
sender.join().unwrap()?;
if let Some(handle) = stdin_pipe_handle {
handle.join().unwrap()?;
@@ -709,14 +709,18 @@ mod linux {
)
}
- fn launch(&self, ipc_url: String) -> anyhow::Result<()> {
- let sock_path = paths::data_dir().join(format!(
+ fn launch(&self, ipc_url: String, user_data_dir: Option<&str>) -> anyhow::Result<()> {
+ let data_dir = user_data_dir
+ .map(PathBuf::from)
+ .unwrap_or_else(|| paths::data_dir().clone());
+
+ let sock_path = data_dir.join(format!(
"zed-{}.sock",
*release_channel::RELEASE_CHANNEL_NAME
));
let sock = UnixDatagram::unbound()?;
if sock.connect(&sock_path).is_err() {
- self.boot_background(ipc_url)?;
+ self.boot_background(ipc_url, user_data_dir)?;
} else {
sock.send(ipc_url.as_bytes())?;
}
@@ -742,7 +746,11 @@ mod linux {
}
impl App {
- fn boot_background(&self, ipc_url: String) -> anyhow::Result<()> {
+ fn boot_background(
+ &self,
+ ipc_url: String,
+ user_data_dir: Option<&str>,
+ ) -> anyhow::Result<()> {
let path = &self.0;
match fork::fork() {
@@ -756,8 +764,13 @@ mod linux {
if fork::close_fd().is_err() {
eprintln!("failed to close_fd: {}", std::io::Error::last_os_error());
}
- let error =
- exec::execvp(path.clone(), &[path.as_os_str(), &OsString::from(ipc_url)]);
+ let mut args: Vec =
+ vec![path.as_os_str().to_owned(), OsString::from(ipc_url)];
+ if let Some(dir) = user_data_dir {
+ args.push(OsString::from("--user-data-dir"));
+ args.push(OsString::from(dir));
+ }
+ let error = exec::execvp(path.clone(), &args);
// if exec succeeded, we never get here.
eprintln!("failed to exec {:?}: {}", path, error);
process::exit(1)
@@ -943,11 +956,14 @@ mod windows {
)
}
- fn launch(&self, ipc_url: String) -> anyhow::Result<()> {
+ fn launch(&self, ipc_url: String, user_data_dir: Option<&str>) -> anyhow::Result<()> {
if check_single_instance() {
- std::process::Command::new(self.0.clone())
- .arg(ipc_url)
- .spawn()?;
+ let mut cmd = std::process::Command::new(self.0.clone());
+ cmd.arg(ipc_url);
+ if let Some(dir) = user_data_dir {
+ cmd.arg("--user-data-dir").arg(dir);
+ }
+ cmd.spawn()?;
} else {
unsafe {
let pipe = CreateFileW(
@@ -1096,7 +1112,7 @@ mod mac_os {
format!("Zed {} – {}", self.version(), self.path().display(),)
}
- fn launch(&self, url: String) -> anyhow::Result<()> {
+ fn launch(&self, url: String, user_data_dir: Option<&str>) -> anyhow::Result<()> {
match self {
Self::App { app_bundle, .. } => {
let app_path = app_bundle;
@@ -1146,8 +1162,11 @@ mod mac_os {
format!("Cloning descriptor for file {subprocess_stdout_file:?}")
})?;
let mut command = std::process::Command::new(executable);
- let command = command
- .env(FORCE_CLI_MODE_ENV_VAR_NAME, "")
+ command.env(FORCE_CLI_MODE_ENV_VAR_NAME, "");
+ if let Some(dir) = user_data_dir {
+ command.arg("--user-data-dir").arg(dir);
+ }
+ command
.stderr(subprocess_stdout_file)
.stdout(subprocess_stdin_file)
.arg(url);
diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs
index 0e82b3323b36f4845b584b33f65e440963801a9f..2e85da2c0622305c4244d7cc15885e0bb4111aa5 100644
--- a/crates/zed/src/main.rs
+++ b/crates/zed/src/main.rs
@@ -3,7 +3,7 @@ mod zed;
use agent_ui::AgentPanel;
use anyhow::{Context as _, Error, Result};
-use clap::{Parser, command};
+use clap::Parser;
use cli::FORCE_CLI_MODE_ENV_VAR_NAME;
use client::{Client, ProxySettings, UserStore, parse_zed_link};
use collab_ui::channel_view::ChannelView;
From 728f09f3f4398191c9c4dc9492be3dcfa9076708 Mon Sep 17 00:00:00 2001
From: Mayank Verma
Date: Wed, 10 Dec 2025 10:04:24 +0530
Subject: [PATCH 24/48] vim: Fix buffer navigation with non-Editor items
(#44350)
Closes #44348
Release Notes:
- Fixed buffer navigation in Vim mode with non-Editor items
---
assets/keymaps/vim.json | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json
index 5ff1dc196a82d0c3226253c4b8d892058598b4e3..b891baf2594cd400583686292d7cb648d8f3666d 100644
--- a/assets/keymaps/vim.json
+++ b/assets/keymaps/vim.json
@@ -902,7 +902,11 @@
"context": "!Editor && !Terminal",
"bindings": {
":": "command_palette::Toggle",
- "g /": "pane::DeploySearch"
+ "g /": "pane::DeploySearch",
+ "] b": "pane::ActivateNextItem",
+ "[ b": "pane::ActivatePreviousItem",
+ "] shift-b": "pane::ActivateLastItem",
+ "[ shift-b": ["pane::ActivateItem", 0]
}
},
{
From 7cbe25fda531f601a50b93e0323818b62b16f5ec Mon Sep 17 00:00:00 2001
From: Mayank Verma
Date: Wed, 10 Dec 2025 10:05:28 +0530
Subject: [PATCH 25/48] vim: Fix editor paste not using clipboard in visual
mode (#44347)
Closes #44178
Release Notes:
- Fixed editor paste not using clipboard when in Vim visual mode
---
crates/vim/src/normal/paste.rs | 46 ++++++++++++++++++++++++++++++++++
crates/vim/src/vim.rs | 1 +
2 files changed, 47 insertions(+)
diff --git a/crates/vim/src/normal/paste.rs b/crates/vim/src/normal/paste.rs
index 978c882f059e1f4cf40089de4a4af746d8526b54..82af828deb85e6e0ef36ea2853a251547051feed 100644
--- a/crates/vim/src/normal/paste.rs
+++ b/crates/vim/src/normal/paste.rs
@@ -773,6 +773,52 @@ mod test {
"});
}
+ #[gpui::test]
+ async fn test_paste_system_clipboard_never(cx: &mut gpui::TestAppContext) {
+ let mut cx = VimTestContext::new(cx, true).await;
+
+ cx.update_global(|store: &mut SettingsStore, cx| {
+ store.update_user_settings(cx, |s| {
+ s.vim.get_or_insert_default().use_system_clipboard = Some(UseSystemClipboard::Never)
+ });
+ });
+
+ cx.set_state(
+ indoc! {"
+ ˇThe quick brown
+ fox jumps over
+ the lazy dog"},
+ Mode::Normal,
+ );
+
+ cx.write_to_clipboard(ClipboardItem::new_string("something else".to_string()));
+
+ cx.simulate_keystrokes("d d");
+ cx.assert_state(
+ indoc! {"
+ ˇfox jumps over
+ the lazy dog"},
+ Mode::Normal,
+ );
+
+ cx.simulate_keystrokes("shift-v p");
+ cx.assert_state(
+ indoc! {"
+ ˇThe quick brown
+ the lazy dog"},
+ Mode::Normal,
+ );
+
+ cx.simulate_keystrokes("shift-v");
+ cx.dispatch_action(editor::actions::Paste);
+ cx.assert_state(
+ indoc! {"
+ ˇsomething else
+ the lazy dog"},
+ Mode::Normal,
+ );
+ }
+
#[gpui::test]
async fn test_numbered_registers(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs
index 1ffcf7e2224341affc7498032fd5a181e256943d..9a9a1a001c32fcf8b22892ce5300d8d2aec3dd37 100644
--- a/crates/vim/src/vim.rs
+++ b/crates/vim/src/vim.rs
@@ -924,6 +924,7 @@ impl Vim {
|vim, _: &editor::actions::Paste, window, cx| match vim.mode {
Mode::Replace => vim.paste_replace(window, cx),
Mode::Visual | Mode::VisualLine | Mode::VisualBlock => {
+ vim.selected_register.replace('+');
vim.paste(&VimPaste::default(), window, cx);
}
_ => {
From 22f1655f8f60ba4fca00a9d197d449e94454835c Mon Sep 17 00:00:00 2001
From: Conrad Irwin
Date: Wed, 10 Dec 2025 00:07:48 -0700
Subject: [PATCH 26/48] Add history to the command palette (#44517)
Co-Authored-By: Claude
Closes #ISSUE
Release Notes:
- Added history to the command palette (`up` will now show recently
executed
commands). This is particularly helpful in vim mode when you may mistype
a
complicated command and want to re-run a slightly different version
thereof.
---------
Co-authored-by: Claude
---
crates/command_palette/src/command_palette.rs | 399 +++++++++++++++++-
crates/command_palette/src/persistence.rs | 10 +
crates/picker/src/picker.rs | 28 ++
3 files changed, 434 insertions(+), 3 deletions(-)
diff --git a/crates/command_palette/src/command_palette.rs b/crates/command_palette/src/command_palette.rs
index d971bca1f01e878d7517c1d13f525dfbf8e47afa..daf97bf676e27b5dd81ce4882c102dbfdefc502a 100644
--- a/crates/command_palette/src/command_palette.rs
+++ b/crates/command_palette/src/command_palette.rs
@@ -2,7 +2,7 @@ mod persistence;
use std::{
cmp::{self, Reverse},
- collections::HashMap,
+ collections::{HashMap, VecDeque},
sync::Arc,
time::Duration,
};
@@ -19,6 +19,7 @@ use gpui::{
ParentElement, Render, Styled, Task, WeakEntity, Window,
};
use persistence::COMMAND_PALETTE_HISTORY;
+use picker::Direction;
use picker::{Picker, PickerDelegate};
use postage::{sink::Sink, stream::Stream};
use settings::Settings;
@@ -163,6 +164,7 @@ pub struct CommandPaletteDelegate {
Task<()>,
postage::dispatch::Receiver<(Vec, Vec, CommandInterceptResult)>,
)>,
+ query_history: QueryHistory,
}
struct Command {
@@ -170,6 +172,91 @@ struct Command {
action: Box,
}
+#[derive(Default)]
+struct QueryHistory {
+ history: Option>,
+ cursor: Option,
+ prefix: Option,
+}
+
+impl QueryHistory {
+ fn history(&mut self) -> &mut VecDeque {
+ self.history.get_or_insert_with(|| {
+ COMMAND_PALETTE_HISTORY
+ .list_recent_queries()
+ .unwrap_or_default()
+ .into_iter()
+ .collect()
+ })
+ }
+
+ fn add(&mut self, query: String) {
+ if let Some(pos) = self.history().iter().position(|h| h == &query) {
+ self.history().remove(pos);
+ }
+ self.history().push_back(query);
+ self.cursor = None;
+ self.prefix = None;
+ }
+
+ fn validate_cursor(&mut self, current_query: &str) -> Option {
+ if let Some(pos) = self.cursor {
+ if self.history().get(pos).map(|s| s.as_str()) != Some(current_query) {
+ self.cursor = None;
+ self.prefix = None;
+ }
+ }
+ self.cursor
+ }
+
+ fn previous(&mut self, current_query: &str) -> Option<&str> {
+ if self.validate_cursor(current_query).is_none() {
+ self.prefix = Some(current_query.to_string());
+ }
+
+ let prefix = self.prefix.clone().unwrap_or_default();
+ let start_index = self.cursor.unwrap_or(self.history().len());
+
+ for i in (0..start_index).rev() {
+ if self
+ .history()
+ .get(i)
+ .is_some_and(|e| e.starts_with(&prefix))
+ {
+ self.cursor = Some(i);
+ return self.history().get(i).map(|s| s.as_str());
+ }
+ }
+ None
+ }
+
+ fn next(&mut self, current_query: &str) -> Option<&str> {
+ let selected = self.validate_cursor(current_query)?;
+ let prefix = self.prefix.clone().unwrap_or_default();
+
+ for i in (selected + 1)..self.history().len() {
+ if self
+ .history()
+ .get(i)
+ .is_some_and(|e| e.starts_with(&prefix))
+ {
+ self.cursor = Some(i);
+ return self.history().get(i).map(|s| s.as_str());
+ }
+ }
+ None
+ }
+
+ fn reset_cursor(&mut self) {
+ self.cursor = None;
+ self.prefix = None;
+ }
+
+ fn is_navigating(&self) -> bool {
+ self.cursor.is_some()
+ }
+}
+
impl Clone for Command {
fn clone(&self) -> Self {
Self {
@@ -196,6 +283,7 @@ impl CommandPaletteDelegate {
previous_focus_handle,
latest_query: String::new(),
updating_matches: None,
+ query_history: Default::default(),
}
}
@@ -271,6 +359,11 @@ impl CommandPaletteDelegate {
// so we need to return an Option here
self.commands.get(action_ix)
}
+
+ #[cfg(any(test, feature = "test-support"))]
+ pub fn seed_history(&mut self, queries: &[&str]) {
+ self.query_history.history = Some(queries.iter().map(|s| s.to_string()).collect());
+ }
}
impl PickerDelegate for CommandPaletteDelegate {
@@ -280,6 +373,38 @@ impl PickerDelegate for CommandPaletteDelegate {
"Execute a command...".into()
}
+ fn select_history(
+ &mut self,
+ direction: Direction,
+ query: &str,
+ _window: &mut Window,
+ _cx: &mut App,
+ ) -> Option {
+ match direction {
+ Direction::Up => {
+ let should_use_history =
+ self.selected_ix == 0 || self.query_history.is_navigating();
+ if should_use_history {
+ if let Some(query) = self.query_history.previous(query).map(|s| s.to_string()) {
+ return Some(query);
+ }
+ }
+ }
+ Direction::Down => {
+ if self.query_history.is_navigating() {
+ if let Some(query) = self.query_history.next(query).map(|s| s.to_string()) {
+ return Some(query);
+ } else {
+ let prefix = self.query_history.prefix.take().unwrap_or_default();
+ self.query_history.reset_cursor();
+ return Some(prefix);
+ }
+ }
+ }
+ }
+ None
+ }
+
fn match_count(&self) -> usize {
self.matches.len()
}
@@ -439,6 +564,12 @@ impl PickerDelegate for CommandPaletteDelegate {
self.dismissed(window, cx);
return;
}
+
+ if !self.latest_query.is_empty() {
+ self.query_history.add(self.latest_query.clone());
+ self.query_history.reset_cursor();
+ }
+
let action_ix = self.matches[self.selected_ix].candidate_id;
let command = self.commands.swap_remove(action_ix);
telemetry::event!(
@@ -588,7 +719,7 @@ mod tests {
use super::*;
use editor::Editor;
use go_to_line::GoToLine;
- use gpui::TestAppContext;
+ use gpui::{TestAppContext, VisualTestContext};
use language::Point;
use project::Project;
use settings::KeymapFile;
@@ -799,7 +930,9 @@ mod tests {
"bindings": {
"cmd-n": "workspace::NewFile",
"enter": "menu::Confirm",
- "cmd-shift-p": "command_palette::Toggle"
+ "cmd-shift-p": "command_palette::Toggle",
+ "up": "menu::SelectPrevious",
+ "down": "menu::SelectNext"
}
}
]"#,
@@ -808,4 +941,264 @@ mod tests {
app_state
})
}
+
+ fn open_palette_with_history(
+ workspace: &Entity,
+ history: &[&str],
+ cx: &mut VisualTestContext,
+ ) -> Entity> {
+ cx.simulate_keystrokes("cmd-shift-p");
+ cx.run_until_parked();
+
+ let palette = workspace.update(cx, |workspace, cx| {
+ workspace
+ .active_modal::(cx)
+ .unwrap()
+ .read(cx)
+ .picker
+ .clone()
+ });
+
+ palette.update(cx, |palette, _cx| {
+ palette.delegate.seed_history(history);
+ });
+
+ palette
+ }
+
+ #[gpui::test]
+ async fn test_history_navigation_basic(cx: &mut TestAppContext) {
+ let app_state = init_test(cx);
+ let project = Project::test(app_state.fs.clone(), [], cx).await;
+ let (workspace, cx) =
+ cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
+
+ let palette = open_palette_with_history(&workspace, &["backspace", "select all"], cx);
+
+ // Query should be empty initially
+ palette.read_with(cx, |palette, cx| {
+ assert_eq!(palette.query(cx), "");
+ });
+
+ // Press up - should load most recent query "select all"
+ cx.simulate_keystrokes("up");
+ cx.background_executor.run_until_parked();
+ palette.read_with(cx, |palette, cx| {
+ assert_eq!(palette.query(cx), "select all");
+ });
+
+ // Press up again - should load "backspace"
+ cx.simulate_keystrokes("up");
+ cx.background_executor.run_until_parked();
+ palette.read_with(cx, |palette, cx| {
+ assert_eq!(palette.query(cx), "backspace");
+ });
+
+ // Press down - should go back to "select all"
+ cx.simulate_keystrokes("down");
+ cx.background_executor.run_until_parked();
+ palette.read_with(cx, |palette, cx| {
+ assert_eq!(palette.query(cx), "select all");
+ });
+
+ // Press down again - should clear query (exit history mode)
+ cx.simulate_keystrokes("down");
+ cx.background_executor.run_until_parked();
+ palette.read_with(cx, |palette, cx| {
+ assert_eq!(palette.query(cx), "");
+ });
+ }
+
+ #[gpui::test]
+ async fn test_history_mode_exit_on_typing(cx: &mut TestAppContext) {
+ let app_state = init_test(cx);
+ let project = Project::test(app_state.fs.clone(), [], cx).await;
+ let (workspace, cx) =
+ cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
+
+ let palette = open_palette_with_history(&workspace, &["backspace"], cx);
+
+ // Press up to enter history mode
+ cx.simulate_keystrokes("up");
+ cx.background_executor.run_until_parked();
+ palette.read_with(cx, |palette, cx| {
+ assert_eq!(palette.query(cx), "backspace");
+ });
+
+ // Type something - should append to the history query
+ cx.simulate_input("x");
+ cx.background_executor.run_until_parked();
+ palette.read_with(cx, |palette, cx| {
+ assert_eq!(palette.query(cx), "backspacex");
+ });
+ }
+
+ #[gpui::test]
+ async fn test_history_navigation_with_suggestions(cx: &mut TestAppContext) {
+ let app_state = init_test(cx);
+ let project = Project::test(app_state.fs.clone(), [], cx).await;
+ let (workspace, cx) =
+ cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
+
+ let palette = open_palette_with_history(&workspace, &["editor: close", "editor: open"], cx);
+
+ // Open palette with a query that has multiple matches
+ cx.simulate_input("editor");
+ cx.background_executor.run_until_parked();
+
+ // Should have multiple matches, selected_ix should be 0
+ palette.read_with(cx, |palette, _| {
+ assert!(palette.delegate.matches.len() > 1);
+ assert_eq!(palette.delegate.selected_ix, 0);
+ });
+
+ // Press down - should navigate to next suggestion (not history)
+ cx.simulate_keystrokes("down");
+ cx.background_executor.run_until_parked();
+ palette.read_with(cx, |palette, _| {
+ assert_eq!(palette.delegate.selected_ix, 1);
+ });
+
+ // Press up - should go back to first suggestion
+ cx.simulate_keystrokes("up");
+ cx.background_executor.run_until_parked();
+ palette.read_with(cx, |palette, _| {
+ assert_eq!(palette.delegate.selected_ix, 0);
+ });
+
+ // Press up again at top - should enter history mode and show previous query
+ // that matches the "editor" prefix
+ cx.simulate_keystrokes("up");
+ cx.background_executor.run_until_parked();
+ palette.read_with(cx, |palette, cx| {
+ assert_eq!(palette.query(cx), "editor: open");
+ });
+ }
+
+ #[gpui::test]
+ async fn test_history_prefix_search(cx: &mut TestAppContext) {
+ let app_state = init_test(cx);
+ let project = Project::test(app_state.fs.clone(), [], cx).await;
+ let (workspace, cx) =
+ cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
+
+ let palette = open_palette_with_history(
+ &workspace,
+ &["open file", "select all", "select line", "backspace"],
+ cx,
+ );
+
+ // Type "sel" as a prefix
+ cx.simulate_input("sel");
+ cx.background_executor.run_until_parked();
+
+ // Press up - should get "select line" (most recent matching "sel")
+ cx.simulate_keystrokes("up");
+ cx.background_executor.run_until_parked();
+ palette.read_with(cx, |palette, cx| {
+ assert_eq!(palette.query(cx), "select line");
+ });
+
+ // Press up again - should get "select all" (next matching "sel")
+ cx.simulate_keystrokes("up");
+ cx.background_executor.run_until_parked();
+ palette.read_with(cx, |palette, cx| {
+ assert_eq!(palette.query(cx), "select all");
+ });
+
+ // Press up again - should stay at "select all" (no more matches for "sel")
+ cx.simulate_keystrokes("up");
+ cx.background_executor.run_until_parked();
+ palette.read_with(cx, |palette, cx| {
+ assert_eq!(palette.query(cx), "select all");
+ });
+
+ // Press down - should go back to "select line"
+ cx.simulate_keystrokes("down");
+ cx.background_executor.run_until_parked();
+ palette.read_with(cx, |palette, cx| {
+ assert_eq!(palette.query(cx), "select line");
+ });
+
+ // Press down again - should return to original prefix "sel"
+ cx.simulate_keystrokes("down");
+ cx.background_executor.run_until_parked();
+ palette.read_with(cx, |palette, cx| {
+ assert_eq!(palette.query(cx), "sel");
+ });
+ }
+
+ #[gpui::test]
+ async fn test_history_prefix_search_no_matches(cx: &mut TestAppContext) {
+ let app_state = init_test(cx);
+ let project = Project::test(app_state.fs.clone(), [], cx).await;
+ let (workspace, cx) =
+ cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
+
+ let palette =
+ open_palette_with_history(&workspace, &["open file", "backspace", "select all"], cx);
+
+ // Type "xyz" as a prefix that doesn't match anything
+ cx.simulate_input("xyz");
+ cx.background_executor.run_until_parked();
+
+ // Press up - should stay at "xyz" (no matches)
+ cx.simulate_keystrokes("up");
+ cx.background_executor.run_until_parked();
+ palette.read_with(cx, |palette, cx| {
+ assert_eq!(palette.query(cx), "xyz");
+ });
+ }
+
+ #[gpui::test]
+ async fn test_history_empty_prefix_searches_all(cx: &mut TestAppContext) {
+ let app_state = init_test(cx);
+ let project = Project::test(app_state.fs.clone(), [], cx).await;
+ let (workspace, cx) =
+ cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
+
+ let palette = open_palette_with_history(&workspace, &["alpha", "beta", "gamma"], cx);
+
+ // With empty query, press up - should get "gamma" (most recent)
+ cx.simulate_keystrokes("up");
+ cx.background_executor.run_until_parked();
+ palette.read_with(cx, |palette, cx| {
+ assert_eq!(palette.query(cx), "gamma");
+ });
+
+ // Press up - should get "beta"
+ cx.simulate_keystrokes("up");
+ cx.background_executor.run_until_parked();
+ palette.read_with(cx, |palette, cx| {
+ assert_eq!(palette.query(cx), "beta");
+ });
+
+ // Press up - should get "alpha"
+ cx.simulate_keystrokes("up");
+ cx.background_executor.run_until_parked();
+ palette.read_with(cx, |palette, cx| {
+ assert_eq!(palette.query(cx), "alpha");
+ });
+
+ // Press down - should get "beta"
+ cx.simulate_keystrokes("down");
+ cx.background_executor.run_until_parked();
+ palette.read_with(cx, |palette, cx| {
+ assert_eq!(palette.query(cx), "beta");
+ });
+
+ // Press down - should get "gamma"
+ cx.simulate_keystrokes("down");
+ cx.background_executor.run_until_parked();
+ palette.read_with(cx, |palette, cx| {
+ assert_eq!(palette.query(cx), "gamma");
+ });
+
+ // Press down - should return to empty string (exit history mode)
+ cx.simulate_keystrokes("down");
+ cx.background_executor.run_until_parked();
+ palette.read_with(cx, |palette, cx| {
+ assert_eq!(palette.query(cx), "");
+ });
+ }
}
diff --git a/crates/command_palette/src/persistence.rs b/crates/command_palette/src/persistence.rs
index feaed72570d56f4895ff05eef891fc81c2e5e0b6..4556079b4f9c8e7a989f3e32eac6f7d084e67a4e 100644
--- a/crates/command_palette/src/persistence.rs
+++ b/crates/command_palette/src/persistence.rs
@@ -123,6 +123,16 @@ impl CommandPaletteDB {
ORDER BY COUNT(1) DESC
}
}
+
+ query! {
+ pub fn list_recent_queries() -> Result> {
+ SELECT user_query
+ FROM command_invocations
+ WHERE user_query != ""
+ GROUP BY user_query
+ ORDER BY MAX(last_invoked) ASC
+ }
+ }
}
#[cfg(test)]
diff --git a/crates/picker/src/picker.rs b/crates/picker/src/picker.rs
index 8fb4941b716efa8186937ec7b49bcc3cfb26d44b..3d6ae27dfa0c6b60088995de6ccc1d85b08c9428 100644
--- a/crates/picker/src/picker.rs
+++ b/crates/picker/src/picker.rs
@@ -97,6 +97,18 @@ pub trait PickerDelegate: Sized + 'static {
window: &mut Window,
cx: &mut Context>,
);
+
+ /// Called before the picker handles `SelectPrevious` or `SelectNext`. Return `Some(query)` to
+ /// set a new query and prevent the default selection behavior.
+ fn select_history(
+ &mut self,
+ _direction: Direction,
+ _query: &str,
+ _window: &mut Window,
+ _cx: &mut App,
+ ) -> Option {
+ None
+ }
fn can_select(
&mut self,
_ix: usize,
@@ -448,6 +460,14 @@ impl Picker {
window: &mut Window,
cx: &mut Context,
) {
+ let query = self.query(cx);
+ if let Some(query) = self
+ .delegate
+ .select_history(Direction::Down, &query, window, cx)
+ {
+ self.set_query(query, window, cx);
+ return;
+ }
let count = self.delegate.match_count();
if count > 0 {
let index = self.delegate.selected_index();
@@ -467,6 +487,14 @@ impl Picker {
window: &mut Window,
cx: &mut Context,
) {
+ let query = self.query(cx);
+ if let Some(query) = self
+ .delegate
+ .select_history(Direction::Up, &query, window, cx)
+ {
+ self.set_query(query, window, cx);
+ return;
+ }
let count = self.delegate.match_count();
if count > 0 {
let index = self.delegate.selected_index();
From fd2094fa19828d34614f7d420adcd87bff597edc Mon Sep 17 00:00:00 2001
From: Mikayla Maki
Date: Tue, 9 Dec 2025 23:46:04 -0800
Subject: [PATCH 27/48] Add inline prompt rating (#44230)
TODO:
- [x] Add inline prompt rating buttons
- [ ] Hook this into our other systems
Release Notes:
- N/A
---
Cargo.lock | 1 +
assets/keymaps/default-linux.json | 5 +-
assets/keymaps/default-macos.json | 4 +-
assets/keymaps/default-windows.json | 4 +-
crates/agent_ui/Cargo.toml | 1 +
crates/agent_ui/src/agent_model_selector.rs | 4 +
crates/agent_ui/src/buffer_codegen.rs | 20 ++
crates/agent_ui/src/inline_prompt_editor.rs | 263 ++++++++++++++++++--
crates/agent_ui/src/terminal_codegen.rs | 17 +-
crates/zed/src/zed.rs | 1 +
10 files changed, 292 insertions(+), 28 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 49b9d6069ccdfadf2d5145808fd7b758f9b389bb..7ae8d55a2484970b0ae1ad0631acadf22d106e46 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -401,6 +401,7 @@ dependencies = [
"unindent",
"url",
"util",
+ "uuid",
"watch",
"workspace",
"zed_actions",
diff --git a/assets/keymaps/default-linux.json b/assets/keymaps/default-linux.json
index 54a4f331c0b0c59eca79065fe42c1a8ecbf646b7..3838edb7a1fbea49ee0c5e1a978f9e8a9b919320 100644
--- a/assets/keymaps/default-linux.json
+++ b/assets/keymaps/default-linux.json
@@ -811,7 +811,10 @@
"context": "PromptEditor",
"bindings": {
"ctrl-[": "agent::CyclePreviousInlineAssist",
- "ctrl-]": "agent::CycleNextInlineAssist"
+ "ctrl-]": "agent::CycleNextInlineAssist",
+ "ctrl-shift-enter": "inline_assistant::ThumbsUpResult",
+ "ctrl-shift-backspace": "inline_assistant::ThumbsDownResult"
+
}
},
{
diff --git a/assets/keymaps/default-macos.json b/assets/keymaps/default-macos.json
index 060151c647e42370f5aa0be5d2fa186774c2574d..9edfaa03f8d7c9609d7b642ee7ddf61973f75e76 100644
--- a/assets/keymaps/default-macos.json
+++ b/assets/keymaps/default-macos.json
@@ -878,7 +878,9 @@
"bindings": {
"cmd-alt-/": "agent::ToggleModelSelector",
"ctrl-[": "agent::CyclePreviousInlineAssist",
- "ctrl-]": "agent::CycleNextInlineAssist"
+ "ctrl-]": "agent::CycleNextInlineAssist",
+ "cmd-shift-enter": "inline_assistant::ThumbsUpResult",
+ "cmd-shift-backspace": "inline_assistant::ThumbsDownResult"
}
},
{
diff --git a/assets/keymaps/default-windows.json b/assets/keymaps/default-windows.json
index d749ac56886860b0e80de27f942082639df0447b..5842fe7729c74ad3f226055382cbac7f0b6d2f8f 100644
--- a/assets/keymaps/default-windows.json
+++ b/assets/keymaps/default-windows.json
@@ -816,7 +816,9 @@
"use_key_equivalents": true,
"bindings": {
"ctrl-[": "agent::CyclePreviousInlineAssist",
- "ctrl-]": "agent::CycleNextInlineAssist"
+ "ctrl-]": "agent::CycleNextInlineAssist",
+ "ctrl-shift-enter": "inline_assistant::ThumbsUpResult",
+ "ctrl-shift-delete": "inline_assistant::ThumbsDownResult"
}
},
{
diff --git a/crates/agent_ui/Cargo.toml b/crates/agent_ui/Cargo.toml
index 048ffab9b72bdecce3754320bf34f1702f021554..2af0ce6fbd2b636d19d9cb8e544851514800313c 100644
--- a/crates/agent_ui/Cargo.toml
+++ b/crates/agent_ui/Cargo.toml
@@ -95,6 +95,7 @@ ui.workspace = true
ui_input.workspace = true
url.workspace = true
util.workspace = true
+uuid.workspace = true
watch.workspace = true
workspace.workspace = true
zed_actions.workspace = true
diff --git a/crates/agent_ui/src/agent_model_selector.rs b/crates/agent_ui/src/agent_model_selector.rs
index 3840e40cf4d22db9d52e74ef0489c06ca8a15f26..9c2634143099d2097b5c6492f81c56aa51f12491 100644
--- a/crates/agent_ui/src/agent_model_selector.rs
+++ b/crates/agent_ui/src/agent_model_selector.rs
@@ -63,6 +63,10 @@ impl AgentModelSelector {
pub fn toggle(&self, window: &mut Window, cx: &mut Context) {
self.menu_handle.toggle(window, cx);
}
+
+ pub fn active_model(&self, cx: &App) -> Option {
+ self.selector.read(cx).delegate.active_model(cx)
+ }
}
impl Render for AgentModelSelector {
diff --git a/crates/agent_ui/src/buffer_codegen.rs b/crates/agent_ui/src/buffer_codegen.rs
index f7e7884310458e97421768882df57934a19b4430..1cd7bec7b5b2c24cfbcf01a20091e8a07608e73a 100644
--- a/crates/agent_ui/src/buffer_codegen.rs
+++ b/crates/agent_ui/src/buffer_codegen.rs
@@ -119,6 +119,10 @@ impl BufferCodegen {
.push(cx.subscribe(&codegen, |_, _, event, cx| cx.emit(*event)));
}
+ pub fn active_completion(&self, cx: &App) -> Option {
+ self.active_alternative().read(cx).current_completion()
+ }
+
pub fn active_alternative(&self) -> &Entity {
&self.alternatives[self.active_alternative]
}
@@ -241,6 +245,10 @@ impl BufferCodegen {
pub fn last_equal_ranges<'a>(&self, cx: &'a App) -> &'a [Range] {
self.active_alternative().read(cx).last_equal_ranges()
}
+
+ pub fn selected_text<'a>(&self, cx: &'a App) -> Option<&'a str> {
+ self.active_alternative().read(cx).selected_text()
+ }
}
impl EventEmitter for BufferCodegen {}
@@ -264,6 +272,7 @@ pub struct CodegenAlternative {
line_operations: Vec,
elapsed_time: Option,
completion: Option,
+ selected_text: Option,
pub message_id: Option,
pub model_explanation: Option,
}
@@ -323,6 +332,7 @@ impl CodegenAlternative {
range,
elapsed_time: None,
completion: None,
+ selected_text: None,
model_explanation: None,
_subscription: cx.subscribe(&buffer, Self::handle_buffer_event),
}
@@ -608,6 +618,8 @@ impl CodegenAlternative {
.text_for_range(self.range.start..self.range.end)
.collect::();
+ self.selected_text = Some(selected_text.to_string());
+
let selection_start = self.range.start.to_point(&snapshot);
// Start with the indentation of the first line in the selection
@@ -868,6 +880,14 @@ impl CodegenAlternative {
cx.notify();
}
+ pub fn current_completion(&self) -> Option {
+ self.completion.clone()
+ }
+
+ pub fn selected_text(&self) -> Option<&str> {
+ self.selected_text.as_deref()
+ }
+
pub fn stop(&mut self, cx: &mut Context) {
self.last_equal_ranges.clear();
if self.diff.is_empty() {
diff --git a/crates/agent_ui/src/inline_prompt_editor.rs b/crates/agent_ui/src/inline_prompt_editor.rs
index b9852ea727c7974e3564fadc652f132076c01f09..4856d4024c94856e8dee91c048fe6ce72e79a7b8 100644
--- a/crates/agent_ui/src/inline_prompt_editor.rs
+++ b/crates/agent_ui/src/inline_prompt_editor.rs
@@ -8,10 +8,11 @@ use editor::{
ContextMenuOptions, Editor, EditorElement, EditorEvent, EditorMode, EditorStyle, MultiBuffer,
actions::{MoveDown, MoveUp},
};
+use feature_flags::{FeatureFlag, FeatureFlagAppExt};
use fs::Fs;
use gpui::{
- AnyElement, App, Context, Entity, EventEmitter, FocusHandle, Focusable, Subscription,
- TextStyle, TextStyleRefinement, WeakEntity, Window,
+ AnyElement, App, ClipboardItem, Context, Entity, EventEmitter, FocusHandle, Focusable,
+ Subscription, TextStyle, TextStyleRefinement, WeakEntity, Window, actions,
};
use language_model::{LanguageModel, LanguageModelRegistry};
use markdown::{HeadingLevelStyles, Markdown, MarkdownElement, MarkdownStyle};
@@ -19,14 +20,16 @@ use parking_lot::Mutex;
use project::Project;
use prompt_store::PromptStore;
use settings::Settings;
-use std::cmp;
use std::ops::Range;
use std::rc::Rc;
use std::sync::Arc;
+use std::{cmp, mem};
use theme::ThemeSettings;
use ui::utils::WithRemSize;
use ui::{IconButtonShape, KeyBinding, PopoverMenuHandle, Tooltip, prelude::*};
-use workspace::Workspace;
+use uuid::Uuid;
+use workspace::notifications::NotificationId;
+use workspace::{Toast, Workspace};
use zed_actions::agent::ToggleModelSelector;
use crate::agent_model_selector::AgentModelSelector;
@@ -39,6 +42,58 @@ use crate::mention_set::{MentionSet, crease_for_mention};
use crate::terminal_codegen::TerminalCodegen;
use crate::{CycleNextInlineAssist, CyclePreviousInlineAssist, ModelUsageContext};
+actions!(inline_assistant, [ThumbsUpResult, ThumbsDownResult]);
+
+pub struct InlineAssistRatingFeatureFlag;
+
+impl FeatureFlag for InlineAssistRatingFeatureFlag {
+ const NAME: &'static str = "inline-assist-rating";
+
+ fn enabled_for_staff() -> bool {
+ false
+ }
+}
+
+enum RatingState {
+ Pending,
+ GeneratedCompletion(Option),
+ Rated(Uuid),
+}
+
+impl RatingState {
+ fn is_pending(&self) -> bool {
+ matches!(self, RatingState::Pending)
+ }
+
+ fn rating_id(&self) -> Option {
+ match self {
+ RatingState::Pending => None,
+ RatingState::GeneratedCompletion(_) => None,
+ RatingState::Rated(id) => Some(*id),
+ }
+ }
+
+ fn rate(&mut self) -> (Uuid, Option) {
+ let id = Uuid::new_v4();
+ let old_state = mem::replace(self, RatingState::Rated(id));
+ let completion = match old_state {
+ RatingState::Pending => None,
+ RatingState::GeneratedCompletion(completion) => completion,
+ RatingState::Rated(_) => None,
+ };
+
+ (id, completion)
+ }
+
+ fn reset(&mut self) {
+ *self = RatingState::Pending;
+ }
+
+ fn generated_completion(&mut self, generated_completion: Option) {
+ *self = RatingState::GeneratedCompletion(generated_completion);
+ }
+}
+
pub struct PromptEditor {
pub editor: Entity,
mode: PromptEditorMode,
@@ -54,6 +109,7 @@ pub struct PromptEditor {
_codegen_subscription: Subscription,
editor_subscriptions: Vec,
show_rate_limit_notice: bool,
+ rated: RatingState,
_phantom: std::marker::PhantomData,
}
@@ -153,6 +209,8 @@ impl Render for PromptEditor {
.on_action(cx.listener(Self::cancel))
.on_action(cx.listener(Self::move_up))
.on_action(cx.listener(Self::move_down))
+ .on_action(cx.listener(Self::thumbs_up))
+ .on_action(cx.listener(Self::thumbs_down))
.capture_action(cx.listener(Self::cycle_prev))
.capture_action(cx.listener(Self::cycle_next))
.child(
@@ -429,6 +487,7 @@ impl PromptEditor {
}
self.edited_since_done = true;
+ self.rated.reset();
cx.notify();
}
EditorEvent::Blurred => {
@@ -516,6 +575,121 @@ impl PromptEditor {
}
}
+ fn thumbs_up(&mut self, _: &ThumbsUpResult, _window: &mut Window, cx: &mut Context) {
+ if self.rated.is_pending() {
+ self.toast("Still generating...", None, cx);
+ return;
+ }
+
+ if let Some(rating_id) = self.rated.rating_id() {
+ self.toast("Already rated this completion", Some(rating_id), cx);
+ return;
+ }
+
+ let (rating_id, completion) = self.rated.rate();
+
+ let selected_text = match &self.mode {
+ PromptEditorMode::Buffer { codegen, .. } => {
+ codegen.read(cx).selected_text(cx).map(|s| s.to_string())
+ }
+ PromptEditorMode::Terminal { .. } => None,
+ };
+
+ let model_info = self.model_selector.read(cx).active_model(cx);
+ let model_id = {
+ let Some(configured_model) = model_info else {
+ self.toast("No configured model", None, cx);
+ return;
+ };
+
+ configured_model.model.telemetry_id()
+ };
+
+ let prompt = self.editor.read(cx).text(cx);
+
+ telemetry::event!(
+ "Inline Assistant Rated",
+ rating = "positive",
+ model = model_id,
+ prompt = prompt,
+ completion = completion,
+ selected_text = selected_text,
+ rating_id = rating_id.to_string()
+ );
+
+ cx.notify();
+ }
+
+ fn thumbs_down(&mut self, _: &ThumbsDownResult, _window: &mut Window, cx: &mut Context) {
+ if self.rated.is_pending() {
+ self.toast("Still generating...", None, cx);
+ return;
+ }
+ if let Some(rating_id) = self.rated.rating_id() {
+ self.toast("Already rated this completion", Some(rating_id), cx);
+ return;
+ }
+
+ let (rating_id, completion) = self.rated.rate();
+
+ let selected_text = match &self.mode {
+ PromptEditorMode::Buffer { codegen, .. } => {
+ codegen.read(cx).selected_text(cx).map(|s| s.to_string())
+ }
+ PromptEditorMode::Terminal { .. } => None,
+ };
+
+ let model_info = self.model_selector.read(cx).active_model(cx);
+ let model_telemetry_id = {
+ let Some(configured_model) = model_info else {
+ self.toast("No configured model", None, cx);
+ return;
+ };
+
+ configured_model.model.telemetry_id()
+ };
+
+ let prompt = self.editor.read(cx).text(cx);
+
+ telemetry::event!(
+ "Inline Assistant Rated",
+ rating = "negative",
+ model = model_telemetry_id,
+ prompt = prompt,
+ completion = completion,
+ selected_text = selected_text,
+ rating_id = rating_id.to_string()
+ );
+
+ cx.notify();
+ }
+
+ fn toast(&mut self, msg: &str, uuid: Option, cx: &mut Context<'_, PromptEditor>) {
+ self.workspace
+ .update(cx, |workspace, cx| {
+ enum InlinePromptRating {}
+ workspace.show_toast(
+ {
+ let mut toast = Toast::new(
+ NotificationId::unique::(),
+ msg.to_string(),
+ )
+ .autohide();
+
+ if let Some(uuid) = uuid {
+ toast = toast.on_click("Click to copy rating ID", move |_, cx| {
+ cx.write_to_clipboard(ClipboardItem::new_string(uuid.to_string()));
+ });
+ };
+
+ toast
+ },
+ cx,
+ );
+ })
+ .ok();
+ }
+
fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context) {
if let Some(ix) = self.prompt_history_ix {
if ix > 0 {
@@ -621,6 +795,9 @@ impl PromptEditor {
.into_any_element(),
]
} else {
+ let show_rating_buttons = cx.has_flag::();
+ let rated = self.rated.rating_id().is_some();
+
let accept = IconButton::new("accept", IconName::Check)
.icon_color(Color::Info)
.shape(IconButtonShape::Square)
@@ -632,25 +809,59 @@ impl PromptEditor {
}))
.into_any_element();
- match &self.mode {
- PromptEditorMode::Terminal { .. } => vec![
- accept,
- IconButton::new("confirm", IconName::PlayFilled)
- .icon_color(Color::Info)
+ let mut buttons = Vec::new();
+
+ if show_rating_buttons {
+ buttons.push(
+ IconButton::new("thumbs-down", IconName::ThumbsDown)
+ .icon_color(if rated { Color::Muted } else { Color::Default })
.shape(IconButtonShape::Square)
- .tooltip(|_window, cx| {
- Tooltip::for_action(
- "Execute Generated Command",
- &menu::SecondaryConfirm,
- cx,
- )
- })
- .on_click(cx.listener(|_, _, _, cx| {
- cx.emit(PromptEditorEvent::ConfirmRequested { execute: true });
+ .disabled(rated)
+ .tooltip(Tooltip::text("Bad result"))
+ .on_click(cx.listener(|this, _, window, cx| {
+ this.thumbs_down(&ThumbsDownResult, window, cx);
}))
.into_any_element(),
- ],
- PromptEditorMode::Buffer { .. } => vec![accept],
+ );
+
+ buttons.push(
+ IconButton::new("thumbs-up", IconName::ThumbsUp)
+ .icon_color(if rated { Color::Muted } else { Color::Default })
+ .shape(IconButtonShape::Square)
+ .disabled(rated)
+ .tooltip(Tooltip::text("Good result"))
+ .on_click(cx.listener(|this, _, window, cx| {
+ this.thumbs_up(&ThumbsUpResult, window, cx);
+ }))
+ .into_any_element(),
+ );
+ }
+
+ buttons.push(accept);
+
+ match &self.mode {
+ PromptEditorMode::Terminal { .. } => {
+ buttons.push(
+ IconButton::new("confirm", IconName::PlayFilled)
+ .icon_color(Color::Info)
+ .shape(IconButtonShape::Square)
+ .tooltip(|_window, cx| {
+ Tooltip::for_action(
+ "Execute Generated Command",
+ &menu::SecondaryConfirm,
+ cx,
+ )
+ })
+ .on_click(cx.listener(|_, _, _, cx| {
+ cx.emit(PromptEditorEvent::ConfirmRequested {
+ execute: true,
+ });
+ }))
+ .into_any_element(),
+ );
+ buttons
+ }
+ PromptEditorMode::Buffer { .. } => buttons,
}
}
}
@@ -979,6 +1190,7 @@ impl PromptEditor {
editor_subscriptions: Vec::new(),
show_rate_limit_notice: false,
mode,
+ rated: RatingState::Pending,
_phantom: Default::default(),
};
@@ -989,7 +1201,7 @@ impl PromptEditor {
fn handle_codegen_changed(
&mut self,
- _: Entity,
+ codegen: Entity,
cx: &mut Context>,
) {
match self.codegen_status(cx) {
@@ -998,10 +1210,13 @@ impl PromptEditor {
.update(cx, |editor, _| editor.set_read_only(false));
}
CodegenStatus::Pending => {
+ self.rated.reset();
self.editor
.update(cx, |editor, _| editor.set_read_only(true));
}
CodegenStatus::Done => {
+ let completion = codegen.read(cx).active_completion(cx);
+ self.rated.generated_completion(completion);
self.edited_since_done = false;
self.editor
.update(cx, |editor, _| editor.set_read_only(false));
@@ -1122,6 +1337,7 @@ impl PromptEditor {
editor_subscriptions: Vec::new(),
mode,
show_rate_limit_notice: false,
+ rated: RatingState::Pending,
_phantom: Default::default(),
};
this.count_lines(cx);
@@ -1154,17 +1370,20 @@ impl PromptEditor {
}
}
- fn handle_codegen_changed(&mut self, _: Entity, cx: &mut Context) {
+ fn handle_codegen_changed(&mut self, codegen: Entity, cx: &mut Context) {
match &self.codegen().read(cx).status {
CodegenStatus::Idle => {
self.editor
.update(cx, |editor, _| editor.set_read_only(false));
}
CodegenStatus::Pending => {
+ self.rated = RatingState::Pending;
self.editor
.update(cx, |editor, _| editor.set_read_only(true));
}
CodegenStatus::Done | CodegenStatus::Error(_) => {
+ self.rated
+ .generated_completion(codegen.read(cx).completion());
self.edited_since_done = false;
self.editor
.update(cx, |editor, _| editor.set_read_only(false));
diff --git a/crates/agent_ui/src/terminal_codegen.rs b/crates/agent_ui/src/terminal_codegen.rs
index 5a4a9d560a16e858dcaedf706f2067a24bc12c5f..cc99471f7f3037cb94ff23979036bd6c2026e2f0 100644
--- a/crates/agent_ui/src/terminal_codegen.rs
+++ b/crates/agent_ui/src/terminal_codegen.rs
@@ -135,6 +135,12 @@ impl TerminalCodegen {
cx.notify();
}
+ pub fn completion(&self) -> Option {
+ self.transaction
+ .as_ref()
+ .map(|transaction| transaction.completion.clone())
+ }
+
pub fn stop(&mut self, cx: &mut Context) {
self.status = CodegenStatus::Done;
self.generation = Task::ready(());
@@ -167,27 +173,32 @@ pub const CLEAR_INPUT: &str = "\x03";
const CARRIAGE_RETURN: &str = "\x0d";
struct TerminalTransaction {
+ completion: String,
terminal: Entity,
}
impl TerminalTransaction {
pub fn start(terminal: Entity) -> Self {
- Self { terminal }
+ Self {
+ completion: String::new(),
+ terminal,
+ }
}
pub fn push(&mut self, hunk: String, cx: &mut App) {
// Ensure that the assistant cannot accidentally execute commands that are streamed into the terminal
let input = Self::sanitize_input(hunk);
+ self.completion.push_str(&input);
self.terminal
.update(cx, |terminal, _| terminal.input(input.into_bytes()));
}
- pub fn undo(&self, cx: &mut App) {
+ pub fn undo(self, cx: &mut App) {
self.terminal
.update(cx, |terminal, _| terminal.input(CLEAR_INPUT.as_bytes()));
}
- pub fn complete(&self, cx: &mut App) {
+ pub fn complete(self, cx: &mut App) {
self.terminal
.update(cx, |terminal, _| terminal.input(CARRIAGE_RETURN.as_bytes()));
}
diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs
index 1361fcdba788752099c8e5b37b51e751fccf4dfd..71653124b1c4af993d9878b2b689d07f4f2acd02 100644
--- a/crates/zed/src/zed.rs
+++ b/crates/zed/src/zed.rs
@@ -4745,6 +4745,7 @@ mod tests {
"git_panel",
"go_to_line",
"icon_theme_selector",
+ "inline_assistant",
"journal",
"keymap_editor",
"keystroke_input",
From a8e2dc2f252ac996a40e82f0f3e94a14b037fda7 Mon Sep 17 00:00:00 2001
From: Richard Feldman
Date: Wed, 10 Dec 2025 05:19:00 -0500
Subject: [PATCH 28/48] Use agent name from extension (#44496)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Previously this rendered `mistral-vibe` and not `Mistral Vibe`:
Release Notes:
- Render agent display names from extension in menu
---
crates/agent_ui/src/agent_configuration.rs | 37 ++++++++++++++++------
crates/agent_ui/src/agent_panel.rs | 5 ++-
crates/project/src/agent_server_store.rs | 23 ++++++++++++++
3 files changed, 54 insertions(+), 11 deletions(-)
diff --git a/crates/agent_ui/src/agent_configuration.rs b/crates/agent_ui/src/agent_configuration.rs
index aa4cbc8e5b261d1953a91fb090e7ecd28b4e3a31..327f699b4dbf5512a60637d8fce2edfba75280f0 100644
--- a/crates/agent_ui/src/agent_configuration.rs
+++ b/crates/agent_ui/src/agent_configuration.rs
@@ -977,7 +977,10 @@ impl AgentConfiguration {
} else {
AgentIcon::Name(IconName::Ai)
};
- (name, icon)
+ let display_name = agent_server_store
+ .agent_display_name(&name)
+ .unwrap_or_else(|| name.0.clone());
+ (name, icon, display_name)
})
.collect();
@@ -1084,6 +1087,7 @@ impl AgentConfiguration {
.child(self.render_agent_server(
AgentIcon::Name(IconName::AiClaude),
"Claude Code",
+ "Claude Code",
false,
cx,
))
@@ -1091,6 +1095,7 @@ impl AgentConfiguration {
.child(self.render_agent_server(
AgentIcon::Name(IconName::AiOpenAi),
"Codex CLI",
+ "Codex CLI",
false,
cx,
))
@@ -1098,16 +1103,23 @@ impl AgentConfiguration {
.child(self.render_agent_server(
AgentIcon::Name(IconName::AiGemini),
"Gemini CLI",
+ "Gemini CLI",
false,
cx,
))
.map(|mut parent| {
- for (name, icon) in user_defined_agents {
+ for (name, icon, display_name) in user_defined_agents {
parent = parent
.child(
Divider::horizontal().color(DividerColor::BorderFaded),
)
- .child(self.render_agent_server(icon, name, true, cx));
+ .child(self.render_agent_server(
+ icon,
+ name,
+ display_name,
+ true,
+ cx,
+ ));
}
parent
}),
@@ -1118,11 +1130,13 @@ impl AgentConfiguration {
fn render_agent_server(
&self,
icon: AgentIcon,
- name: impl Into,
+ id: impl Into,
+ display_name: impl Into,
external: bool,
cx: &mut Context,
) -> impl IntoElement {
- let name = name.into();
+ let id = id.into();
+ let display_name = display_name.into();
let icon = match icon {
AgentIcon::Name(icon_name) => Icon::new(icon_name)
.size(IconSize::Small)
@@ -1132,12 +1146,15 @@ impl AgentConfiguration {
.color(Color::Muted),
};
- let tooltip_id = SharedString::new(format!("agent-source-{}", name));
- let tooltip_message = format!("The {} agent was installed from an extension.", name);
+ let tooltip_id = SharedString::new(format!("agent-source-{}", id));
+ let tooltip_message = format!(
+ "The {} agent was installed from an extension.",
+ display_name
+ );
- let agent_server_name = ExternalAgentServerName(name.clone());
+ let agent_server_name = ExternalAgentServerName(id.clone());
- let uninstall_btn_id = SharedString::from(format!("uninstall-{}", name));
+ let uninstall_btn_id = SharedString::from(format!("uninstall-{}", id));
let uninstall_button = IconButton::new(uninstall_btn_id, IconName::Trash)
.icon_color(Color::Muted)
.icon_size(IconSize::Small)
@@ -1161,7 +1178,7 @@ impl AgentConfiguration {
h_flex()
.gap_1p5()
.child(icon)
- .child(Label::new(name))
+ .child(Label::new(display_name))
.when(external, |this| {
this.child(
div()
diff --git a/crates/agent_ui/src/agent_panel.rs b/crates/agent_ui/src/agent_panel.rs
index 18e8f1e731defa82e865dd45e66389634992037c..d5dd0a25c818600226eb8a894f73dffcba86b797 100644
--- a/crates/agent_ui/src/agent_panel.rs
+++ b/crates/agent_ui/src/agent_panel.rs
@@ -2083,8 +2083,11 @@ impl AgentPanel {
for agent_name in agent_names {
let icon_path = agent_server_store.agent_icon(&agent_name);
+ let display_name = agent_server_store
+ .agent_display_name(&agent_name)
+ .unwrap_or_else(|| agent_name.0.clone());
- let mut entry = ContextMenuEntry::new(agent_name.clone());
+ let mut entry = ContextMenuEntry::new(display_name);
if let Some(icon_path) = icon_path {
entry = entry.custom_icon_svg(icon_path);
diff --git a/crates/project/src/agent_server_store.rs b/crates/project/src/agent_server_store.rs
index 6524cc6d22a0bf7c5d7a5c4ad6ae0e86d795be28..a2cc57beae9702e4d5b495a135e7c357c638c17a 100644
--- a/crates/project/src/agent_server_store.rs
+++ b/crates/project/src/agent_server_store.rs
@@ -137,6 +137,7 @@ pub struct AgentServerStore {
state: AgentServerStoreState,
external_agents: HashMap>,
agent_icons: HashMap,
+ agent_display_names: HashMap,
}
pub struct AgentServersUpdated;
@@ -155,6 +156,7 @@ mod ext_agent_tests {
state: AgentServerStoreState::Collab,
external_agents: HashMap::default(),
agent_icons: HashMap::default(),
+ agent_display_names: HashMap::default(),
}
}
@@ -258,6 +260,7 @@ impl AgentServerStore {
self.external_agents.retain(|name, agent| {
if agent.downcast_mut::().is_some() {
self.agent_icons.remove(name);
+ self.agent_display_names.remove(name);
false
} else {
// Keep the hardcoded external agents that don't come from extensions
@@ -275,6 +278,12 @@ impl AgentServerStore {
for (ext_id, manifest) in manifests {
for (agent_name, agent_entry) in &manifest.agent_servers {
// Store absolute icon path if provided, resolving symlinks for dev extensions
+ // Store display name from manifest
+ self.agent_display_names.insert(
+ ExternalAgentServerName(agent_name.clone().into()),
+ SharedString::from(agent_entry.name.clone()),
+ );
+
let icon_path = if let Some(icon) = &agent_entry.icon {
let icon_path = extensions_dir.join(ext_id).join(icon);
// Canonicalize to resolve symlinks (dev extensions are symlinked)
@@ -310,6 +319,12 @@ impl AgentServerStore {
let mut agents = vec![];
for (ext_id, manifest) in manifests {
for (agent_name, agent_entry) in &manifest.agent_servers {
+ // Store display name from manifest
+ self.agent_display_names.insert(
+ ExternalAgentServerName(agent_name.clone().into()),
+ SharedString::from(agent_entry.name.clone()),
+ );
+
// Store absolute icon path if provided, resolving symlinks for dev extensions
let icon = if let Some(icon) = &agent_entry.icon {
let icon_path = extensions_dir.join(ext_id).join(icon);
@@ -369,6 +384,10 @@ impl AgentServerStore {
self.agent_icons.get(name).cloned()
}
+ pub fn agent_display_name(&self, name: &ExternalAgentServerName) -> Option {
+ self.agent_display_names.get(name).cloned()
+ }
+
pub fn init_remote(session: &AnyProtoClient) {
session.add_entity_message_handler(Self::handle_external_agents_updated);
session.add_entity_message_handler(Self::handle_loading_status_updated);
@@ -559,6 +578,7 @@ impl AgentServerStore {
},
external_agents: Default::default(),
agent_icons: Default::default(),
+ agent_display_names: Default::default(),
};
if let Some(_events) = extension::ExtensionEvents::try_global(cx) {}
this.agent_servers_settings_changed(cx);
@@ -609,6 +629,7 @@ impl AgentServerStore {
},
external_agents: external_agents.into_iter().collect(),
agent_icons: HashMap::default(),
+ agent_display_names: HashMap::default(),
}
}
@@ -617,6 +638,7 @@ impl AgentServerStore {
state: AgentServerStoreState::Collab,
external_agents: Default::default(),
agent_icons: Default::default(),
+ agent_display_names: Default::default(),
}
}
@@ -2040,6 +2062,7 @@ mod extension_agent_tests {
state: AgentServerStoreState::Collab,
external_agents: HashMap::default(),
agent_icons: HashMap::default(),
+ agent_display_names: HashMap::default(),
};
// Seed with extension agents (contain ": ") and custom agents (don't contain ": ")
From 30597a0cbafdf44255e7a252f1b1b3e2e12f0668 Mon Sep 17 00:00:00 2001
From: Lukas Wirth
Date: Wed, 10 Dec 2025 11:33:49 +0100
Subject: [PATCH 29/48] project_panel: Fix create entry with trailing dot
duplicating on windows (#44524)
Release Notes:
- Fixed an issue where creating a file through the project panel with a
trailing dot in its name would duplicate the entries with and without
the dot
Co-authored by: Smit Barmase
---
crates/languages/src/go.rs | 4 +-
crates/project_panel/src/project_panel.rs | 12 +++-
.../project_panel/src/project_panel_tests.rs | 68 +++++++++++++++++++
3 files changed, 81 insertions(+), 3 deletions(-)
diff --git a/crates/languages/src/go.rs b/crates/languages/src/go.rs
index a8699fe9c2dc8cf99ca46a16fe75b1de6eea7ffa..130e142076b8c6ec0393e4f0d617c3a522b2ef22 100644
--- a/crates/languages/src/go.rs
+++ b/crates/languages/src/go.rs
@@ -73,7 +73,9 @@ impl LspInstaller for GoLspAdapter {
delegate.show_notification(NOTIFICATION_MESSAGE, cx);
})?
}
- anyhow::bail!("cannot install gopls");
+ anyhow::bail!(
+ "Could not install the Go language server `gopls`, because `go` was not found."
+ );
}
let release =
diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs
index e53be8cd33fa265dfadb201b2bcd613c54ffb9dd..0c633bcfcda2415bae84201f7e67a91fbd5a866d 100644
--- a/crates/project_panel/src/project_panel.rs
+++ b/crates/project_panel/src/project_panel.rs
@@ -1663,12 +1663,20 @@ impl ProjectPanel {
let edit_state = self.state.edit_state.as_mut()?;
let worktree_id = edit_state.worktree_id;
let is_new_entry = edit_state.is_new_entry();
- let filename = self.filename_editor.read(cx).text(cx);
+ let mut filename = self.filename_editor.read(cx).text(cx);
+ let path_style = self.project.read(cx).path_style(cx);
+ if path_style.is_windows() {
+ // on windows, trailing dots are ignored in paths
+ // this can cause project panel to create a new entry with a trailing dot
+ // while the actual one without the dot gets populated by the file watcher
+ while let Some(trimmed) = filename.strip_suffix('.') {
+ filename = trimmed.to_string();
+ }
+ }
if filename.trim().is_empty() {
return None;
}
- let path_style = self.project.read(cx).path_style(cx);
let filename_indicates_dir = if path_style.is_windows() {
filename.ends_with('/') || filename.ends_with('\\')
} else {
diff --git a/crates/project_panel/src/project_panel_tests.rs b/crates/project_panel/src/project_panel_tests.rs
index 6cf487bf9849a9252abc21504171b8c6bdf7e298..3f54e01927d67541fb3b17e88facadd1e6776bb6 100644
--- a/crates/project_panel/src/project_panel_tests.rs
+++ b/crates/project_panel/src/project_panel_tests.rs
@@ -6612,6 +6612,74 @@ async fn test_create_entries_without_selection_hide_root(cx: &mut gpui::TestAppC
);
}
+#[cfg(windows)]
+#[gpui::test]
+async fn test_create_entry_with_trailing_dot_windows(cx: &mut gpui::TestAppContext) {
+ init_test(cx);
+
+ let fs = FakeFs::new(cx.executor());
+ fs.insert_tree(
+ path!("/root"),
+ json!({
+ "dir1": {
+ "file1.txt": "",
+ },
+ }),
+ )
+ .await;
+
+ let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await;
+ let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
+ let cx = &mut VisualTestContext::from_window(*workspace, cx);
+
+ let panel = workspace
+ .update(cx, |workspace, window, cx| {
+ let panel = ProjectPanel::new(workspace, window, cx);
+ workspace.add_panel(panel.clone(), window, cx);
+ panel
+ })
+ .unwrap();
+ cx.run_until_parked();
+
+ #[rustfmt::skip]
+ assert_eq!(
+ visible_entries_as_strings(&panel, 0..20, cx),
+ &[
+ "v root",
+ " > dir1",
+ ],
+ "Initial state with nothing selected"
+ );
+
+ panel.update_in(cx, |panel, window, cx| {
+ panel.new_file(&NewFile, window, cx);
+ });
+ cx.run_until_parked();
+ panel.update_in(cx, |panel, window, cx| {
+ assert!(panel.filename_editor.read(cx).is_focused(window));
+ });
+ panel
+ .update_in(cx, |panel, window, cx| {
+ panel
+ .filename_editor
+ .update(cx, |editor, cx| editor.set_text("foo.", window, cx));
+ panel.confirm_edit(true, window, cx).unwrap()
+ })
+ .await
+ .unwrap();
+ cx.run_until_parked();
+ #[rustfmt::skip]
+ assert_eq!(
+ visible_entries_as_strings(&panel, 0..20, cx),
+ &[
+ "v root",
+ " > dir1",
+ " foo <== selected <== marked",
+ ],
+ "A new file is created under the root directory without the trailing dot"
+ );
+}
+
#[gpui::test]
async fn test_highlight_entry_for_external_drag(cx: &mut gpui::TestAppContext) {
init_test(cx);
From b1333b53ad6595e12587e8bbc58509fb2551bc43 Mon Sep 17 00:00:00 2001
From: Lukas Wirth
Date: Wed, 10 Dec 2025 11:35:29 +0100
Subject: [PATCH 30/48] editor: Improve performance of
`create_highlight_endpoints` (#44521)
We reallocate quite a bunch in this codepath even though we don't need
to, we already roughly know what number of elements we are working with
so we can reduce the required allocations to some degree. This also
reduces the amount of anchor comparisons required.
Came up in profiling for
https://github.com/zed-industries/zed/issues/44503
Release Notes:
- N/A *or* Added/Fixed/Improved ...
---
crates/editor/src/display_map/custom_highlights.rs | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/crates/editor/src/display_map/custom_highlights.rs b/crates/editor/src/display_map/custom_highlights.rs
index c9202280bf957fac4d729bab558f686c0f62e774..1ece2493e3228536999036a32959a6228f0f7cd1 100644
--- a/crates/editor/src/display_map/custom_highlights.rs
+++ b/crates/editor/src/display_map/custom_highlights.rs
@@ -79,12 +79,15 @@ fn create_highlight_endpoints(
let start_ix = ranges
.binary_search_by(|probe| probe.end.cmp(&start, buffer).then(cmp::Ordering::Less))
.unwrap_or_else(|i| i);
+ let end_ix = ranges[start_ix..]
+ .binary_search_by(|probe| {
+ probe.start.cmp(&end, buffer).then(cmp::Ordering::Greater)
+ })
+ .unwrap_or_else(|i| i);
- for range in &ranges[start_ix..] {
- if range.start.cmp(&end, buffer).is_ge() {
- break;
- }
+ highlight_endpoints.reserve(2 * end_ix);
+ for range in &ranges[start_ix..][..end_ix] {
let start = range.start.to_offset(buffer);
let end = range.end.to_offset(buffer);
if start == end {
From 0a816cbc87a4c513c429ee025caeac1082988335 Mon Sep 17 00:00:00 2001
From: Agus Zubiaga
Date: Wed, 10 Dec 2025 09:48:10 -0300
Subject: [PATCH 31/48] edit prediction: Exclude whole-module definitions from
context (#44414)
For qualified identifiers we end up requesting both the definition of
the module and the item within it, but we only want the latter. At the
moment, we can't skip the request altogether, because we can't tell them
apart from the highlights query. However, we can tell from the target
range length, because it should be small for individual definitions as
it only covers their name, not the whole body.
Release Notes:
- N/A
---
.../src/edit_prediction_context.rs | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/crates/edit_prediction_context/src/edit_prediction_context.rs b/crates/edit_prediction_context/src/edit_prediction_context.rs
index 475050fabb8b17ad76c34234094cf798e36a76ab..d3aefaa6e4ec585dc7c90fee1e95de17e018f90f 100644
--- a/crates/edit_prediction_context/src/edit_prediction_context.rs
+++ b/crates/edit_prediction_context/src/edit_prediction_context.rs
@@ -383,6 +383,8 @@ async fn rebuild_related_files(
.await)
}
+const MAX_TARGET_LEN: usize = 128;
+
fn process_definition(
location: LocationLink,
project: &Entity,
@@ -395,6 +397,15 @@ fn process_definition(
if worktree.read(cx).is_single_file() {
return None;
}
+
+ // If the target range is large, it likely means we requested the definition of an entire module.
+ // For individual definitions, the target range should be small as it only covers the symbol.
+ let buffer = location.target.buffer.read(cx);
+ let target_len = anchor_range.to_offset(&buffer).len();
+ if target_len > MAX_TARGET_LEN {
+ return None;
+ }
+
Some(CachedDefinition {
path: ProjectPath {
worktree_id: file.worktree_id(cx),
From 511e51c80eb19ae7ce46c83ea609fa65a137dbee Mon Sep 17 00:00:00 2001
From: Lukas Wirth
Date: Wed, 10 Dec 2025 14:01:31 +0100
Subject: [PATCH 32/48] text: Replace some more release panics with graceful
fallbacks (#44542)
Fixes ZED-3P7
Release Notes:
- N/A *or* Added/Fixed/Improved ...
---
crates/text/src/text.rs | 13 +++++++++++--
1 file changed, 11 insertions(+), 2 deletions(-)
diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs
index acd712f40da23af4c364649b14860e41a346389c..31eed1e926d49584e0e71a494555284c66a4e255 100644
--- a/crates/text/src/text.rs
+++ b/crates/text/src/text.rs
@@ -39,6 +39,7 @@ pub use subscription::*;
pub use sum_tree::Bias;
use sum_tree::{Dimensions, FilterCursor, SumTree, TreeMap, TreeSet};
use undo_map::UndoMap;
+use util::debug_panic;
#[cfg(any(test, feature = "test-support"))]
use util::RandomCharIter;
@@ -2439,7 +2440,7 @@ impl BufferSnapshot {
if bias == Bias::Left && offset == 0 {
Anchor::min_for_buffer(self.remote_id)
} else if bias == Bias::Right
- && ((cfg!(debug_assertions) && offset >= self.len()) || offset == self.len())
+ && ((!cfg!(debug_assertions) && offset >= self.len()) || offset == self.len())
{
Anchor::max_for_buffer(self.remote_id)
} else {
@@ -2453,7 +2454,15 @@ impl BufferSnapshot {
};
}
let (start, _, item) = self.fragments.find::(&None, &offset, bias);
- let fragment = item.unwrap();
+ let Some(fragment) = item else {
+ // We got a bad offset, likely out of bounds
+ debug_panic!(
+ "Failed to find fragment at offset {} (len: {})",
+ offset,
+ self.len()
+ );
+ return Anchor::max_for_buffer(self.remote_id);
+ };
let overshoot = offset - start;
Anchor {
timestamp: fragment.timestamp,
From dd431631b43ce33bd21494122ab74db691fec549 Mon Sep 17 00:00:00 2001
From: Finn Evers
Date: Wed, 10 Dec 2025 15:28:19 +0100
Subject: [PATCH 33/48] editor: Ensure completion menu scrollbar does not
become stale (#44536)
Only by reusing the previous scroll handle, we can ensure that both the
scrollbar remains usable and also that the scrollbar does not flicker.
Previously, the scrollbar would hold the reference to an outdated
handle.
I tried invalidating the handle the scrollbar uses, but that leads to
flickering, which is worse. Hence, let's just reuse the scrollbar here.
Release Notes:
- Fixed an issue where the scrollbar would become stale in the code
completions menu after the items were updated.
---
crates/editor/src/code_context_menus.rs | 13 +++++++++++--
crates/editor/src/editor.rs | 10 +++++++++-
2 files changed, 20 insertions(+), 3 deletions(-)
diff --git a/crates/editor/src/code_context_menus.rs b/crates/editor/src/code_context_menus.rs
index dcd96674207f02101b4066924b011d2b9ebd7a08..d255effdb72a003014dff0805fa34a23d11c8c81 100644
--- a/crates/editor/src/code_context_menus.rs
+++ b/crates/editor/src/code_context_menus.rs
@@ -206,6 +206,13 @@ impl CodeContextMenu {
CodeContextMenu::CodeActions(_) => (),
}
}
+
+ pub fn primary_scroll_handle(&self) -> UniformListScrollHandle {
+ match self {
+ CodeContextMenu::Completions(menu) => menu.scroll_handle.clone(),
+ CodeContextMenu::CodeActions(menu) => menu.scroll_handle.clone(),
+ }
+ }
}
pub enum ContextMenuOrigin {
@@ -303,6 +310,7 @@ impl CompletionsMenu {
is_incomplete: bool,
buffer: Entity,
completions: Box<[Completion]>,
+ scroll_handle: Option,
display_options: CompletionDisplayOptions,
snippet_sort_order: SnippetSortOrder,
language_registry: Option>,
@@ -332,7 +340,7 @@ impl CompletionsMenu {
selected_item: 0,
filter_task: Task::ready(()),
cancel_filter: Arc::new(AtomicBool::new(false)),
- scroll_handle: UniformListScrollHandle::new(),
+ scroll_handle: scroll_handle.unwrap_or_else(UniformListScrollHandle::new),
scroll_handle_aside: ScrollHandle::new(),
resolve_completions: true,
last_rendered_range: RefCell::new(None).into(),
@@ -354,6 +362,7 @@ impl CompletionsMenu {
choices: &Vec,
selection: Range,
buffer: Entity,
+ scroll_handle: Option,
snippet_sort_order: SnippetSortOrder,
) -> Self {
let completions = choices
@@ -404,7 +413,7 @@ impl CompletionsMenu {
selected_item: 0,
filter_task: Task::ready(()),
cancel_filter: Arc::new(AtomicBool::new(false)),
- scroll_handle: UniformListScrollHandle::new(),
+ scroll_handle: scroll_handle.unwrap_or_else(UniformListScrollHandle::new),
scroll_handle_aside: ScrollHandle::new(),
resolve_completions: false,
show_completion_documentation: false,
diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs
index d841bf858b8a77f502b8bfb2499118f9e714572e..aa226758648ac0d140edc6aa7b019c6271910848 100644
--- a/crates/editor/src/editor.rs
+++ b/crates/editor/src/editor.rs
@@ -5882,6 +5882,11 @@ impl Editor {
is_incomplete,
buffer.clone(),
completions.into(),
+ editor
+ .context_menu()
+ .borrow_mut()
+ .as_ref()
+ .map(|menu| menu.primary_scroll_handle()),
display_options,
snippet_sort_order,
languages,
@@ -10016,13 +10021,16 @@ impl Editor {
let id = post_inc(&mut self.next_completion_id);
let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
- *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
+ let mut context_menu = self.context_menu.borrow_mut();
+ let old_menu = context_menu.take();
+ *context_menu = Some(CodeContextMenu::Completions(
CompletionsMenu::new_snippet_choices(
id,
true,
choices,
selection,
buffer,
+ old_menu.map(|menu| menu.primary_scroll_handle()),
snippet_sort_order,
),
));
From 21f7e6a9e6b1e60d196cab72a2bbf402d3c69ae7 Mon Sep 17 00:00:00 2001
From: Agus Zubiaga
Date: Wed, 10 Dec 2025 12:01:49 -0300
Subject: [PATCH 34/48] commit view: Fix layout shift while loading commit
(#44548)
Fixes a few cases where the commit view would layout shift as the diff
loaded. This was caused by:
- Adding the commit message buffer after all the diff files
- Using the gutter dimensions from the last frame for the avatar spacing
Release Notes:
- commit view: Fix layout shift while loading commit
---------
Co-authored-by: MrSubidubi
---
crates/editor/src/editor.rs | 326 ++++++++++--------
crates/editor/src/editor_tests.rs | 26 +-
crates/editor/src/element.rs | 88 ++---
crates/editor/src/mouse_context_menu.rs | 2 +-
crates/editor/src/test/editor_test_context.rs | 3 +-
crates/git_ui/src/commit_view.rs | 133 +++----
crates/vim/src/normal/scroll.rs | 5 +-
crates/vim/src/test.rs | 2 +-
.../src/test/neovim_backed_test_context.rs | 5 +-
crates/zed/src/zed/quick_action_bar.rs | 16 +-
10 files changed, 307 insertions(+), 299 deletions(-)
diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs
index aa226758648ac0d140edc6aa7b019c6271910848..30e040fac1fc5682cbae8f9261c6996ec48a074d 100644
--- a/crates/editor/src/editor.rs
+++ b/crates/editor/src/editor.rs
@@ -108,7 +108,7 @@ use gpui::{
DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
MouseButton, MouseDownEvent, MouseMoveEvent, PaintQuad, ParentElement, Pixels, Render,
- ScrollHandle, SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle,
+ ScrollHandle, SharedString, Size, Stateful, Styled, Subscription, Task, TextRun, TextStyle,
TextStyleRefinement, UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity,
WeakFocusHandle, Window, div, point, prelude::*, pulsating_between, px, relative, size,
};
@@ -575,7 +575,7 @@ impl Default for EditorStyle {
}
}
-pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
+pub fn make_inlay_hints_style(cx: &App) -> HighlightStyle {
let show_background = language_settings::language_settings(None, None, cx)
.inlay_hints
.show_background;
@@ -598,7 +598,7 @@ pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
style
}
-pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
+pub fn make_suggestion_styles(cx: &App) -> EditPredictionStyles {
EditPredictionStyles {
insertion: HighlightStyle {
color: Some(cx.theme().status().predictive),
@@ -1249,6 +1249,7 @@ impl NextScrollCursorCenterTopBottom {
pub struct EditorSnapshot {
pub mode: EditorMode,
show_gutter: bool,
+ offset_content: bool,
show_line_numbers: Option,
show_git_diff_gutter: Option,
show_code_actions: Option,
@@ -1825,7 +1826,11 @@ impl Editor {
Editor::new_internal(mode, buffer, project, None, window, cx)
}
- pub fn sticky_headers(&self, cx: &App) -> Option>> {
+ pub fn sticky_headers(
+ &self,
+ style: &EditorStyle,
+ cx: &App,
+ ) -> Option>> {
let multi_buffer = self.buffer().read(cx);
let multi_buffer_snapshot = multi_buffer.snapshot(cx);
let multi_buffer_visible_start = self
@@ -1843,7 +1848,7 @@ impl Editor {
.outline_items_containing(
Point::new(start_row, 0)..Point::new(end_row, 0),
true,
- self.style().map(|style| style.syntax.as_ref()),
+ Some(style.syntax.as_ref()),
)
.into_iter()
.map(|outline_item| OutlineItem {
@@ -2935,6 +2940,7 @@ impl Editor {
EditorSnapshot {
mode: self.mode.clone(),
show_gutter: self.show_gutter,
+ offset_content: self.offset_content,
show_line_numbers: self.show_line_numbers,
show_git_diff_gutter: self.show_git_diff_gutter,
show_code_actions: self.show_code_actions,
@@ -6895,7 +6901,7 @@ impl Editor {
};
let anchor = self.selections.newest_anchor().head();
- let position = self.to_pixel_point(anchor, &snapshot, window);
+ let position = self.to_pixel_point(anchor, &snapshot, window, cx);
if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
self.show_blame_popover(
buffer,
@@ -9208,7 +9214,8 @@ impl Editor {
let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
- let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
+ let line_origin =
+ self.display_to_pixel_point(target_line_end, editor_snapshot, window, cx)?;
let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
let mut origin = start_point
@@ -9950,8 +9957,7 @@ impl Editor {
}
pub fn render_context_menu(
- &self,
- style: &EditorStyle,
+ &mut self,
max_height_in_lines: u32,
window: &mut Window,
cx: &mut Context,
@@ -9961,7 +9967,9 @@ impl Editor {
if !menu.visible() {
return None;
};
- Some(menu.render(style, max_height_in_lines, window, cx))
+ self.style
+ .as_ref()
+ .map(|style| menu.render(style, max_height_in_lines, window, cx))
}
fn render_context_menu_aside(
@@ -20364,8 +20372,11 @@ impl Editor {
self.style = Some(style);
}
- pub fn style(&self) -> Option<&EditorStyle> {
- self.style.as_ref()
+ pub fn style(&mut self, cx: &App) -> &EditorStyle {
+ if self.style.is_none() {
+ self.style = Some(self.create_style(cx));
+ }
+ self.style.as_ref().unwrap()
}
// Called by the element. This method is not designed to be called outside of the editor
@@ -22989,22 +23000,24 @@ impl Editor {
}
pub fn to_pixel_point(
- &self,
+ &mut self,
source: multi_buffer::Anchor,
editor_snapshot: &EditorSnapshot,
window: &mut Window,
+ cx: &App,
) -> Option> {
let source_point = source.to_display_point(editor_snapshot);
- self.display_to_pixel_point(source_point, editor_snapshot, window)
+ self.display_to_pixel_point(source_point, editor_snapshot, window, cx)
}
pub fn display_to_pixel_point(
- &self,
+ &mut self,
source: DisplayPoint,
editor_snapshot: &EditorSnapshot,
window: &mut Window,
+ cx: &App,
) -> Option> {
- let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
+ let line_height = self.style(cx).text.line_height_in_pixels(window.rem_size());
let text_layout_details = self.text_layout_details(window);
let scroll_top = text_layout_details
.scroll_anchor
@@ -23068,10 +23081,6 @@ impl Editor {
}
}
- pub fn last_gutter_dimensions(&self) -> &GutterDimensions {
- &self.gutter_dimensions
- }
-
pub fn wait_for_diff_to_load(&self) -> Option>> {
self.load_diff_task.clone()
}
@@ -23171,6 +23180,57 @@ impl Editor {
// skip any LSP updates for it.
self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
}
+
+ fn create_style(&self, cx: &App) -> EditorStyle {
+ let settings = ThemeSettings::get_global(cx);
+
+ let mut text_style = match self.mode {
+ EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
+ color: cx.theme().colors().editor_foreground,
+ font_family: settings.ui_font.family.clone(),
+ font_features: settings.ui_font.features.clone(),
+ font_fallbacks: settings.ui_font.fallbacks.clone(),
+ font_size: rems(0.875).into(),
+ font_weight: settings.ui_font.weight,
+ line_height: relative(settings.buffer_line_height.value()),
+ ..Default::default()
+ },
+ EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
+ color: cx.theme().colors().editor_foreground,
+ font_family: settings.buffer_font.family.clone(),
+ font_features: settings.buffer_font.features.clone(),
+ font_fallbacks: settings.buffer_font.fallbacks.clone(),
+ font_size: settings.buffer_font_size(cx).into(),
+ font_weight: settings.buffer_font.weight,
+ line_height: relative(settings.buffer_line_height.value()),
+ ..Default::default()
+ },
+ };
+ if let Some(text_style_refinement) = &self.text_style_refinement {
+ text_style.refine(text_style_refinement)
+ }
+
+ let background = match self.mode {
+ EditorMode::SingleLine => cx.theme().system().transparent,
+ EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
+ EditorMode::Full { .. } => cx.theme().colors().editor_background,
+ EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
+ };
+
+ EditorStyle {
+ background,
+ border: cx.theme().colors().border,
+ local_player: cx.theme().players().local(),
+ text: text_style,
+ scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
+ syntax: cx.theme().syntax().clone(),
+ status: cx.theme().status().clone(),
+ inlay_hints_style: make_inlay_hints_style(cx),
+ edit_prediction_styles: make_suggestion_styles(cx),
+ unnecessary_code_fade: settings.unnecessary_code_fade,
+ show_underlines: self.diagnostics_enabled(),
+ }
+ }
}
fn edit_for_markdown_paste<'a>(
@@ -24698,94 +24758,98 @@ impl EditorSnapshot {
self.scroll_anchor.scroll_position(&self.display_snapshot)
}
- fn gutter_dimensions(
+ pub fn gutter_dimensions(
&self,
font_id: FontId,
font_size: Pixels,
- max_line_number_width: Pixels,
+ style: &EditorStyle,
+ window: &mut Window,
cx: &App,
- ) -> Option {
- if !self.show_gutter {
- return None;
- }
-
- let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
- let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
+ ) -> GutterDimensions {
+ if self.show_gutter
+ && let Some(ch_width) = cx.text_system().ch_width(font_id, font_size).log_err()
+ && let Some(ch_advance) = cx.text_system().ch_advance(font_id, font_size).log_err()
+ {
+ let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
+ matches!(
+ ProjectSettings::get_global(cx).git.git_gutter,
+ GitGutterSetting::TrackedFiles
+ )
+ });
+ let gutter_settings = EditorSettings::get_global(cx).gutter;
+ let show_line_numbers = self
+ .show_line_numbers
+ .unwrap_or(gutter_settings.line_numbers);
+ let line_gutter_width = if show_line_numbers {
+ // Avoid flicker-like gutter resizes when the line number gains another digit by
+ // only resizing the gutter on files with > 10**min_line_number_digits lines.
+ let min_width_for_number_on_gutter =
+ ch_advance * gutter_settings.min_line_number_digits as f32;
+ self.max_line_number_width(style, window)
+ .max(min_width_for_number_on_gutter)
+ } else {
+ 0.0.into()
+ };
- let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
- matches!(
- ProjectSettings::get_global(cx).git.git_gutter,
- GitGutterSetting::TrackedFiles
- )
- });
- let gutter_settings = EditorSettings::get_global(cx).gutter;
- let show_line_numbers = self
- .show_line_numbers
- .unwrap_or(gutter_settings.line_numbers);
- let line_gutter_width = if show_line_numbers {
- // Avoid flicker-like gutter resizes when the line number gains another digit by
- // only resizing the gutter on files with > 10**min_line_number_digits lines.
- let min_width_for_number_on_gutter =
- ch_advance * gutter_settings.min_line_number_digits as f32;
- max_line_number_width.max(min_width_for_number_on_gutter)
- } else {
- 0.0.into()
- };
+ let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
+ let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
- let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
- let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
+ let git_blame_entries_width =
+ self.git_blame_gutter_max_author_length
+ .map(|max_author_length| {
+ let renderer = cx.global::().0.clone();
+ const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
- let git_blame_entries_width =
- self.git_blame_gutter_max_author_length
- .map(|max_author_length| {
- let renderer = cx.global::().0.clone();
- const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
+ /// The number of characters to dedicate to gaps and margins.
+ const SPACING_WIDTH: usize = 4;
- /// The number of characters to dedicate to gaps and margins.
- const SPACING_WIDTH: usize = 4;
+ let max_char_count = max_author_length.min(renderer.max_author_length())
+ + ::git::SHORT_SHA_LENGTH
+ + MAX_RELATIVE_TIMESTAMP.len()
+ + SPACING_WIDTH;
- let max_char_count = max_author_length.min(renderer.max_author_length())
- + ::git::SHORT_SHA_LENGTH
- + MAX_RELATIVE_TIMESTAMP.len()
- + SPACING_WIDTH;
+ ch_advance * max_char_count
+ });
- ch_advance * max_char_count
- });
+ let is_singleton = self.buffer_snapshot().is_singleton();
+
+ let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
+ left_padding += if !is_singleton {
+ ch_width * 4.0
+ } else if show_runnables || show_breakpoints {
+ ch_width * 3.0
+ } else if show_git_gutter && show_line_numbers {
+ ch_width * 2.0
+ } else if show_git_gutter || show_line_numbers {
+ ch_width
+ } else {
+ px(0.)
+ };
- let is_singleton = self.buffer_snapshot().is_singleton();
-
- let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
- left_padding += if !is_singleton {
- ch_width * 4.0
- } else if show_runnables || show_breakpoints {
- ch_width * 3.0
- } else if show_git_gutter && show_line_numbers {
- ch_width * 2.0
- } else if show_git_gutter || show_line_numbers {
- ch_width
- } else {
- px(0.)
- };
+ let shows_folds = is_singleton && gutter_settings.folds;
- let shows_folds = is_singleton && gutter_settings.folds;
+ let right_padding = if shows_folds && show_line_numbers {
+ ch_width * 4.0
+ } else if shows_folds || (!is_singleton && show_line_numbers) {
+ ch_width * 3.0
+ } else if show_line_numbers {
+ ch_width
+ } else {
+ px(0.)
+ };
- let right_padding = if shows_folds && show_line_numbers {
- ch_width * 4.0
- } else if shows_folds || (!is_singleton && show_line_numbers) {
- ch_width * 3.0
- } else if show_line_numbers {
- ch_width
+ GutterDimensions {
+ left_padding,
+ right_padding,
+ width: line_gutter_width + left_padding + right_padding,
+ margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
+ git_blame_entries_width,
+ }
+ } else if self.offset_content {
+ GutterDimensions::default_with_margin(font_id, font_size, cx)
} else {
- px(0.)
- };
-
- Some(GutterDimensions {
- left_padding,
- right_padding,
- width: line_gutter_width + left_padding + right_padding,
- margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
- git_blame_entries_width,
- })
+ GutterDimensions::default()
+ }
}
pub fn render_crease_toggle(
@@ -24868,6 +24932,28 @@ impl EditorSnapshot {
None
}
}
+
+ pub fn max_line_number_width(&self, style: &EditorStyle, window: &mut Window) -> Pixels {
+ let digit_count = self.widest_line_number().ilog10() + 1;
+ column_pixels(style, digit_count as usize, window)
+ }
+}
+
+pub fn column_pixels(style: &EditorStyle, column: usize, window: &Window) -> Pixels {
+ let font_size = style.text.font_size.to_pixels(window.rem_size());
+ let layout = window.text_system().shape_line(
+ SharedString::from(" ".repeat(column)),
+ font_size,
+ &[TextRun {
+ len: column,
+ font: style.text.font(),
+ color: Hsla::default(),
+ ..Default::default()
+ }],
+ None,
+ );
+
+ layout.width
}
impl Deref for EditorSnapshot {
@@ -24948,57 +25034,7 @@ impl Focusable for Editor {
impl Render for Editor {
fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement {
- let settings = ThemeSettings::get_global(cx);
-
- let mut text_style = match self.mode {
- EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
- color: cx.theme().colors().editor_foreground,
- font_family: settings.ui_font.family.clone(),
- font_features: settings.ui_font.features.clone(),
- font_fallbacks: settings.ui_font.fallbacks.clone(),
- font_size: rems(0.875).into(),
- font_weight: settings.ui_font.weight,
- line_height: relative(settings.buffer_line_height.value()),
- ..Default::default()
- },
- EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
- color: cx.theme().colors().editor_foreground,
- font_family: settings.buffer_font.family.clone(),
- font_features: settings.buffer_font.features.clone(),
- font_fallbacks: settings.buffer_font.fallbacks.clone(),
- font_size: settings.buffer_font_size(cx).into(),
- font_weight: settings.buffer_font.weight,
- line_height: relative(settings.buffer_line_height.value()),
- ..Default::default()
- },
- };
- if let Some(text_style_refinement) = &self.text_style_refinement {
- text_style.refine(text_style_refinement)
- }
-
- let background = match self.mode {
- EditorMode::SingleLine => cx.theme().system().transparent,
- EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
- EditorMode::Full { .. } => cx.theme().colors().editor_background,
- EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
- };
-
- EditorElement::new(
- &cx.entity(),
- EditorStyle {
- background,
- border: cx.theme().colors().border,
- local_player: cx.theme().players().local(),
- text: text_style,
- scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
- syntax: cx.theme().syntax().clone(),
- status: cx.theme().status().clone(),
- inlay_hints_style: make_inlay_hints_style(cx),
- edit_prediction_styles: make_suggestion_styles(cx),
- unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
- show_underlines: self.diagnostics_enabled(),
- },
- )
+ EditorElement::new(&cx.entity(), self.create_style(cx))
}
}
diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs
index 3c33519370907d3a2f53d63d9e24403c36a5e45a..c97607bb256ff4b4e3054d5de4e3057e58798e73 100644
--- a/crates/editor/src/editor_tests.rs
+++ b/crates/editor/src/editor_tests.rs
@@ -2218,10 +2218,9 @@ async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext)
init_test(cx, |_| {});
let mut cx = EditorTestContext::new(cx).await;
- let line_height = cx.editor(|editor, window, _| {
+ let line_height = cx.update_editor(|editor, window, cx| {
editor
- .style()
- .unwrap()
+ .style(cx)
.text
.line_height_in_pixels(window.rem_size())
});
@@ -2334,10 +2333,9 @@ async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext)
async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorTestContext::new(cx).await;
- let line_height = cx.editor(|editor, window, _| {
+ let line_height = cx.update_editor(|editor, window, cx| {
editor
- .style()
- .unwrap()
+ .style(cx)
.text
.line_height_in_pixels(window.rem_size())
});
@@ -2400,8 +2398,7 @@ async fn test_autoscroll(cx: &mut TestAppContext) {
let line_height = cx.update_editor(|editor, window, cx| {
editor.set_vertical_scroll_margin(2, cx);
editor
- .style()
- .unwrap()
+ .style(cx)
.text
.line_height_in_pixels(window.rem_size())
});
@@ -2480,10 +2477,9 @@ async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorTestContext::new(cx).await;
- let line_height = cx.editor(|editor, window, _cx| {
+ let line_height = cx.update_editor(|editor, window, cx| {
editor
- .style()
- .unwrap()
+ .style(cx)
.text
.line_height_in_pixels(window.rem_size())
});
@@ -28311,7 +28307,8 @@ async fn test_sticky_scroll(cx: &mut TestAppContext) {
let mut sticky_headers = |offset: ScrollOffset| {
cx.update_editor(|e, window, cx| {
e.scroll(gpui::Point { x: 0., y: offset }, None, window, cx);
- EditorElement::sticky_headers(&e, &e.snapshot(window, cx), cx)
+ let style = e.style(cx).clone();
+ EditorElement::sticky_headers(&e, &e.snapshot(window, cx), &style, cx)
.into_iter()
.map(
|StickyHeader {
@@ -28365,10 +28362,9 @@ async fn test_scroll_by_clicking_sticky_header(cx: &mut TestAppContext) {
});
let mut cx = EditorTestContext::new(cx).await;
- let line_height = cx.editor(|editor, window, _cx| {
+ let line_height = cx.update_editor(|editor, window, cx| {
editor
- .style()
- .unwrap()
+ .style(cx)
.text
.line_height_in_pixels(window.rem_size())
});
diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs
index fab51cbef29de436e447c317849ad15aa318c45d..653cf291a7ff2ea79152535392241ae94eaf05f3 100644
--- a/crates/editor/src/element.rs
+++ b/crates/editor/src/element.rs
@@ -11,6 +11,7 @@ use crate::{
SelectedTextHighlight, Selection, SelectionDragState, SelectionEffects, SizingBehavior,
SoftWrap, StickyHeaderExcerpt, ToPoint, ToggleFold, ToggleFoldAll,
code_context_menus::{CodeActionsMenu, MENU_ASIDE_MAX_WIDTH, MENU_ASIDE_MIN_WIDTH, MENU_GAP},
+ column_pixels,
display_map::{
Block, BlockContext, BlockStyle, ChunkRendererId, DisplaySnapshot, EditorMargins,
HighlightKey, HighlightedChunk, ToDisplayPoint,
@@ -2269,7 +2270,8 @@ impl EditorElement {
};
let padding = ProjectSettings::get_global(cx).diagnostics.inline.padding as f32 * em_width;
- let min_x = self.column_pixels(
+ let min_x = column_pixels(
+ &self.style,
ProjectSettings::get_global(cx)
.diagnostics
.inline
@@ -2572,7 +2574,8 @@ impl EditorElement {
let padded_line_end = line_end + padding;
- let min_column_in_pixels = self.column_pixels(
+ let min_column_in_pixels = column_pixels(
+ &self.style,
ProjectSettings::get_global(cx).git.inline_blame.min_column as usize,
window,
);
@@ -2796,7 +2799,7 @@ impl EditorElement {
.enumerate()
.filter_map(|(i, indent_guide)| {
let single_indent_width =
- self.column_pixels(indent_guide.tab_size as usize, window);
+ column_pixels(&self.style, indent_guide.tab_size as usize, window);
let total_width = single_indent_width * indent_guide.depth as f32;
let start_x = Pixels::from(
ScrollOffset::from(content_origin.x + total_width)
@@ -2853,7 +2856,7 @@ impl EditorElement {
.wrap_guides(cx)
.into_iter()
.flat_map(|(guide, active)| {
- let wrap_position = self.column_pixels(guide, window);
+ let wrap_position = column_pixels(&self.style, guide, window);
let wrap_guide_x = wrap_position + horizontal_offset;
let display_wrap_guide = wrap_guide_x >= content_origin
&& wrap_guide_x <= hitbox.bounds.right() - vertical_scrollbar_width;
@@ -4619,6 +4622,7 @@ impl EditorElement {
gutter_dimensions: &GutterDimensions,
gutter_hitbox: &Hitbox,
text_hitbox: &Hitbox,
+ style: &EditorStyle,
window: &mut Window,
cx: &mut App,
) -> Option {
@@ -4626,7 +4630,7 @@ impl EditorElement {
.show_line_numbers
.unwrap_or_else(|| EditorSettings::get_global(cx).gutter.line_numbers);
- let rows = Self::sticky_headers(self.editor.read(cx), snapshot, cx);
+ let rows = Self::sticky_headers(self.editor.read(cx), snapshot, style, cx);
let mut lines = Vec::::new();
@@ -4685,6 +4689,7 @@ impl EditorElement {
pub(crate) fn sticky_headers(
editor: &Editor,
snapshot: &EditorSnapshot,
+ style: &EditorStyle,
cx: &App,
) -> Vec {
let scroll_top = snapshot.scroll_position().y;
@@ -4692,7 +4697,7 @@ impl EditorElement {
let mut end_rows = Vec::::new();
let mut rows = Vec::::new();
- let items = editor.sticky_headers(cx).unwrap_or_default();
+ let items = editor.sticky_headers(style, cx).unwrap_or_default();
for item in items {
let start_point = item.range.start.to_point(snapshot.buffer_snapshot());
@@ -5255,7 +5260,7 @@ impl EditorElement {
) -> Option {
let max_height_in_lines = ((height - POPOVER_Y_PADDING) / line_height).floor() as u32;
self.editor.update(cx, |editor, cx| {
- editor.render_context_menu(&self.style, max_height_in_lines, window, cx)
+ editor.render_context_menu(max_height_in_lines, window, cx)
})
}
@@ -5282,16 +5287,18 @@ impl EditorElement {
window: &mut Window,
cx: &mut App,
) -> Option {
- let position = self.editor.update(cx, |editor, _cx| {
+ let position = self.editor.update(cx, |editor, cx| {
let visible_start_point = editor.display_to_pixel_point(
DisplayPoint::new(visible_range.start, 0),
editor_snapshot,
window,
+ cx,
)?;
let visible_end_point = editor.display_to_pixel_point(
DisplayPoint::new(visible_range.end, 0),
editor_snapshot,
window,
+ cx,
)?;
let mouse_context_menu = editor.mouse_context_menu.as_ref()?;
@@ -5299,7 +5306,8 @@ impl EditorElement {
MenuPosition::PinnedToScreen(point) => (None, point),
MenuPosition::PinnedToEditor { source, offset } => {
let source_display_point = source.to_display_point(editor_snapshot);
- let source_point = editor.to_pixel_point(source, editor_snapshot, window)?;
+ let source_point =
+ editor.to_pixel_point(source, editor_snapshot, window, cx)?;
let position = content_origin + source_point + offset;
(Some(source_display_point), position)
}
@@ -7773,29 +7781,6 @@ impl EditorElement {
});
}
- fn column_pixels(&self, column: usize, window: &Window) -> Pixels {
- let style = &self.style;
- let font_size = style.text.font_size.to_pixels(window.rem_size());
- let layout = window.text_system().shape_line(
- SharedString::from(" ".repeat(column)),
- font_size,
- &[TextRun {
- len: column,
- font: style.text.font(),
- color: Hsla::default(),
- ..Default::default()
- }],
- None,
- );
-
- layout.width
- }
-
- fn max_line_number_width(&self, snapshot: &EditorSnapshot, window: &mut Window) -> Pixels {
- let digit_count = snapshot.widest_line_number().ilog10() + 1;
- self.column_pixels(digit_count as usize, window)
- }
-
fn shape_line_number(
&self,
text: SharedString,
@@ -8943,8 +8928,6 @@ impl Element for EditorElement {
max_lines,
} => {
let editor_handle = cx.entity();
- let max_line_number_width =
- self.max_line_number_width(&editor.snapshot(window, cx), window);
window.request_measured_layout(
Style::default(),
move |known_dimensions, available_space, window, cx| {
@@ -8954,7 +8937,6 @@ impl Element for EditorElement {
editor,
min_lines,
max_lines,
- max_line_number_width,
known_dimensions,
available_space.width,
window,
@@ -9041,15 +9023,10 @@ impl Element for EditorElement {
.gutter_dimensions(
font_id,
font_size,
- self.max_line_number_width(&snapshot, window),
+ style,
+ window,
cx,
- )
- .or_else(|| {
- self.editor.read(cx).offset_content.then(|| {
- GutterDimensions::default_with_margin(font_id, font_size, cx)
- })
- })
- .unwrap_or_default();
+ );
let text_width = bounds.size.width - gutter_dimensions.width;
let settings = EditorSettings::get_global(cx);
@@ -9740,6 +9717,7 @@ impl Element for EditorElement {
&gutter_dimensions,
&gutter_hitbox,
&text_hitbox,
+ &style,
window,
cx,
)
@@ -11456,7 +11434,6 @@ fn compute_auto_height_layout(
editor: &mut Editor,
min_lines: usize,
max_lines: Option,
- max_line_number_width: Pixels,
known_dimensions: Size>,
available_width: AvailableSpace,
window: &mut Window,
@@ -11480,14 +11457,7 @@ fn compute_auto_height_layout(
let em_width = window.text_system().em_width(font_id, font_size).unwrap();
let mut snapshot = editor.snapshot(window, cx);
- let gutter_dimensions = snapshot
- .gutter_dimensions(font_id, font_size, max_line_number_width, cx)
- .or_else(|| {
- editor
- .offset_content
- .then(|| GutterDimensions::default_with_margin(font_id, font_size, cx))
- })
- .unwrap_or_default();
+ let gutter_dimensions = snapshot.gutter_dimensions(font_id, font_size, style, window, cx);
editor.gutter_dimensions = gutter_dimensions;
let text_width = width - gutter_dimensions.width;
@@ -11550,7 +11520,7 @@ mod tests {
});
let cx = &mut VisualTestContext::from_window(*window, cx);
let editor = window.root(cx).unwrap();
- let style = cx.update(|_, cx| editor.read(cx).style().unwrap().clone());
+ let style = cx.update(|_, cx| editor.update(cx, |editor, cx| editor.style(cx).clone()));
for x in 1..=100 {
let (_, state) = cx.draw(
@@ -11578,7 +11548,7 @@ mod tests {
});
let cx = &mut VisualTestContext::from_window(*window, cx);
let editor = window.root(cx).unwrap();
- let style = cx.update(|_, cx| editor.read(cx).style().unwrap().clone());
+ let style = cx.update(|_, cx| editor.update(cx, |editor, cx| editor.style(cx).clone()));
for x in 1..=100 {
let (_, state) = cx.draw(
@@ -11603,7 +11573,7 @@ mod tests {
});
let editor = window.root(cx).unwrap();
- let style = cx.update(|cx| editor.read(cx).style().unwrap().clone());
+ let style = editor.update(cx, |editor, cx| editor.style(cx).clone());
let line_height = window
.update(cx, |_, window, _| {
style.text.line_height_in_pixels(window.rem_size())
@@ -11751,7 +11721,7 @@ mod tests {
});
let editor = window.root(cx).unwrap();
- let style = cx.update(|cx| editor.read(cx).style().unwrap().clone());
+ let style = editor.update(cx, |editor, cx| editor.style(cx).clone());
let line_height = window
.update(cx, |_, window, _| {
style.text.line_height_in_pixels(window.rem_size())
@@ -11878,7 +11848,7 @@ mod tests {
});
let cx = &mut VisualTestContext::from_window(*window, cx);
let editor = window.root(cx).unwrap();
- let style = cx.update(|_, cx| editor.read(cx).style().unwrap().clone());
+ let style = cx.update(|_, cx| editor.update(cx, |editor, cx| editor.style(cx).clone()));
window
.update(cx, |editor, window, cx| {
@@ -11949,7 +11919,7 @@ mod tests {
});
let cx = &mut VisualTestContext::from_window(*window, cx);
let editor = window.root(cx).unwrap();
- let style = cx.update(|_, cx| editor.read(cx).style().unwrap().clone());
+ let style = cx.update(|_, cx| editor.update(cx, |editor, cx| editor.style(cx).clone()));
window
.update(cx, |editor, window, cx| {
editor.set_placeholder_text("hello", window, cx);
@@ -12189,7 +12159,7 @@ mod tests {
let cx = &mut VisualTestContext::from_window(*window, cx);
let editor = window.root(cx).unwrap();
- let style = cx.update(|_, cx| editor.read(cx).style().unwrap().clone());
+ let style = editor.update(cx, |editor, cx| editor.style(cx).clone());
window
.update(cx, |editor, _, cx| {
editor.set_soft_wrap_mode(language_settings::SoftWrap::EditorWidth, cx);
diff --git a/crates/editor/src/mouse_context_menu.rs b/crates/editor/src/mouse_context_menu.rs
index e868b105fac8a8fa87601e2d5bc8578c94bd1940..bda6f1d1d5c893c4fd3fbd4e4d2e72f6ae789361 100644
--- a/crates/editor/src/mouse_context_menu.rs
+++ b/crates/editor/src/mouse_context_menu.rs
@@ -59,7 +59,7 @@ impl MouseContextMenu {
x: editor.gutter_dimensions.width,
y: Pixels::ZERO,
};
- let source_position = editor.to_pixel_point(source, &editor_snapshot, window)?;
+ let source_position = editor.to_pixel_point(source, &editor_snapshot, window, cx)?;
let menu_position = MenuPosition::PinnedToEditor {
source,
offset: position - (source_position + content_origin),
diff --git a/crates/editor/src/test/editor_test_context.rs b/crates/editor/src/test/editor_test_context.rs
index cd45a6ec47ad7631404189194a6a0291a6240647..511629c59d8f61f1c53f5deaa406f113b9dfc3d9 100644
--- a/crates/editor/src/test/editor_test_context.rs
+++ b/crates/editor/src/test/editor_test_context.rs
@@ -283,8 +283,7 @@ impl EditorTestContext {
.head();
let pixel_position = editor.pixel_position_of_newest_cursor.unwrap();
let line_height = editor
- .style()
- .unwrap()
+ .style(cx)
.text
.line_height_in_pixels(window.rem_size());
let snapshot = editor.snapshot(window, cx);
diff --git a/crates/git_ui/src/commit_view.rs b/crates/git_ui/src/commit_view.rs
index 238b0cbf52fdb4312178b868be4b22986ea946c3..30b4e3d986b12f4aba1c5487fac7500bb5cbe670 100644
--- a/crates/git_ui/src/commit_view.rs
+++ b/crates/git_ui/src/commit_view.rs
@@ -1,9 +1,7 @@
use anyhow::{Context as _, Result};
use buffer_diff::{BufferDiff, BufferDiffSnapshot};
use editor::display_map::{BlockPlacement, BlockProperties, BlockStyle};
-use editor::{
- Editor, EditorEvent, ExcerptId, ExcerptRange, MultiBuffer, multibuffer_context_lines,
-};
+use editor::{Editor, EditorEvent, ExcerptRange, MultiBuffer, multibuffer_context_lines};
use git::repository::{CommitDetails, CommitDiff, RepoPath};
use git::{GitHostingProviderRegistry, GitRemote, parse_git_remote_url};
use gpui::{
@@ -13,7 +11,7 @@ use gpui::{
};
use language::{
Anchor, Buffer, Capability, DiskState, File, LanguageRegistry, LineEnding, OffsetRangeExt as _,
- ReplicaId, Rope, TextBuffer,
+ Point, ReplicaId, Rope, TextBuffer,
};
use multi_buffer::PathKey;
use project::{Project, WorktreeId, git_store::Repository};
@@ -70,6 +68,7 @@ struct GitBlob {
display_name: Arc,
}
+const COMMIT_MESSAGE_SORT_PREFIX: u64 = 0;
const FILE_NAMESPACE_SORT_PREFIX: u64 = 1;
impl CommitView {
@@ -147,6 +146,32 @@ impl CommitView {
) -> Self {
let language_registry = project.read(cx).languages().clone();
let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadOnly));
+
+ let message_buffer = cx.new(|cx| {
+ let mut buffer = Buffer::local(commit.message.clone(), cx);
+ buffer.set_capability(Capability::ReadOnly, cx);
+ buffer
+ });
+
+ multibuffer.update(cx, |multibuffer, cx| {
+ let snapshot = message_buffer.read(cx).snapshot();
+ let full_range = Point::zero()..snapshot.max_point();
+ let range = ExcerptRange {
+ context: full_range.clone(),
+ primary: full_range,
+ };
+ multibuffer.set_excerpt_ranges_for_path(
+ PathKey::with_sort_prefix(
+ COMMIT_MESSAGE_SORT_PREFIX,
+ RelPath::unix("commit message").unwrap().into(),
+ ),
+ message_buffer.clone(),
+ &snapshot,
+ vec![range],
+ cx,
+ )
+ });
+
let editor = cx.new(|cx| {
let mut editor =
Editor::for_multibuffer(multibuffer.clone(), Some(project.clone()), window, cx);
@@ -154,9 +179,38 @@ impl CommitView {
editor.disable_inline_diagnostics();
editor.set_show_breakpoints(false, cx);
editor.set_expand_all_diff_hunks(cx);
+ editor.disable_header_for_buffer(message_buffer.read(cx).remote_id(), cx);
+ editor.disable_indent_guides_for_buffer(message_buffer.read(cx).remote_id(), cx);
+
+ editor.insert_blocks(
+ [BlockProperties {
+ placement: BlockPlacement::Above(editor::Anchor::min()),
+ height: Some(1),
+ style: BlockStyle::Sticky,
+ render: Arc::new(|_| gpui::Empty.into_any_element()),
+ priority: 0,
+ }]
+ .into_iter()
+ .chain(
+ editor
+ .buffer()
+ .read(cx)
+ .buffer_anchor_to_anchor(&message_buffer, Anchor::MAX, cx)
+ .map(|anchor| BlockProperties {
+ placement: BlockPlacement::Below(anchor),
+ height: Some(1),
+ style: BlockStyle::Sticky,
+ render: Arc::new(|_| gpui::Empty.into_any_element()),
+ priority: 0,
+ }),
+ ),
+ None,
+ cx,
+ );
editor
});
+
let commit_sha = Arc::::from(commit.sha.as_ref());
let first_worktree_id = project
@@ -166,7 +220,6 @@ impl CommitView {
.map(|worktree| worktree.read(cx).id());
let repository_clone = repository.clone();
- let commit_message = commit.message.clone();
cx.spawn(async move |this, cx| {
for file in commit_diff.files {
@@ -228,59 +281,6 @@ impl CommitView {
})?;
}
- let message_buffer = cx.new(|cx| {
- let mut buffer = Buffer::local(commit_message, cx);
- buffer.set_capability(Capability::ReadOnly, cx);
- buffer
- })?;
-
- this.update(cx, |this, cx| {
- this.multibuffer.update(cx, |multibuffer, cx| {
- let range = ExcerptRange {
- context: Anchor::MIN..Anchor::MAX,
- primary: Anchor::MIN..Anchor::MAX,
- };
- multibuffer.insert_excerpts_after(
- ExcerptId::min(),
- message_buffer.clone(),
- [range],
- cx,
- )
- });
-
- this.editor.update(cx, |editor, cx| {
- editor.disable_header_for_buffer(message_buffer.read(cx).remote_id(), cx);
- editor
- .disable_indent_guides_for_buffer(message_buffer.read(cx).remote_id(), cx);
-
- editor.insert_blocks(
- [BlockProperties {
- placement: BlockPlacement::Above(editor::Anchor::min()),
- height: Some(1),
- style: BlockStyle::Sticky,
- render: Arc::new(|_| gpui::Empty.into_any_element()),
- priority: 0,
- }]
- .into_iter()
- .chain(
- editor
- .buffer()
- .read(cx)
- .buffer_anchor_to_anchor(&message_buffer, Anchor::MAX, cx)
- .map(|anchor| BlockProperties {
- placement: BlockPlacement::Below(anchor),
- height: Some(1),
- style: BlockStyle::Sticky,
- render: Arc::new(|_| gpui::Empty.into_any_element()),
- priority: 0,
- }),
- ),
- None,
- cx,
- )
- });
- })?;
-
anyhow::Ok(())
})
.detach();
@@ -417,12 +417,23 @@ impl CommitView {
None
};
+ let gutter_width = self.editor.update(cx, |editor, cx| {
+ let snapshot = editor.snapshot(window, cx);
+ let style = editor.style(cx);
+ let font_id = window.text_system().resolve_font(&style.text.font());
+ let font_size = style.text.font_size.to_pixels(window.rem_size());
+ snapshot
+ .gutter_dimensions(font_id, font_size, style, window, cx)
+ .full_width()
+ });
+
h_flex()
.border_b_1()
.border_color(cx.theme().colors().border_variant)
+ .w_full()
.child(
h_flex()
- .w(self.editor.read(cx).last_gutter_dimensions().full_width())
+ .w(gutter_width)
.justify_center()
.child(self.render_commit_avatar(&commit.sha, rems_from_px(48.), window, cx)),
)
@@ -1011,7 +1022,9 @@ impl Render for CommitView {
.size_full()
.bg(cx.theme().colors().editor_background)
.child(self.render_header(window, cx))
- .child(div().flex_grow().child(self.editor.clone()))
+ .when(!self.editor.read(cx).is_empty(cx), |this| {
+ this.child(div().flex_grow().child(self.editor.clone()))
+ })
}
}
diff --git a/crates/vim/src/normal/scroll.rs b/crates/vim/src/normal/scroll.rs
index ff884e3b7393b39b86114338fe2af11e384e1fa0..73209c88735a59bb2dc5c2b73bb3ba0c7d03dd56 100644
--- a/crates/vim/src/normal/scroll.rs
+++ b/crates/vim/src/normal/scroll.rs
@@ -294,11 +294,10 @@ mod test {
async fn test_scroll(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
- let (line_height, visible_line_count) = cx.editor(|editor, window, _cx| {
+ let (line_height, visible_line_count) = cx.update_editor(|editor, window, cx| {
(
editor
- .style()
- .unwrap()
+ .style(cx)
.text
.line_height_in_pixels(window.rem_size()),
editor.visible_line_count().unwrap(),
diff --git a/crates/vim/src/test.rs b/crates/vim/src/test.rs
index 4294b5e1dbdf1a287909bd3ab5770dfcd718f98d..4c61479157268e4f0276bddf9dd1eb913284d27e 100644
--- a/crates/vim/src/test.rs
+++ b/crates/vim/src/test.rs
@@ -2399,7 +2399,7 @@ async fn test_clipping_on_mode_change(cx: &mut gpui::TestAppContext) {
.end;
editor.last_bounds().unwrap().origin
+ editor
- .display_to_pixel_point(current_head, &snapshot, window)
+ .display_to_pixel_point(current_head, &snapshot, window, cx)
.unwrap()
});
pixel_position.x += px(100.);
diff --git a/crates/vim/src/test/neovim_backed_test_context.rs b/crates/vim/src/test/neovim_backed_test_context.rs
index 21cdda111c4fdacaf0871dd087bca01de6f83957..d20464ccc4b36c8f7024db6bd63558a6292e7c68 100644
--- a/crates/vim/src/test/neovim_backed_test_context.rs
+++ b/crates/vim/src/test/neovim_backed_test_context.rs
@@ -304,11 +304,10 @@ impl NeovimBackedTestContext {
self.neovim.set_option(&format!("scrolloff={}", 3)).await;
// +2 to account for the vim command UI at the bottom.
self.neovim.set_option(&format!("lines={}", rows + 2)).await;
- let (line_height, visible_line_count) = self.editor(|editor, window, _cx| {
+ let (line_height, visible_line_count) = self.update_editor(|editor, window, cx| {
(
editor
- .style()
- .unwrap()
+ .style(cx)
.text
.line_height_in_pixels(window.rem_size()),
editor.visible_line_count().unwrap(),
diff --git a/crates/zed/src/zed/quick_action_bar.rs b/crates/zed/src/zed/quick_action_bar.rs
index 402881680232ea636f7cb105db759f417a435145..2a52cc697249cb1f8eb280a48c89ff5aadf6fd85 100644
--- a/crates/zed/src/zed/quick_action_bar.rs
+++ b/crates/zed/src/zed/quick_action_bar.rs
@@ -174,17 +174,13 @@ impl Render for QuickActionBar {
.as_ref()
.is_some_and(|menu| matches!(menu.origin(), ContextMenuOrigin::QuickActionBar))
};
- let code_action_element = if is_deployed {
- editor.update(cx, |editor, cx| {
- if let Some(style) = editor.style() {
- editor.render_context_menu(style, MAX_CODE_ACTION_MENU_LINES, window, cx)
- } else {
- None
- }
+ let code_action_element = is_deployed
+ .then(|| {
+ editor.update(cx, |editor, cx| {
+ editor.render_context_menu(MAX_CODE_ACTION_MENU_LINES, window, cx)
+ })
})
- } else {
- None
- };
+ .flatten();
v_flex()
.child(
IconButton::new("toggle_code_actions_icon", IconName::BoltOutlined)
From 4106c8a1884926b86a3cc8df7ff613adac61dd6a Mon Sep 17 00:00:00 2001
From: Finn Evers
Date: Wed, 10 Dec 2025 16:12:41 +0100
Subject: [PATCH 35/48] Disable OmniSharp by default for C# files (#44427)
In preparation for https://github.com/zed-extensions/csharp/pull/11. Do
not merge before that PR is published.
Release Notes:
- Added support for Roslyn in C# files. Roslyn will now be the default
language server for C#
---
assets/settings/default.json | 3 +++
1 file changed, 3 insertions(+)
diff --git a/assets/settings/default.json b/assets/settings/default.json
index f687778d7bd7fc0f6d66404199c34fac8d77e7a8..dd51099799abb49325e9a2747ee18f9837e4409b 100644
--- a/assets/settings/default.json
+++ b/assets/settings/default.json
@@ -1810,6 +1810,9 @@
"allowed": false
}
},
+ "CSharp": {
+ "language_servers": ["roslyn", "!omnisharp", "..."]
+ },
"CSS": {
"prettier": {
"allowed": true
From f48aa252f896933206467077ab47db6d35e7556b Mon Sep 17 00:00:00 2001
From: "Joseph T. Lyons"
Date: Wed, 10 Dec 2025 10:28:39 -0500
Subject: [PATCH 36/48] Bump Zed to v0.218 (#44551)
Release Notes:
- N/A
---
Cargo.lock | 2 +-
crates/zed/Cargo.toml | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 7ae8d55a2484970b0ae1ad0631acadf22d106e46..cfe2bba0f47aa8ce8c3f69dec74c2286bd034501 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -20469,7 +20469,7 @@ dependencies = [
[[package]]
name = "zed"
-version = "0.217.0"
+version = "0.218.0"
dependencies = [
"acp_tools",
"activity_indicator",
diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml
index a9a8ba87c645e99a68409865a95737e3222c87b3..92a274da9640bbe9ee3afefeacd7566c853bdd2d 100644
--- a/crates/zed/Cargo.toml
+++ b/crates/zed/Cargo.toml
@@ -2,7 +2,7 @@
description = "The fast, collaborative code editor."
edition.workspace = true
name = "zed"
-version = "0.217.0"
+version = "0.218.0"
publish.workspace = true
license = "GPL-3.0-or-later"
authors = ["Zed Team "]
From f5babf96e1682c16a9dc2356d1aaa31df185fe7f Mon Sep 17 00:00:00 2001
From: Bennet Bo Fenner
Date: Wed, 10 Dec 2025 17:30:10 +0100
Subject: [PATCH 37/48] agent_ui: Fix `project path not found` error when
pasting code from other project (#44555)
The problem with inserting the absolute paths is that the agent will try
to read them. However, we don't allow the agent to read files outside
the current project. For now, we will only insert the crease in case the
code that is getting pasted is from the same project
Release Notes:
- Fixed an issue where pasting code into the agent panel from another
window would show an error
---
crates/agent_ui/src/acp/message_editor.rs | 20 +++++++++++++++++++-
1 file changed, 19 insertions(+), 1 deletion(-)
diff --git a/crates/agent_ui/src/acp/message_editor.rs b/crates/agent_ui/src/acp/message_editor.rs
index bc293c98a84540c1e00d9895be2cc05b0bdd08a5..5e9c55cc56868ac2e7db65043d13eb46efcd89a6 100644
--- a/crates/agent_ui/src/acp/message_editor.rs
+++ b/crates/agent_ui/src/acp/message_editor.rs
@@ -565,8 +565,26 @@ impl MessageEditor {
if let Some((workspace, selections)) =
self.workspace.upgrade().zip(editor_clipboard_selections)
{
- cx.stop_propagation();
+ let Some(first_selection) = selections.first() else {
+ return;
+ };
+ if let Some(file_path) = &first_selection.file_path {
+ // In case someone pastes selections from another window
+ // with a different project, we don't want to insert the
+ // crease (containing the absolute path) since the agent
+ // cannot access files outside the project.
+ let is_in_project = workspace
+ .read(cx)
+ .project()
+ .read(cx)
+ .project_path_for_absolute_path(file_path, cx)
+ .is_some();
+ if !is_in_project {
+ return;
+ }
+ }
+ cx.stop_propagation();
let insertion_target = self
.editor
.read(cx)
From 326ebb523049b8b512c962f56630063f825482c9 Mon Sep 17 00:00:00 2001
From: Mayank Verma
Date: Wed, 10 Dec 2025 22:04:49 +0530
Subject: [PATCH 38/48] git: Fix failing commits when hook command is not
available (#43993)
---
crates/git/src/repository.rs | 32 +++++++++++++++++++++++++++++++-
1 file changed, 31 insertions(+), 1 deletion(-)
diff --git a/crates/git/src/repository.rs b/crates/git/src/repository.rs
index 70cbf6e3c58b7d8f6b690a554370d34262f541e3..6d0dc8de9e2fbd5066f6abf4e4eeaddeb8cd33bf 100644
--- a/crates/git/src/repository.rs
+++ b/crates/git/src/repository.rs
@@ -2295,8 +2295,38 @@ impl GitRepository for RealGitRepository {
self.executor
.spawn(async move {
let working_directory = working_directory?;
- let git = GitBinary::new(git_binary_path, working_directory, executor)
+ let git = GitBinary::new(git_binary_path, working_directory.clone(), executor)
.envs(HashMap::clone(&env));
+
+ let output = git.run(&["help", "-a"]).await?;
+ if !output.lines().any(|line| line.trim().starts_with("hook ")) {
+ log::warn!(
+ "git hook command not available, running the {} hook manually",
+ hook.as_str()
+ );
+
+ let hook_abs_path = working_directory
+ .join(".git")
+ .join("hooks")
+ .join(hook.as_str());
+ if hook_abs_path.is_file() {
+ let output = new_smol_command(&hook_abs_path)
+ .envs(env.iter())
+ .current_dir(&working_directory)
+ .output()
+ .await?;
+
+ anyhow::ensure!(
+ output.status.success(),
+ "{} hook failed:\n{}",
+ hook.as_str(),
+ String::from_utf8_lossy(&output.stderr)
+ );
+ }
+
+ return Ok(());
+ }
+
git.run(&["hook", "run", "--ignore-missing", hook.as_str()])
.await?;
Ok(())
From 5b309ef986c8efd2a53abfe4099bda901f8e263d Mon Sep 17 00:00:00 2001
From: Ben Brandt
Date: Wed, 10 Dec 2025 17:48:08 +0100
Subject: [PATCH 39/48] acp: Better telemetry IDs for ACP agents (#44544)
We were defining these in multiple places and also weren't leveraging
the ids the agents were already providing.
This should make sure we use them consistently and avoid issues in the
future.
Release Notes:
- N/A
---
crates/acp_thread/src/acp_thread.rs | 13 +-
crates/acp_thread/src/connection.rs | 6 +-
crates/action_log/src/action_log.rs | 2 +-
crates/agent/src/agent.rs | 4 +-
crates/agent/src/native_agent_server.rs | 4 -
crates/agent_servers/src/acp.rs | 16 +-
crates/agent_servers/src/agent_servers.rs | 1 -
crates/agent_servers/src/claude.rs | 6 -
crates/agent_servers/src/codex.rs | 6 -
crates/agent_servers/src/custom.rs | 10 +-
crates/agent_servers/src/gemini.rs | 6 -
crates/agent_ui/src/acp/thread_view.rs | 194 +++++++++++-----------
crates/agent_ui/src/agent_panel.rs | 6 +-
crates/agent_ui/src/agent_ui.rs | 10 --
14 files changed, 123 insertions(+), 161 deletions(-)
diff --git a/crates/acp_thread/src/acp_thread.rs b/crates/acp_thread/src/acp_thread.rs
index b96ef1d898086a0b4b9336a21d1d8369fea4ad6c..53294a963d9d230c9b06372c26591ede0434ab28 100644
--- a/crates/acp_thread/src/acp_thread.rs
+++ b/crates/acp_thread/src/acp_thread.rs
@@ -1372,7 +1372,7 @@ impl AcpThread {
let path_style = self.project.read(cx).path_style(cx);
let id = update.tool_call_id.clone();
- let agent = self.connection().telemetry_id();
+ let agent_telemetry_id = self.connection().telemetry_id();
let session = self.session_id();
if let ToolCallStatus::Completed | ToolCallStatus::Failed = status {
let status = if matches!(status, ToolCallStatus::Completed) {
@@ -1380,7 +1380,12 @@ impl AcpThread {
} else {
"failed"
};
- telemetry::event!("Agent Tool Call Completed", agent, session, status);
+ telemetry::event!(
+ "Agent Tool Call Completed",
+ agent_telemetry_id,
+ session,
+ status
+ );
}
if let Some(ix) = self.index_for_tool_call(&id) {
@@ -3556,8 +3561,8 @@ mod tests {
}
impl AgentConnection for FakeAgentConnection {
- fn telemetry_id(&self) -> &'static str {
- "fake"
+ fn telemetry_id(&self) -> SharedString {
+ "fake".into()
}
fn auth_methods(&self) -> &[acp::AuthMethod] {
diff --git a/crates/acp_thread/src/connection.rs b/crates/acp_thread/src/connection.rs
index 8213786a182e1d93d1bfc1a8918a8830ecaa754b..3c8c56b2c02cd775be030cb4c4b05a9c75f0d10f 100644
--- a/crates/acp_thread/src/connection.rs
+++ b/crates/acp_thread/src/connection.rs
@@ -20,7 +20,7 @@ impl UserMessageId {
}
pub trait AgentConnection {
- fn telemetry_id(&self) -> &'static str;
+ fn telemetry_id(&self) -> SharedString;
fn new_thread(
self: Rc,
@@ -322,8 +322,8 @@ mod test_support {
}
impl AgentConnection for StubAgentConnection {
- fn telemetry_id(&self) -> &'static str {
- "stub"
+ fn telemetry_id(&self) -> SharedString {
+ "stub".into()
}
fn auth_methods(&self) -> &[acp::AuthMethod] {
diff --git a/crates/action_log/src/action_log.rs b/crates/action_log/src/action_log.rs
index 80c9438bc9f8051cb58357e56a82b5307fd20b75..6eb18a4f12325f0c181928f99b4eb921265dbf9c 100644
--- a/crates/action_log/src/action_log.rs
+++ b/crates/action_log/src/action_log.rs
@@ -777,7 +777,7 @@ impl ActionLog {
#[derive(Clone)]
pub struct ActionLogTelemetry {
- pub agent_telemetry_id: &'static str,
+ pub agent_telemetry_id: SharedString,
pub session_id: Arc,
}
diff --git a/crates/agent/src/agent.rs b/crates/agent/src/agent.rs
index aec0767c25422dbfeae6fdddcf33e54f8045995c..cf98a24ac52579fc65bdbcc3444615c89625812a 100644
--- a/crates/agent/src/agent.rs
+++ b/crates/agent/src/agent.rs
@@ -947,8 +947,8 @@ impl acp_thread::AgentModelSelector for NativeAgentModelSelector {
}
impl acp_thread::AgentConnection for NativeAgentConnection {
- fn telemetry_id(&self) -> &'static str {
- "zed"
+ fn telemetry_id(&self) -> SharedString {
+ "zed".into()
}
fn new_thread(
diff --git a/crates/agent/src/native_agent_server.rs b/crates/agent/src/native_agent_server.rs
index 4c78c5a3f85b6628f9784fe7ecbadc8531b017d0..a9ade8141a678329e0dd8dad9808e55eee3c382b 100644
--- a/crates/agent/src/native_agent_server.rs
+++ b/crates/agent/src/native_agent_server.rs
@@ -21,10 +21,6 @@ impl NativeAgentServer {
}
impl AgentServer for NativeAgentServer {
- fn telemetry_id(&self) -> &'static str {
- "zed"
- }
-
fn name(&self) -> SharedString {
"Zed Agent".into()
}
diff --git a/crates/agent_servers/src/acp.rs b/crates/agent_servers/src/acp.rs
index 153357a79afdaeeb4bf4c9e2b48bee32245ba2ef..138353592e8c0f185d14398544c226e9381e17cd 100644
--- a/crates/agent_servers/src/acp.rs
+++ b/crates/agent_servers/src/acp.rs
@@ -29,7 +29,7 @@ pub struct UnsupportedVersion;
pub struct AcpConnection {
server_name: SharedString,
- telemetry_id: &'static str,
+ telemetry_id: SharedString,
connection: Rc,
sessions: Rc>>,
auth_methods: Vec,
@@ -54,7 +54,6 @@ pub struct AcpSession {
pub async fn connect(
server_name: SharedString,
- telemetry_id: &'static str,
command: AgentServerCommand,
root_dir: &Path,
default_mode: Option,
@@ -64,7 +63,6 @@ pub async fn connect(
) -> Result> {
let conn = AcpConnection::stdio(
server_name,
- telemetry_id,
command.clone(),
root_dir,
default_mode,
@@ -81,7 +79,6 @@ const MINIMUM_SUPPORTED_VERSION: acp::ProtocolVersion = acp::ProtocolVersion::V1
impl AcpConnection {
pub async fn stdio(
server_name: SharedString,
- telemetry_id: &'static str,
command: AgentServerCommand,
root_dir: &Path,
default_mode: Option,
@@ -199,6 +196,13 @@ impl AcpConnection {
return Err(UnsupportedVersion.into());
}
+ let telemetry_id = response
+ .agent_info
+ // Use the one the agent provides if we have one
+ .map(|info| info.name.into())
+ // Otherwise, just use the name
+ .unwrap_or_else(|| server_name.clone());
+
Ok(Self {
auth_methods: response.auth_methods,
root_dir: root_dir.to_owned(),
@@ -233,8 +237,8 @@ impl Drop for AcpConnection {
}
impl AgentConnection for AcpConnection {
- fn telemetry_id(&self) -> &'static str {
- self.telemetry_id
+ fn telemetry_id(&self) -> SharedString {
+ self.telemetry_id.clone()
}
fn new_thread(
diff --git a/crates/agent_servers/src/agent_servers.rs b/crates/agent_servers/src/agent_servers.rs
index cf03b71a78b358d7b110c450f769f9645094baaa..46e8508e44f07e4fb3d613e30387d5afd3f38423 100644
--- a/crates/agent_servers/src/agent_servers.rs
+++ b/crates/agent_servers/src/agent_servers.rs
@@ -56,7 +56,6 @@ impl AgentServerDelegate {
pub trait AgentServer: Send {
fn logo(&self) -> ui::IconName;
fn name(&self) -> SharedString;
- fn telemetry_id(&self) -> &'static str;
fn default_mode(&self, _cx: &mut App) -> Option {
None
}
diff --git a/crates/agent_servers/src/claude.rs b/crates/agent_servers/src/claude.rs
index f49dce59c4282eb278e16ef664c75ed56652de2e..e67ddd5c0698758fdec7c7796b26a1351e9990e5 100644
--- a/crates/agent_servers/src/claude.rs
+++ b/crates/agent_servers/src/claude.rs
@@ -22,10 +22,6 @@ pub struct AgentServerLoginCommand {
}
impl AgentServer for ClaudeCode {
- fn telemetry_id(&self) -> &'static str {
- "claude-code"
- }
-
fn name(&self) -> SharedString {
"Claude Code".into()
}
@@ -83,7 +79,6 @@ impl AgentServer for ClaudeCode {
cx: &mut App,
) -> Task, Option)>> {
let name = self.name();
- let telemetry_id = self.telemetry_id();
let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().into_owned());
let is_remote = delegate.project.read(cx).is_via_remote_server();
let store = delegate.store.downgrade();
@@ -108,7 +103,6 @@ impl AgentServer for ClaudeCode {
.await?;
let connection = crate::acp::connect(
name,
- telemetry_id,
command,
root_dir.as_ref(),
default_mode,
diff --git a/crates/agent_servers/src/codex.rs b/crates/agent_servers/src/codex.rs
index d14d2f0c9aeb499624943962437821d571bc0299..c2b308e48b7a984b0374272c0059286e933916b3 100644
--- a/crates/agent_servers/src/codex.rs
+++ b/crates/agent_servers/src/codex.rs
@@ -23,10 +23,6 @@ pub(crate) mod tests {
}
impl AgentServer for Codex {
- fn telemetry_id(&self) -> &'static str {
- "codex"
- }
-
fn name(&self) -> SharedString {
"Codex".into()
}
@@ -84,7 +80,6 @@ impl AgentServer for Codex {
cx: &mut App,
) -> Task, Option)>> {
let name = self.name();
- let telemetry_id = self.telemetry_id();
let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().into_owned());
let is_remote = delegate.project.read(cx).is_via_remote_server();
let store = delegate.store.downgrade();
@@ -110,7 +105,6 @@ impl AgentServer for Codex {
let connection = crate::acp::connect(
name,
- telemetry_id,
command,
root_dir.as_ref(),
default_mode,
diff --git a/crates/agent_servers/src/custom.rs b/crates/agent_servers/src/custom.rs
index 634b31e90267e064f0d0df9b6014d279a44a7986..4cde4d4b9f5040f9b51365defa9a43bd0ab2b082 100644
--- a/crates/agent_servers/src/custom.rs
+++ b/crates/agent_servers/src/custom.rs
@@ -1,4 +1,4 @@
-use crate::{AgentServerDelegate, load_proxy_env};
+use crate::{AgentServer, AgentServerDelegate, load_proxy_env};
use acp_thread::AgentConnection;
use agent_client_protocol as acp;
use anyhow::{Context as _, Result};
@@ -20,11 +20,7 @@ impl CustomAgentServer {
}
}
-impl crate::AgentServer for CustomAgentServer {
- fn telemetry_id(&self) -> &'static str {
- "custom"
- }
-
+impl AgentServer for CustomAgentServer {
fn name(&self) -> SharedString {
self.name.clone()
}
@@ -112,7 +108,6 @@ impl crate::AgentServer for CustomAgentServer {
cx: &mut App,
) -> Task, Option)>> {
let name = self.name();
- let telemetry_id = self.telemetry_id();
let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().into_owned());
let is_remote = delegate.project.read(cx).is_via_remote_server();
let default_mode = self.default_mode(cx);
@@ -139,7 +134,6 @@ impl crate::AgentServer for CustomAgentServer {
.await?;
let connection = crate::acp::connect(
name,
- telemetry_id,
command,
root_dir.as_ref(),
default_mode,
diff --git a/crates/agent_servers/src/gemini.rs b/crates/agent_servers/src/gemini.rs
index c1b2efb081551f82752dc15a909eec64ff78d94e..5fea74746aec73f3ea7bb33562244e4a6eea5ba7 100644
--- a/crates/agent_servers/src/gemini.rs
+++ b/crates/agent_servers/src/gemini.rs
@@ -12,10 +12,6 @@ use project::agent_server_store::GEMINI_NAME;
pub struct Gemini;
impl AgentServer for Gemini {
- fn telemetry_id(&self) -> &'static str {
- "gemini-cli"
- }
-
fn name(&self) -> SharedString {
"Gemini CLI".into()
}
@@ -31,7 +27,6 @@ impl AgentServer for Gemini {
cx: &mut App,
) -> Task, Option)>> {
let name = self.name();
- let telemetry_id = self.telemetry_id();
let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().into_owned());
let is_remote = delegate.project.read(cx).is_via_remote_server();
let store = delegate.store.downgrade();
@@ -66,7 +61,6 @@ impl AgentServer for Gemini {
let connection = crate::acp::connect(
name,
- telemetry_id,
command,
root_dir.as_ref(),
default_mode,
diff --git a/crates/agent_ui/src/acp/thread_view.rs b/crates/agent_ui/src/acp/thread_view.rs
index 36fb7e9097488f8070b740a63ed67ee74445602a..63ea9eb279d26ff610c12f9785ef882be61f5e26 100644
--- a/crates/agent_ui/src/acp/thread_view.rs
+++ b/crates/agent_ui/src/acp/thread_view.rs
@@ -170,7 +170,7 @@ impl ThreadFeedbackState {
}
}
let session_id = thread.read(cx).session_id().clone();
- let agent = thread.read(cx).connection().telemetry_id();
+ let agent_telemetry_id = thread.read(cx).connection().telemetry_id();
let task = telemetry.thread_data(&session_id, cx);
let rating = match feedback {
ThreadFeedback::Positive => "positive",
@@ -180,7 +180,7 @@ impl ThreadFeedbackState {
let thread = task.await?;
telemetry::event!(
"Agent Thread Rated",
- agent = agent,
+ agent = agent_telemetry_id,
session_id = session_id,
rating = rating,
thread = thread
@@ -207,13 +207,13 @@ impl ThreadFeedbackState {
self.comments_editor.take();
let session_id = thread.read(cx).session_id().clone();
- let agent = thread.read(cx).connection().telemetry_id();
+ let agent_telemetry_id = thread.read(cx).connection().telemetry_id();
let task = telemetry.thread_data(&session_id, cx);
cx.background_spawn(async move {
let thread = task.await?;
telemetry::event!(
"Agent Thread Feedback Comments",
- agent = agent,
+ agent = agent_telemetry_id,
session_id = session_id,
comments = comments,
thread = thread
@@ -333,6 +333,7 @@ impl AcpThreadView {
project: Entity,
history_store: Entity,
prompt_store: Option>,
+ track_load_event: bool,
window: &mut Window,
cx: &mut Context,
) -> Self {
@@ -391,8 +392,9 @@ impl AcpThreadView {
),
];
- let show_codex_windows_warning = crate::ExternalAgent::parse_built_in(agent.as_ref())
- == Some(crate::ExternalAgent::Codex);
+ let show_codex_windows_warning = cfg!(windows)
+ && project.read(cx).is_local()
+ && agent.clone().downcast::().is_some();
Self {
agent: agent.clone(),
@@ -404,6 +406,7 @@ impl AcpThreadView {
resume_thread.clone(),
workspace.clone(),
project.clone(),
+ track_load_event,
window,
cx,
),
@@ -448,6 +451,7 @@ impl AcpThreadView {
self.resume_thread_metadata.clone(),
self.workspace.clone(),
self.project.clone(),
+ true,
window,
cx,
);
@@ -461,6 +465,7 @@ impl AcpThreadView {
resume_thread: Option,
workspace: WeakEntity,
project: Entity