From 1c050624823d2aa66d57c845662405d9d44556f6 Mon Sep 17 00:00:00 2001 From: Danilo Leal <67129314+danilo-leal@users.noreply.github.com> Date: Mon, 30 Jun 2025 10:32:27 -0300 Subject: [PATCH 001/134] agent: Always focus on to the active model in the picker (#33567) Release Notes: - agent: Improved the model selector by ensuring the active model is always focused on open. --- crates/agent_ui/src/language_model_selector.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/crates/agent_ui/src/language_model_selector.rs b/crates/agent_ui/src/language_model_selector.rs index d9d11231edbe128fcfd9a486278ed8e1542b4397..55c0974fc1d2bdbd65e0b6d746abf7f4ef10654d 100644 --- a/crates/agent_ui/src/language_model_selector.rs +++ b/crates/agent_ui/src/language_model_selector.rs @@ -399,7 +399,7 @@ impl PickerDelegate for LanguageModelPickerDelegate { cx: &mut Context>, ) -> Task<()> { let all_models = self.all_models.clone(); - let current_index = self.selected_index; + let active_model = (self.get_active_model)(cx); let bg_executor = cx.background_executor(); let language_model_registry = LanguageModelRegistry::global(cx); @@ -441,12 +441,9 @@ impl PickerDelegate for LanguageModelPickerDelegate { cx.spawn_in(window, async move |this, cx| { this.update_in(cx, |this, window, cx| { this.delegate.filtered_entries = filtered_models.entries(); - // Preserve selection focus - let new_index = if current_index >= this.delegate.filtered_entries.len() { - 0 - } else { - current_index - }; + // Finds the currently selected model in the list + let new_index = + Self::get_active_model_index(&this.delegate.filtered_entries, active_model); this.set_selected_index(new_index, Some(picker::Direction::Down), true, window, cx); cx.notify(); }) From e37ef2a99110eea68ebb25dcb458ee4eda1d33b1 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 30 Jun 2025 16:40:31 +0300 Subject: [PATCH 002/134] Use more generic error messages in gpui (#33651) Follow-up of https://github.com/zed-industries/zed/pull/32537 Release Notes: - N/A --- crates/gpui/src/platform.rs | 2 +- crates/gpui/src/platform/windows/window.rs | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index ac21c5dea12c783790d3877735a7074f7dbd9c95..277f2d9ab8762c43473c7c07ef58a3c5188d091b 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -151,7 +151,7 @@ pub fn guess_compositor() -> &'static str { pub(crate) fn current_platform(_headless: bool) -> Rc { Rc::new( WindowsPlatform::new() - .inspect_err(|err| show_error("Error: Zed failed to launch", err.to_string())) + .inspect_err(|err| show_error("Failed to launch", err.to_string())) .unwrap(), ) } diff --git a/crates/gpui/src/platform/windows/window.rs b/crates/gpui/src/platform/windows/window.rs index c363d5854deccbb6d0f29391b2d47316f228b57d..27c843932bb2a38f37f3b01b354a7eed7f8354fe 100644 --- a/crates/gpui/src/platform/windows/window.rs +++ b/crates/gpui/src/platform/windows/window.rs @@ -1299,12 +1299,8 @@ mod windows_renderer { size: Default::default(), transparent, }; - BladeRenderer::new(context, &raw, config).inspect_err(|err| { - show_error( - "Error: Zed failed to initialize BladeRenderer", - err.to_string(), - ) - }) + BladeRenderer::new(context, &raw, config) + .inspect_err(|err| show_error("Failed to initialize BladeRenderer", err.to_string())) } struct RawWindow { From f106ea7641c585d31ff9703b270d7f39d602f8ec Mon Sep 17 00:00:00 2001 From: Danilo Leal <67129314+danilo-leal@users.noreply.github.com> Date: Mon, 30 Jun 2025 10:42:38 -0300 Subject: [PATCH 003/134] docs: Update custom MCP format template (#33649) To match the new format added in https://github.com/zed-industries/zed/pull/33539. Release Notes: - N/A --- docs/src/ai/mcp.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/docs/src/ai/mcp.md b/docs/src/ai/mcp.md index 8fbea0fef11fa6b18480ea1a1b73501fcea59c89..202b14102209ae8d3dbf338ff11bb8b443432cf9 100644 --- a/docs/src/ai/mcp.md +++ b/docs/src/ai/mcp.md @@ -40,13 +40,11 @@ You can connect them by adding their commands directly to your `settings.json`, ```json { "context_servers": { - "some-context-server": { + "your-mcp-server": { "source": "custom", - "command": { - "path": "some-command", - "args": ["arg-1", "arg-2"], - "env": {} - } + "command": "some-command", + "args": ["arg-1", "arg-2"], + "env": {} } } } From 22ab4c53d119213454a6e2fad2bd44f47c9c5af5 Mon Sep 17 00:00:00 2001 From: Peter Tripp Date: Mon, 30 Jun 2025 10:03:09 -0400 Subject: [PATCH 004/134] R docs: Remove non-working configuration (#33654) This config was meant to be commented out in #33594 because it does not work. Release Notes: - N/A --- docs/src/languages/r.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/src/languages/r.md b/docs/src/languages/r.md index 6b40f6970dda16b9405bfd15842452fbe28fe947..226a6f866846da43a3f32668dd19e1efb3f657ce 100644 --- a/docs/src/languages/r.md +++ b/docs/src/languages/r.md @@ -63,6 +63,9 @@ See [Using lintr](https://lintr.r-lib.org/articles/lintr.html) for a complete li `REditorSupport/languageserver` bundles support for [r-lib/styler](https://github.com/r-lib/styler) as a formatter. See [Customizing Styler](https://cran.r-project.org/web/packages/styler/vignettes/customizing_styler.html) for more information on how to customize its behavior. + + "] -autoclose_before = "}])>" +autoclose_before = ";:.,=}])>" brackets = [ { start = "{", end = "}", close = true, newline = true }, { start = "[", end = "]", close = true, newline = true }, From 0629804390cc2e6c7d35b3f4f88154286ccf0c43 Mon Sep 17 00:00:00 2001 From: Bennet Bo Fenner Date: Tue, 1 Jul 2025 13:32:14 +0200 Subject: [PATCH 037/134] agent: Clarify upgrade path when starting trial (#33706) Release Notes: - N/A --- crates/language_models/src/provider/cloud.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/crates/language_models/src/provider/cloud.rs b/crates/language_models/src/provider/cloud.rs index 9472fed18e78a3005ea3ceb7e373292ecdc2b4aa..5417c329abaa107f9750c8a35a791afcc11b87ae 100644 --- a/crates/language_models/src/provider/cloud.rs +++ b/crates/language_models/src/provider/cloud.rs @@ -1106,10 +1106,17 @@ impl RenderOnce for ZedAIConfiguration { .on_click(|_, _, cx| cx.open_url(ZED_PRICING_URL)), ) .child( - Button::new("upgrade", "Upgrade") - .style(ButtonStyle::Subtle) - .color(Color::Accent) - .on_click(|_, _, cx| cx.open_url(&zed_urls::account_url(cx))), + Button::new( + "upgrade", + if self.plan.is_none() && self.eligible_for_trial { + "Start Trial" + } else { + "Upgrade" + }, + ) + .style(ButtonStyle::Subtle) + .color(Color::Accent) + .on_click(|_, _, cx| cx.open_url(&zed_urls::account_url(cx))), ) }; From a5b2428897bc441bd7ac8bf9a62368622c12fbe5 Mon Sep 17 00:00:00 2001 From: Cole Miller Date: Tue, 1 Jul 2025 07:34:50 -0400 Subject: [PATCH 038/134] debugger: Fix Go locator for subtests (#33694) Closes #33054 Release Notes: - Fixed debugging Go subtests. --- crates/project/src/debugger/locators/go.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/crates/project/src/debugger/locators/go.rs b/crates/project/src/debugger/locators/go.rs index 79d7a1721c5f4013443bdbda7970571377c6a65d..61436fce8f3659d4b12c3010b82e0d845654c4e9 100644 --- a/crates/project/src/debugger/locators/go.rs +++ b/crates/project/src/debugger/locators/go.rs @@ -117,7 +117,20 @@ impl DapLocator for GoLocator { // HACK: tasks assume that they are run in a shell context, // so the -run regex has escaped specials. Delve correctly // handles escaping, so we undo that here. - if arg.starts_with("\\^") && arg.ends_with("\\$") { + if let Some((left, right)) = arg.split_once("/") + && left.starts_with("\\^") + && left.ends_with("\\$") + && right.starts_with("\\^") + && right.ends_with("\\$") + { + let mut left = left[1..left.len() - 2].to_string(); + left.push('$'); + + let mut right = right[1..right.len() - 2].to_string(); + right.push('$'); + + args.push(format!("{left}/{right}")); + } else if arg.starts_with("\\^") && arg.ends_with("\\$") { let mut arg = arg[1..arg.len() - 2].to_string(); arg.push('$'); args.push(arg); From 42f788185a568b43fa6845968bff59a73cd1fbfd Mon Sep 17 00:00:00 2001 From: Danilo Leal <67129314+danilo-leal@users.noreply.github.com> Date: Tue, 1 Jul 2025 09:00:20 -0300 Subject: [PATCH 039/134] agent: Use callout for displaying errors instead of toasts (#33680) This PR makes all errors in the agent panel to use the `Callout` component instead of toasts. Reason for that is because the toasts obscured part of the panel's UI, which wasn't ideal. We can also be more expressive here with a background color, which I think helps with parsing the message. Release Notes: - agent: Improved how we display errors in the panel. --- crates/agent_ui/src/agent_panel.rs | 266 ++++++++++++---------------- crates/ui/src/components/callout.rs | 63 ++++--- 2 files changed, 155 insertions(+), 174 deletions(-) diff --git a/crates/agent_ui/src/agent_panel.rs b/crates/agent_ui/src/agent_panel.rs index 978a4a4f2797ef2bcc7e6058281103df9466cc6a..5f58e0bd8d1a6c3c7faed310898f4ee858afb4f8 100644 --- a/crates/agent_ui/src/agent_panel.rs +++ b/crates/agent_ui/src/agent_panel.rs @@ -41,7 +41,7 @@ use editor::{Anchor, AnchorRangeExt as _, Editor, EditorEvent, MultiBuffer}; use fs::Fs; use gpui::{ Action, Animation, AnimationExt as _, AnyElement, App, AsyncWindowContext, ClipboardItem, - Corner, DismissEvent, Entity, EventEmitter, ExternalPaths, FocusHandle, Focusable, FontWeight, + Corner, DismissEvent, Entity, EventEmitter, ExternalPaths, FocusHandle, Focusable, Hsla, KeyContext, Pixels, Subscription, Task, UpdateGlobal, WeakEntity, linear_color_stop, linear_gradient, prelude::*, pulsating_between, }; @@ -59,7 +59,7 @@ use theme::ThemeSettings; use time::UtcOffset; use ui::utils::WithRemSize; use ui::{ - Banner, CheckboxWithLabel, ContextMenu, ElevationIndex, KeyBinding, PopoverMenu, + Banner, Callout, CheckboxWithLabel, ContextMenu, ElevationIndex, KeyBinding, PopoverMenu, PopoverMenuHandle, ProgressBar, Tab, Tooltip, Vector, VectorName, prelude::*, }; use util::ResultExt as _; @@ -2689,58 +2689,90 @@ impl AgentPanel { Some(div().px_2().pb_2().child(banner).into_any_element()) } + fn create_copy_button(&self, message: impl Into) -> impl IntoElement { + let message = message.into(); + + IconButton::new("copy", IconName::Copy) + .icon_size(IconSize::Small) + .icon_color(Color::Muted) + .tooltip(Tooltip::text("Copy Error Message")) + .on_click(move |_, _, cx| { + cx.write_to_clipboard(ClipboardItem::new_string(message.clone())) + }) + } + + fn dismiss_error_button( + &self, + thread: &Entity, + cx: &mut Context, + ) -> impl IntoElement { + IconButton::new("dismiss", IconName::Close) + .icon_size(IconSize::Small) + .icon_color(Color::Muted) + .tooltip(Tooltip::text("Dismiss Error")) + .on_click(cx.listener({ + let thread = thread.clone(); + move |_, _, _, cx| { + thread.update(cx, |this, _cx| { + this.clear_last_error(); + }); + + cx.notify(); + } + })) + } + + fn upgrade_button( + &self, + thread: &Entity, + cx: &mut Context, + ) -> impl IntoElement { + Button::new("upgrade", "Upgrade") + .label_size(LabelSize::Small) + .style(ButtonStyle::Tinted(ui::TintColor::Accent)) + .on_click(cx.listener({ + let thread = thread.clone(); + move |_, _, _, cx| { + thread.update(cx, |this, _cx| { + this.clear_last_error(); + }); + + cx.open_url(&zed_urls::account_url(cx)); + cx.notify(); + } + })) + } + + fn error_callout_bg(&self, cx: &Context) -> Hsla { + cx.theme().status().error.opacity(0.08) + } + fn render_payment_required_error( &self, thread: &Entity, cx: &mut Context, ) -> AnyElement { - const ERROR_MESSAGE: &str = "Free tier exceeded. Subscribe and add payment to continue using Zed LLMs. You'll be billed at cost for tokens used."; - - v_flex() - .gap_0p5() - .child( - h_flex() - .gap_1p5() - .items_center() - .child(Icon::new(IconName::XCircle).color(Color::Error)) - .child(Label::new("Free Usage Exceeded").weight(FontWeight::MEDIUM)), - ) - .child( - div() - .id("error-message") - .max_h_24() - .overflow_y_scroll() - .child(Label::new(ERROR_MESSAGE)), - ) - .child( - h_flex() - .justify_end() - .mt_1() - .gap_1() - .child(self.create_copy_button(ERROR_MESSAGE)) - .child(Button::new("subscribe", "Subscribe").on_click(cx.listener({ - let thread = thread.clone(); - move |_, _, _, cx| { - thread.update(cx, |this, _cx| { - this.clear_last_error(); - }); + const ERROR_MESSAGE: &str = + "You reached your free usage limit. Upgrade to Zed Pro for more prompts."; - cx.open_url(&zed_urls::account_url(cx)); - cx.notify(); - } - }))) - .child(Button::new("dismiss", "Dismiss").on_click(cx.listener({ - let thread = thread.clone(); - move |_, _, _, cx| { - thread.update(cx, |this, _cx| { - this.clear_last_error(); - }); + let icon = Icon::new(IconName::XCircle) + .size(IconSize::Small) + .color(Color::Error); - cx.notify(); - } - }))), + div() + .border_t_1() + .border_color(cx.theme().colors().border) + .child( + Callout::new() + .icon(icon) + .title("Free Usage Exceeded") + .description(ERROR_MESSAGE) + .tertiary_action(self.upgrade_button(thread, cx)) + .secondary_action(self.create_copy_button(ERROR_MESSAGE)) + .primary_action(self.dismiss_error_button(thread, cx)) + .bg_color(self.error_callout_bg(cx)), ) - .into_any() + .into_any_element() } fn render_model_request_limit_reached_error( @@ -2750,67 +2782,28 @@ impl AgentPanel { cx: &mut Context, ) -> AnyElement { let error_message = match plan { - Plan::ZedPro => { - "Model request limit reached. Upgrade to usage-based billing for more requests." - } - Plan::ZedProTrial => { - "Model request limit reached. Upgrade to Zed Pro for more requests." - } - Plan::Free => "Model request limit reached. Upgrade to Zed Pro for more requests.", - }; - let call_to_action = match plan { - Plan::ZedPro => "Upgrade to usage-based billing", - Plan::ZedProTrial => "Upgrade to Zed Pro", - Plan::Free => "Upgrade to Zed Pro", + Plan::ZedPro => "Upgrade to usage-based billing for more prompts.", + Plan::ZedProTrial | Plan::Free => "Upgrade to Zed Pro for more prompts.", }; - v_flex() - .gap_0p5() - .child( - h_flex() - .gap_1p5() - .items_center() - .child(Icon::new(IconName::XCircle).color(Color::Error)) - .child(Label::new("Model Request Limit Reached").weight(FontWeight::MEDIUM)), - ) - .child( - div() - .id("error-message") - .max_h_24() - .overflow_y_scroll() - .child(Label::new(error_message)), - ) - .child( - h_flex() - .justify_end() - .mt_1() - .gap_1() - .child(self.create_copy_button(error_message)) - .child( - Button::new("subscribe", call_to_action).on_click(cx.listener({ - let thread = thread.clone(); - move |_, _, _, cx| { - thread.update(cx, |this, _cx| { - this.clear_last_error(); - }); - - cx.open_url(&zed_urls::account_url(cx)); - cx.notify(); - } - })), - ) - .child(Button::new("dismiss", "Dismiss").on_click(cx.listener({ - let thread = thread.clone(); - move |_, _, _, cx| { - thread.update(cx, |this, _cx| { - this.clear_last_error(); - }); + let icon = Icon::new(IconName::XCircle) + .size(IconSize::Small) + .color(Color::Error); - cx.notify(); - } - }))), + div() + .border_t_1() + .border_color(cx.theme().colors().border) + .child( + Callout::new() + .icon(icon) + .title("Model Prompt Limit Reached") + .description(error_message) + .tertiary_action(self.upgrade_button(thread, cx)) + .secondary_action(self.create_copy_button(error_message)) + .primary_action(self.dismiss_error_button(thread, cx)) + .bg_color(self.error_callout_bg(cx)), ) - .into_any() + .into_any_element() } fn render_error_message( @@ -2821,40 +2814,24 @@ impl AgentPanel { cx: &mut Context, ) -> AnyElement { let message_with_header = format!("{}\n{}", header, message); - v_flex() - .gap_0p5() - .child( - h_flex() - .gap_1p5() - .items_center() - .child(Icon::new(IconName::XCircle).color(Color::Error)) - .child(Label::new(header).weight(FontWeight::MEDIUM)), - ) - .child( - div() - .id("error-message") - .max_h_32() - .overflow_y_scroll() - .child(Label::new(message.clone())), - ) - .child( - h_flex() - .justify_end() - .mt_1() - .gap_1() - .child(self.create_copy_button(message_with_header)) - .child(Button::new("dismiss", "Dismiss").on_click(cx.listener({ - let thread = thread.clone(); - move |_, _, _, cx| { - thread.update(cx, |this, _cx| { - this.clear_last_error(); - }); - cx.notify(); - } - }))), + let icon = Icon::new(IconName::XCircle) + .size(IconSize::Small) + .color(Color::Error); + + div() + .border_t_1() + .border_color(cx.theme().colors().border) + .child( + Callout::new() + .icon(icon) + .title(header) + .description(message.clone()) + .primary_action(self.dismiss_error_button(thread, cx)) + .secondary_action(self.create_copy_button(message_with_header)) + .bg_color(self.error_callout_bg(cx)), ) - .into_any() + .into_any_element() } fn render_prompt_editor( @@ -2999,15 +2976,6 @@ impl AgentPanel { } } - fn create_copy_button(&self, message: impl Into) -> impl IntoElement { - let message = message.into(); - IconButton::new("copy", IconName::Copy) - .on_click(move |_, _, cx| { - cx.write_to_clipboard(ClipboardItem::new_string(message.clone())) - }) - .tooltip(Tooltip::text("Copy Error Message")) - } - fn key_context(&self) -> KeyContext { let mut key_context = KeyContext::new_with_defaults(); key_context.add("AgentPanel"); @@ -3089,18 +3057,9 @@ impl Render for AgentPanel { thread.clone().into_any_element() }) .children(self.render_tool_use_limit_reached(window, cx)) - .child(h_flex().child(message_editor.clone())) .when_some(thread.read(cx).last_error(), |this, last_error| { this.child( div() - .absolute() - .right_3() - .bottom_12() - .max_w_96() - .py_2() - .px_3() - .elevation_2(cx) - .occlude() .child(match last_error { ThreadError::PaymentRequired => { self.render_payment_required_error(thread, cx) @@ -3114,6 +3073,7 @@ impl Render for AgentPanel { .into_any(), ) }) + .child(h_flex().child(message_editor.clone())) .child(self.render_drag_target(cx)), ActiveView::History => parent.child(self.history.clone()), ActiveView::TextThread { diff --git a/crates/ui/src/components/callout.rs b/crates/ui/src/components/callout.rs index b3f3758db6ce331eb17f4fe50e579dc148afb1da..d15fa122ed95e5e9a922c8bc694d1c35d975f9a4 100644 --- a/crates/ui/src/components/callout.rs +++ b/crates/ui/src/components/callout.rs @@ -1,4 +1,4 @@ -use gpui::AnyElement; +use gpui::{AnyElement, Hsla}; use crate::prelude::*; @@ -24,7 +24,9 @@ pub struct Callout { description: Option, primary_action: Option, secondary_action: Option, + tertiary_action: Option, line_height: Option, + bg_color: Option, } impl Callout { @@ -36,7 +38,9 @@ impl Callout { description: None, primary_action: None, secondary_action: None, + tertiary_action: None, line_height: None, + bg_color: None, } } @@ -71,64 +75,81 @@ impl Callout { self } + /// Sets an optional tertiary call-to-action button. + pub fn tertiary_action(mut self, action: impl IntoElement) -> Self { + self.tertiary_action = Some(action.into_any_element()); + self + } + /// Sets a custom line height for the callout content. pub fn line_height(mut self, line_height: Pixels) -> Self { self.line_height = Some(line_height); self } + + /// Sets a custom background color for the callout content. + pub fn bg_color(mut self, color: Hsla) -> Self { + self.bg_color = Some(color); + self + } } impl RenderOnce for Callout { fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement { let line_height = self.line_height.unwrap_or(window.line_height()); + let bg_color = self + .bg_color + .unwrap_or(cx.theme().colors().panel_background); + let has_actions = self.primary_action.is_some() + || self.secondary_action.is_some() + || self.tertiary_action.is_some(); h_flex() - .w_full() .p_2() .gap_2() .items_start() - .bg(cx.theme().colors().panel_background) + .bg(bg_color) .overflow_x_hidden() .when_some(self.icon, |this, icon| { this.child(h_flex().h(line_height).justify_center().child(icon)) }) .child( v_flex() + .min_w_0() .w_full() .child( h_flex() .h(line_height) .w_full() .gap_1() - .flex_wrap() .justify_between() .when_some(self.title, |this, title| { this.child(h_flex().child(Label::new(title).size(LabelSize::Small))) }) - .when( - self.primary_action.is_some() || self.secondary_action.is_some(), - |this| { - this.child( - h_flex() - .gap_0p5() - .when_some(self.secondary_action, |this, action| { - this.child(action) - }) - .when_some(self.primary_action, |this, action| { - this.child(action) - }), - ) - }, - ), + .when(has_actions, |this| { + this.child( + h_flex() + .gap_0p5() + .when_some(self.tertiary_action, |this, action| { + this.child(action) + }) + .when_some(self.secondary_action, |this, action| { + this.child(action) + }) + .when_some(self.primary_action, |this, action| { + this.child(action) + }), + ) + }), ) .when_some(self.description, |this, description| { this.child( div() .w_full() .flex_1() - .child(description) .text_ui_sm(cx) - .text_color(cx.theme().colors().text_muted), + .text_color(cx.theme().colors().text_muted) + .child(description), ) }), ) From 6e9c6c568465b12d9be13e35f56fa5fef0050433 Mon Sep 17 00:00:00 2001 From: Umesh Yadav <23421535+imumesh18@users.noreply.github.com> Date: Tue, 1 Jul 2025 17:35:08 +0530 Subject: [PATCH 040/134] git_ui: Fix list in git commit message (#33409) Follow up: #32114 Closes #33274 Use the new support for language-specific rewrap_prefixes added in https://github.com/zed-industries/zed/pull/33702. Release Notes: - Fix git commit message line break getting stripped after committing. --------- Signed-off-by: Umesh Yadav --- crates/languages/src/gitcommit/config.toml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/languages/src/gitcommit/config.toml b/crates/languages/src/gitcommit/config.toml index ae4b836ed66dc7558cf7b033e555dd2dba3b309c..c2421ce00613e5848aacab5d1230ab839c8b1388 100644 --- a/crates/languages/src/gitcommit/config.toml +++ b/crates/languages/src/gitcommit/config.toml @@ -16,3 +16,9 @@ brackets = [ { start = "{", end = "}", close = true, newline = false }, { start = "[", end = "]", close = true, newline = false }, ] +rewrap_prefixes = [ + "[-*+]\\s+", + "\\d+\\.\\s+", + ">\\s*", + "[-*+]\\s+\\[[\\sx]\\]\\s+" +] From 0fe73a99e50a5bdfeb383d2cddb8178af7bb5c62 Mon Sep 17 00:00:00 2001 From: Vitaly Slobodin Date: Tue, 1 Jul 2025 15:12:08 +0200 Subject: [PATCH 041/134] ruby: Add basic documentation about debugging (#33572) Hi, this pull request adds basic documentation about debugging feature available in the Ruby extension. Release Notes: - N/A --- docs/src/languages/ruby.md | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/docs/src/languages/ruby.md b/docs/src/languages/ruby.md index 4c563ca1f41d98f5c9b32fcafe0fd5f151540ed5..8b3094a3b714b12e310b7b30ff7b5d196e1893d8 100644 --- a/docs/src/languages/ruby.md +++ b/docs/src/languages/ruby.md @@ -340,3 +340,41 @@ Plain minitest does not support running tests by line number, only by name, so w ``` Similar task syntax can be used for other test frameworks such as `quickdraw` or `tldr`. + +## Debugging + +The Ruby extension provides a debug adapter for debugging Ruby code. Zed's name for the adapter (in the UI and `debug.json`) is `rdbg`, and under the hood, it uses the [`debug`](https://github.com/ruby/debug) gem. The extension uses the [same activation logic](#language-server-activation) as the language servers. + +### Examples + +#### Debug a Ruby script + +```jsonc +[ + { + "label": "Debug current file", + "adapter": "rdbg", + "request": "launch", + "script": "$ZED_FILE", + "cwd": "$ZED_WORKTREE_ROOT", + }, +] +``` + +#### Debug Rails server + +```jsonc +[ + { + "label": "Debug Rails server", + "adapter": "rdbg", + "request": "launch", + "command": "$ZED_WORKTREE_ROOT/bin/rails", + "args": ["server"], + "cwd": "$ZED_WORKTREE_ROOT", + "env": { + "RUBY_DEBUG_OPEN": "true", + }, + }, +] +``` From 62e8f4530436ed4b0cb6e793be1ad6e19da7123d Mon Sep 17 00:00:00 2001 From: Bennet Bo Fenner Date: Tue, 1 Jul 2025 15:17:36 +0200 Subject: [PATCH 042/134] settings: Remove `version` field migration (#33711) This reverts some parts of #33372, as it will break the settings for users running stable and preview at the same time. We can add it back once the changes make it to stable. Release Notes: - N/A --- crates/migrator/src/migrations.rs | 6 - .../src/migrations/m_2025_06_25/settings.rs | 133 ------------------ crates/migrator/src/migrator.rs | 79 ----------- 3 files changed, 218 deletions(-) delete mode 100644 crates/migrator/src/migrations/m_2025_06_25/settings.rs diff --git a/crates/migrator/src/migrations.rs b/crates/migrator/src/migrations.rs index 4e3839358b6e55b0cbec72f92ca12d9b2b30c168..84ef0f0456e65da7dbb604e7733fec8ab1cdd386 100644 --- a/crates/migrator/src/migrations.rs +++ b/crates/migrator/src/migrations.rs @@ -82,12 +82,6 @@ pub(crate) mod m_2025_06_16 { pub(crate) use settings::SETTINGS_PATTERNS; } -pub(crate) mod m_2025_06_25 { - mod settings; - - pub(crate) use settings::SETTINGS_PATTERNS; -} - pub(crate) mod m_2025_06_27 { mod settings; diff --git a/crates/migrator/src/migrations/m_2025_06_25/settings.rs b/crates/migrator/src/migrations/m_2025_06_25/settings.rs deleted file mode 100644 index 5dd6c3093a43b00acff3db6c1e316a3fc6664175..0000000000000000000000000000000000000000 --- a/crates/migrator/src/migrations/m_2025_06_25/settings.rs +++ /dev/null @@ -1,133 +0,0 @@ -use std::ops::Range; -use tree_sitter::{Query, QueryMatch}; - -use crate::MigrationPatterns; - -pub const SETTINGS_PATTERNS: MigrationPatterns = &[ - (SETTINGS_VERSION_PATTERN, remove_version_fields), - ( - SETTINGS_NESTED_VERSION_PATTERN, - remove_nested_version_fields, - ), -]; - -const SETTINGS_VERSION_PATTERN: &str = r#"(document - (object - (pair - key: (string (string_content) @key) - value: (object - (pair - key: (string (string_content) @version_key) - value: (_) @version_value - ) @version_pair - ) - ) - ) - (#eq? @key "agent") - (#eq? @version_key "version") -)"#; - -const SETTINGS_NESTED_VERSION_PATTERN: &str = r#"(document - (object - (pair - key: (string (string_content) @language_models) - value: (object - (pair - key: (string (string_content) @provider) - value: (object - (pair - key: (string (string_content) @version_key) - value: (_) @version_value - ) @version_pair - ) - ) - ) - ) - ) - (#eq? @language_models "language_models") - (#match? @provider "^(anthropic|openai)$") - (#eq? @version_key "version") -)"#; - -fn remove_version_fields( - contents: &str, - mat: &QueryMatch, - query: &Query, -) -> Option<(Range, String)> { - let version_pair_ix = query.capture_index_for_name("version_pair")?; - let version_pair_node = mat.nodes_for_capture_index(version_pair_ix).next()?; - - remove_pair_with_whitespace(contents, version_pair_node) -} - -fn remove_nested_version_fields( - contents: &str, - mat: &QueryMatch, - query: &Query, -) -> Option<(Range, String)> { - let version_pair_ix = query.capture_index_for_name("version_pair")?; - let version_pair_node = mat.nodes_for_capture_index(version_pair_ix).next()?; - - remove_pair_with_whitespace(contents, version_pair_node) -} - -fn remove_pair_with_whitespace( - contents: &str, - pair_node: tree_sitter::Node, -) -> Option<(Range, String)> { - let mut range_to_remove = pair_node.byte_range(); - - // Check if there's a comma after this pair - if let Some(next_sibling) = pair_node.next_sibling() { - if next_sibling.kind() == "," { - range_to_remove.end = next_sibling.end_byte(); - } - } else { - // If no next sibling, check if there's a comma before - if let Some(prev_sibling) = pair_node.prev_sibling() { - if prev_sibling.kind() == "," { - range_to_remove.start = prev_sibling.start_byte(); - } - } - } - - // Include any leading whitespace/newline, including comments - let text_before = &contents[..range_to_remove.start]; - if let Some(last_newline) = text_before.rfind('\n') { - let whitespace_start = last_newline + 1; - let potential_whitespace = &contents[whitespace_start..range_to_remove.start]; - - // Check if it's only whitespace or comments - let mut is_whitespace_or_comment = true; - let mut in_comment = false; - let mut chars = potential_whitespace.chars().peekable(); - - while let Some(ch) = chars.next() { - if in_comment { - if ch == '\n' { - in_comment = false; - } - } else if ch == '/' && chars.peek() == Some(&'/') { - in_comment = true; - chars.next(); // Skip the second '/' - } else if !ch.is_whitespace() { - is_whitespace_or_comment = false; - break; - } - } - - if is_whitespace_or_comment { - range_to_remove.start = whitespace_start; - } - } - - // Also check if we need to include trailing whitespace up to the next line - let text_after = &contents[range_to_remove.end..]; - if let Some(newline_pos) = text_after.find('\n') { - if text_after[..newline_pos].chars().all(|c| c.is_whitespace()) { - range_to_remove.end += newline_pos + 1; - } - } - - Some((range_to_remove, String::new())) -} diff --git a/crates/migrator/src/migrator.rs b/crates/migrator/src/migrator.rs index be32b2734e66ebd621ff858a79eef322468b11ae..06e96a6f865c227579ab2452426b5d8cf46fda7c 100644 --- a/crates/migrator/src/migrator.rs +++ b/crates/migrator/src/migrator.rs @@ -152,10 +152,6 @@ pub fn migrate_settings(text: &str) -> Result> { migrations::m_2025_06_16::SETTINGS_PATTERNS, &SETTINGS_QUERY_2025_06_16, ), - ( - migrations::m_2025_06_25::SETTINGS_PATTERNS, - &SETTINGS_QUERY_2025_06_25, - ), ( migrations::m_2025_06_27::SETTINGS_PATTERNS, &SETTINGS_QUERY_2025_06_27, @@ -262,10 +258,6 @@ define_query!( SETTINGS_QUERY_2025_06_16, migrations::m_2025_06_16::SETTINGS_PATTERNS ); -define_query!( - SETTINGS_QUERY_2025_06_25, - migrations::m_2025_06_25::SETTINGS_PATTERNS -); define_query!( SETTINGS_QUERY_2025_06_27, migrations::m_2025_06_27::SETTINGS_PATTERNS @@ -1089,77 +1081,6 @@ mod tests { ); } - #[test] - fn test_remove_version_fields() { - assert_migrate_settings( - r#"{ - "language_models": { - "anthropic": { - "version": "1", - "api_url": "https://api.anthropic.com" - }, - "openai": { - "version": "1", - "api_url": "https://api.openai.com/v1" - } - }, - "agent": { - "version": "2", - "enabled": true, - "preferred_completion_mode": "normal", - "button": true, - "dock": "right", - "default_width": 640, - "default_height": 320, - "default_model": { - "provider": "zed.dev", - "model": "claude-sonnet-4" - } - } -}"#, - Some( - r#"{ - "language_models": { - "anthropic": { - "api_url": "https://api.anthropic.com" - }, - "openai": { - "api_url": "https://api.openai.com/v1" - } - }, - "agent": { - "enabled": true, - "preferred_completion_mode": "normal", - "button": true, - "dock": "right", - "default_width": 640, - "default_height": 320, - "default_model": { - "provider": "zed.dev", - "model": "claude-sonnet-4" - } - } -}"#, - ), - ); - - // Test that version fields in other contexts are not removed - assert_migrate_settings( - r#"{ - "language_models": { - "other_provider": { - "version": "1", - "api_url": "https://api.example.com" - } - }, - "other_section": { - "version": "1" - } -}"#, - None, - ); - } - #[test] fn test_flatten_context_server_command() { assert_migrate_settings( From 52c42125a7c77834aead079575932bd51824175d Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 1 Jul 2025 09:29:43 -0400 Subject: [PATCH 043/134] language_models: Fix casing of `ZedAiConfiguration` (#33712) This PR fixes the casing of the `ZedAiConfiguration` identifier. Release Notes: - N/A --- crates/language_models/src/provider/cloud.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/language_models/src/provider/cloud.rs b/crates/language_models/src/provider/cloud.rs index 5417c329abaa107f9750c8a35a791afcc11b87ae..505caa2e42b27f21e07cda9dc55252dfdde403b1 100644 --- a/crates/language_models/src/provider/cloud.rs +++ b/crates/language_models/src/provider/cloud.rs @@ -1057,7 +1057,7 @@ fn response_lines( } #[derive(IntoElement, RegisterComponent)] -struct ZedAIConfiguration { +struct ZedAiConfiguration { is_connected: bool, plan: Option, subscription_period: Option<(DateTime, DateTime)>, @@ -1068,7 +1068,7 @@ struct ZedAIConfiguration { sign_in_callback: Arc, } -impl RenderOnce for ZedAIConfiguration { +impl RenderOnce for ZedAiConfiguration { fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement { const ZED_PRICING_URL: &str = "https://zed.dev/pricing"; @@ -1195,7 +1195,7 @@ impl Render for ConfigurationView { let state = self.state.read(cx); let user_store = state.user_store.read(cx); - ZedAIConfiguration { + ZedAiConfiguration { is_connected: !state.is_signed_out(), plan: user_store.current_plan(), subscription_period: user_store.subscription_period(), @@ -1208,7 +1208,7 @@ impl Render for ConfigurationView { } } -impl Component for ZedAIConfiguration { +impl Component for ZedAiConfiguration { fn scope() -> ComponentScope { ComponentScope::Agent } @@ -1220,7 +1220,7 @@ impl Component for ZedAIConfiguration { eligible_for_trial: bool, has_accepted_terms_of_service: bool, ) -> AnyElement { - ZedAIConfiguration { + ZedAiConfiguration { is_connected, plan, subscription_period: plan From 3041de0cdfa1581ec560be24eeeedbe7f6bed7c7 Mon Sep 17 00:00:00 2001 From: Abdelhakim Qbaich Date: Tue, 1 Jul 2025 10:54:53 -0400 Subject: [PATCH 044/134] Suggest Typst extension for .typ files (#33632) Release Notes: - N/A --- crates/extensions_ui/src/extension_suggest.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/extensions_ui/src/extension_suggest.rs b/crates/extensions_ui/src/extension_suggest.rs index 9b1d1f8cdfc5e3f9201e6513d632c1ec96f15058..ab990881cca337e72361a0a79ce1ded5739595da 100644 --- a/crates/extensions_ui/src/extension_suggest.rs +++ b/crates/extensions_ui/src/extension_suggest.rs @@ -70,6 +70,7 @@ const SUGGESTIONS_BY_EXTENSION_ID: &[(&str, &[&str])] = &[ ("templ", &["templ"]), ("terraform", &["tf", "tfvars", "hcl"]), ("toml", &["Cargo.lock", "toml"]), + ("typst", &["typ"]), ("vue", &["vue"]), ("wgsl", &["wgsl"]), ("wit", &["wit"]), From 351ba5023b7ee8f09712437016ca33ea6c1cbb8e Mon Sep 17 00:00:00 2001 From: G36maid <53391375+G36maid@users.noreply.github.com> Date: Tue, 1 Jul 2025 23:18:34 +0800 Subject: [PATCH 045/134] docs: Add FreeBSD build instructions and current status (#33617) This adds documentation for building Zed on FreeBSD. Notice WebRTC/LiveKit remains unsupported on this platform for now. Follow-up to: - #33162 - #30981 Release Notes: - N/A --------- Co-authored-by: Peter Tripp --- docs/src/development/freebsd.md | 37 ++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/docs/src/development/freebsd.md b/docs/src/development/freebsd.md index 33ff9a56d94c3f3882d7465d82f236b463fac7d6..199e653a65c5cf3d86881c4c038677d60ac2fec5 100644 --- a/docs/src/development/freebsd.md +++ b/docs/src/development/freebsd.md @@ -16,15 +16,36 @@ Clone the [Zed repository](https://github.com/zed-industries/zed). If preferred, you can inspect [`script/freebsd`](https://github.com/zed-industries/zed/blob/main/script/freebsd) and perform the steps manually. ---- +## Building from source -### ⚠️ WebRTC Notice +Once the dependencies are installed, you can build Zed using [Cargo](https://doc.rust-lang.org/cargo/). -Currently, building `webrtc-sys` on FreeBSD fails due to missing upstream support and unavailable prebuilt binaries. -This is actively being worked on. +For a debug build of the editor: -More progress and discussion can be found in [Zed’s GitHub Discussions](https://github.com/zed-industries/zed/discussions/29550). +```sh +cargo run +``` -_Environment: -FreeBSD 14.2-RELEASE -Architecture: amd64 (x86_64)_ +And to run the tests: + +```sh +cargo test --workspace +``` + +In release mode, the primary user interface is the `cli` crate. You can run it in development with: + +```sh +cargo run -p cli +``` + +### WebRTC Notice + +Currently, building `webrtc-sys` on FreeBSD fails due to missing upstream support and unavailable prebuilt binaries. As a result, some collaboration features (audio calls and screensharing) that depend on WebRTC are temporarily disabled. + +See [Issue #15309: FreeBSD Support] and [Discussion #29550: Unofficial FreeBSD port for Zed] for more. + +## Troubleshooting + +### Cargo errors claiming that a dependency is using unstable features + +Try `cargo clean` and `cargo build`. From 31b7786be7edb7f9a12bcc13530a062bfafdee9c Mon Sep 17 00:00:00 2001 From: Alex Shi Date: Tue, 1 Jul 2025 23:43:39 +0800 Subject: [PATCH 046/134] Fix IndentGuides story (#32781) This PR updates the `Model` to `Entity` also fixes the `IndentGuidesStory`. In this [commit](https://github.com/zed-industries/zed/commit/6fca1d2b0ba93cdbc3255657a990828a0f22b199), `Entity` replaces `View`/`Model`. Other than this, I noticed the storybook fails on my MacOS and Ubuntu, see error below ``` thread 'main' panicked at crates/gpui/src/colors.rs:99:15: called `Result::unwrap()` on an `Err` value: no state of type gpui::colors::GlobalColors exists note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace ``` This was resolved by explicitly specifying `GlobalColors` in Storybook. Release Notes: - N/A --- crates/storybook/src/stories.rs | 2 ++ crates/storybook/src/stories/indent_guides.rs | 28 ++++++++----------- crates/storybook/src/story_selector.rs | 2 ++ crates/storybook/src/storybook.rs | 6 +++- crates/ui_input/src/ui_input.rs | 2 +- 5 files changed, 22 insertions(+), 18 deletions(-) diff --git a/crates/storybook/src/stories.rs b/crates/storybook/src/stories.rs index b824235b00b5d49502734515ca14b853ca3be435..63992d259c7a1cb76a3684f53c55fe255522aced 100644 --- a/crates/storybook/src/stories.rs +++ b/crates/storybook/src/stories.rs @@ -1,6 +1,7 @@ mod auto_height_editor; mod cursor; mod focus; +mod indent_guides; mod kitchen_sink; mod overflow_scroll; mod picker; @@ -12,6 +13,7 @@ mod with_rem_size; pub use auto_height_editor::*; pub use cursor::*; pub use focus::*; +pub use indent_guides::*; pub use kitchen_sink::*; pub use overflow_scroll::*; pub use picker::*; diff --git a/crates/storybook/src/stories/indent_guides.rs b/crates/storybook/src/stories/indent_guides.rs index 068890ae50c524fa9242c53327ed0b929d098363..e83c9ed3837b49c4c701d4434ca1533fef83a5d7 100644 --- a/crates/storybook/src/stories/indent_guides.rs +++ b/crates/storybook/src/stories/indent_guides.rs @@ -1,13 +1,10 @@ -use std::fmt::format; +use std::ops::Range; + +use gpui::{Entity, Render, div, uniform_list}; +use gpui::{prelude::*, *}; +use ui::{AbsoluteLength, Color, DefiniteLength, Label, LabelCommon, px, v_flex}; -use gpui::{ - DefaultColor, DefaultThemeAppearance, Hsla, Render, colors, div, prelude::*, uniform_list, -}; use story::Story; -use strum::IntoEnumIterator; -use ui::{ - AbsoluteLength, ActiveTheme, Color, DefiniteLength, Label, LabelCommon, h_flex, px, v_flex, -}; const LENGTH: usize = 100; @@ -16,7 +13,7 @@ pub struct IndentGuidesStory { } impl IndentGuidesStory { - pub fn model(window: &mut Window, cx: &mut AppContext) -> Model { + pub fn model(_window: &mut Window, cx: &mut App) -> Entity { let mut depths = Vec::new(); depths.push(0); depths.push(1); @@ -33,16 +30,15 @@ impl IndentGuidesStory { } impl Render for IndentGuidesStory { - fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { + fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { Story::container(cx) - .child(Story::title("Indent guides")) + .child(Story::title("Indent guides", cx)) .child( v_flex().size_full().child( uniform_list( - cx.entity().clone(), "some-list", self.depths.len(), - |this, range, cx| { + cx.processor(move |this, range: Range, _window, _cx| { this.depths .iter() .enumerate() @@ -56,7 +52,7 @@ impl Render for IndentGuidesStory { .child(Label::new(format!("Item {}", i)).color(Color::Info)) }) .collect() - }, + }), ) .with_sizing_behavior(gpui::ListSizingBehavior::Infer) .with_decoration(ui::indent_guides( @@ -64,10 +60,10 @@ impl Render for IndentGuidesStory { px(16.), ui::IndentGuideColors { default: Color::Info.color(cx), - hovered: Color::Accent.color(cx), + hover: Color::Accent.color(cx), active: Color::Accent.color(cx), }, - |this, range, cx| { + |this, range, _cx, _context| { this.depths .iter() .skip(range.start) diff --git a/crates/storybook/src/story_selector.rs b/crates/storybook/src/story_selector.rs index 1de6191367fb821ffeb41f88db0b9c5b275c499a..fd0be97ff6f8e5ef04126a4de60f41d4f31e2bef 100644 --- a/crates/storybook/src/story_selector.rs +++ b/crates/storybook/src/story_selector.rs @@ -31,6 +31,7 @@ pub enum ComponentStory { ToggleButton, ViewportUnits, WithRemSize, + IndentGuides, } impl ComponentStory { @@ -60,6 +61,7 @@ impl ComponentStory { Self::ToggleButton => cx.new(|_| ui::ToggleButtonStory).into(), Self::ViewportUnits => cx.new(|_| crate::stories::ViewportUnitsStory).into(), Self::WithRemSize => cx.new(|_| crate::stories::WithRemSizeStory).into(), + Self::IndentGuides => crate::stories::IndentGuidesStory::model(window, cx).into(), } } } diff --git a/crates/storybook/src/storybook.rs b/crates/storybook/src/storybook.rs index c8b055a67e60a07c87696515013b1a6fd5fefb1d..4c5b6272ef1f26d1fd065f76032e327ce59d1e12 100644 --- a/crates/storybook/src/storybook.rs +++ b/crates/storybook/src/storybook.rs @@ -9,7 +9,9 @@ use std::sync::Arc; use clap::Parser; use dialoguer::FuzzySelect; use gpui::{ - AnyView, App, Bounds, Context, Render, Window, WindowBounds, WindowOptions, div, px, size, + AnyView, App, Bounds, Context, Render, Window, WindowBounds, WindowOptions, + colors::{Colors, GlobalColors}, + div, px, size, }; use log::LevelFilter; use project::Project; @@ -68,6 +70,8 @@ fn main() { gpui::Application::new().with_assets(Assets).run(move |cx| { load_embedded_fonts(cx).unwrap(); + cx.set_global(GlobalColors(Arc::new(Colors::default()))); + let http_client = ReqwestClient::user_agent("zed_storybook").unwrap(); cx.set_http_client(Arc::new(http_client)); diff --git a/crates/ui_input/src/ui_input.rs b/crates/ui_input/src/ui_input.rs index dfecc08dac203d597cb72e11a11acafe965b9571..bd99814cb30534165ad2bfba3911233e2946271b 100644 --- a/crates/ui_input/src/ui_input.rs +++ b/crates/ui_input/src/ui_input.rs @@ -29,7 +29,7 @@ pub struct SingleLineInput { label: Option, /// The placeholder text for the text field. placeholder: SharedString, - /// Exposes the underlying [`Model`] to allow for customizing the editor beyond the provided API. + /// Exposes the underlying [`Entity`] to allow for customizing the editor beyond the provided API. /// /// This likely will only be public in the short term, ideally the API will be expanded to cover necessary use cases. pub editor: Entity, From 274f2e90da29401e978738f45cd7b1a44d0080ee Mon Sep 17 00:00:00 2001 From: Peter Tripp Date: Tue, 1 Jul 2025 12:12:46 -0400 Subject: [PATCH 047/134] Add support for more python operators (#33720) Closes: https://github.com/zed-industries/zed/issues/33683 | Before | After | | - | - | | Screenshot 2025-07-01 at 11 42 56 | Screenshot 2025-07-01 at 11 44 45 | Release Notes: - python: Properly highlight additional operators ("&=", "<<=", ">>=", "@=", "^=" and "|=") --- crates/languages/src/python/highlights.scm | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/languages/src/python/highlights.scm b/crates/languages/src/python/highlights.scm index 97d5fb52755c7c9e25d1016f085dc9660a081f30..77db9b2f4c17519e966b68c44fede2aa9bc4c29f 100644 --- a/crates/languages/src/python/highlights.scm +++ b/crates/languages/src/python/highlights.scm @@ -226,6 +226,12 @@ ">>" "|" "~" + "&=" + "<<=" + ">>=" + "@=" + "^=" + "|=" ] @operator [ From a11647d07f30a410983615a9af66cc01af9cdc18 Mon Sep 17 00:00:00 2001 From: Julia Ryan Date: Tue, 1 Jul 2025 09:14:25 -0700 Subject: [PATCH 048/134] ci: Block PRs on Nix build failures (#33688) Closes #17458 For now we're being conservative and only running CI on changes to the following files: - `flake.{nix,lock}` - `Cargo.{lock,toml}` - `nix/*` - `.cargo/config.toml` - `rust-toolchain.toml` Release Notes: - N/A --- .github/workflows/ci.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3b6e014d25900931a02926ec69d64f211590c99e..39036ef5649e699ffda1636f304629fce6184371 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,6 +30,7 @@ jobs: run_tests: ${{ steps.filter.outputs.run_tests }} run_license: ${{ steps.filter.outputs.run_license }} run_docs: ${{ steps.filter.outputs.run_docs }} + run_nix: ${{ steps.filter.outputs.run_nix }} runs-on: - ubuntu-latest steps: @@ -69,6 +70,12 @@ jobs: else echo "run_license=false" >> $GITHUB_OUTPUT fi + NIX_REGEX='^(nix/|flake\.|Cargo\.|rust-toolchain.toml|\.cargo/config.toml)' + if [[ $(git diff --name-only $COMPARE_REV ${{ github.sha }} | grep "$NIX_REGEX") ]]; then + echo "run_nix=true" >> $GITHUB_OUTPUT + else + echo "run_nix=false" >> $GITHUB_OUTPUT + fi migration_checks: name: Check Postgres and Protobuf migrations, mergability @@ -746,7 +753,10 @@ jobs: nix-build: name: Build with Nix uses: ./.github/workflows/nix.yml - if: github.repository_owner == 'zed-industries' && contains(github.event.pull_request.labels.*.name, 'run-nix') + needs: [job_spec] + if: github.repository_owner == 'zed-industries' && + (contains(github.event.pull_request.labels.*.name, 'run-nix') || + needs.job_spec.outputs.run_nix == 'true') secrets: inherit with: flake-output: debug From 0068de03867488207b8022134895b205dfdafd87 Mon Sep 17 00:00:00 2001 From: Julia Ryan Date: Tue, 1 Jul 2025 09:14:59 -0700 Subject: [PATCH 049/134] debugger: Handle the `envFile` setting for Go (#33666) Fixes #32984 Release Notes: - The Go debugger now respects the `envFile` setting. --- Cargo.lock | 10 ++--- Cargo.toml | 2 +- crates/dap_adapters/Cargo.toml | 2 + crates/dap_adapters/src/go.rs | 82 +++++++++++++++++++++++++++++----- crates/eval/Cargo.toml | 2 +- crates/eval/src/eval.rs | 2 +- 6 files changed, 80 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c523c12b135bcf1c2ec7e612318ca35030cd644e..36d08ec201e47fa87db1df64e698654a227e7c80 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4147,6 +4147,8 @@ dependencies = [ "async-trait", "collections", "dap", + "dotenvy", + "fs", "futures 0.3.31", "gpui", "json_dotpath", @@ -4675,12 +4677,6 @@ dependencies = [ "syn 2.0.101", ] -[[package]] -name = "dotenv" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" - [[package]] name = "dotenvy" version = "0.15.7" @@ -5114,7 +5110,7 @@ dependencies = [ "collections", "debug_adapter_extension", "dirs 4.0.0", - "dotenv", + "dotenvy", "env_logger 0.11.8", "extension", "fs", diff --git a/Cargo.toml b/Cargo.toml index bc686419e59b1ff151e66c861f2c04d417b85698..e7926f025fd4b367d366876ec120b43da487aa38 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -449,7 +449,7 @@ dashmap = "6.0" derive_more = "0.99.17" dirs = "4.0" documented = "0.9.1" -dotenv = "0.15.0" +dotenvy = "0.15.0" ec4rs = "1.1" emojis = "0.6.1" env_logger = "0.11" diff --git a/crates/dap_adapters/Cargo.toml b/crates/dap_adapters/Cargo.toml index 07356c20849918f9ee5b8bbd426f672af3d888f2..65544fbb6a1b7565c4fe641058e4e6c725b21016 100644 --- a/crates/dap_adapters/Cargo.toml +++ b/crates/dap_adapters/Cargo.toml @@ -25,7 +25,9 @@ anyhow.workspace = true async-trait.workspace = true collections.workspace = true dap.workspace = true +dotenvy.workspace = true futures.workspace = true +fs.workspace = true gpui.workspace = true json_dotpath.workspace = true language.workspace = true diff --git a/crates/dap_adapters/src/go.rs b/crates/dap_adapters/src/go.rs index bc3f5007454adee4cfcbc8a3cf09c87ae0100b97..d32f5cbf3426f1b669132e74e389862e7944267b 100644 --- a/crates/dap_adapters/src/go.rs +++ b/crates/dap_adapters/src/go.rs @@ -7,13 +7,22 @@ use dap::{ latest_github_release, }, }; - +use fs::Fs; use gpui::{AsyncApp, SharedString}; use language::LanguageName; -use std::{env::consts, ffi::OsStr, path::PathBuf, sync::OnceLock}; +use log::warn; +use serde_json::{Map, Value}; use task::TcpArgumentsTemplate; use util; +use std::{ + env::consts, + ffi::OsStr, + path::{Path, PathBuf}, + str::FromStr, + sync::OnceLock, +}; + use crate::*; #[derive(Default, Debug)] @@ -437,22 +446,34 @@ impl DebugAdapter for GoDebugAdapter { adapter_path.join("dlv").to_string_lossy().to_string() }; - let cwd = task_definition - .config - .get("cwd") - .and_then(|s| s.as_str()) - .map(PathBuf::from) - .unwrap_or_else(|| delegate.worktree_root_path().to_path_buf()); + let cwd = Some( + task_definition + .config + .get("cwd") + .and_then(|s| s.as_str()) + .map(PathBuf::from) + .unwrap_or_else(|| delegate.worktree_root_path().to_path_buf()), + ); let arguments; let command; let connection; let mut configuration = task_definition.config.clone(); + let mut envs = HashMap::default(); + if let Some(configuration) = configuration.as_object_mut() { configuration .entry("cwd") .or_insert_with(|| delegate.worktree_root_path().to_string_lossy().into()); + + handle_envs( + configuration, + &mut envs, + cwd.as_deref(), + delegate.fs().clone(), + ) + .await; } if let Some(connection_options) = &task_definition.tcp_connection { @@ -494,8 +515,8 @@ impl DebugAdapter for GoDebugAdapter { Ok(DebugAdapterBinary { command, arguments, - cwd: Some(cwd), - envs: HashMap::default(), + cwd, + envs, connection, request_args: StartDebuggingRequestArguments { configuration, @@ -504,3 +525,44 @@ impl DebugAdapter for GoDebugAdapter { }) } } + +// delve doesn't do anything with the envFile setting, so we intercept it +async fn handle_envs( + config: &mut Map, + envs: &mut HashMap, + cwd: Option<&Path>, + fs: Arc, +) -> Option<()> { + let env_files = match config.get("envFile")? { + Value::Array(arr) => arr.iter().map(|v| v.as_str()).collect::>(), + Value::String(s) => vec![Some(s.as_str())], + _ => return None, + }; + + let rebase_path = |path: PathBuf| { + if path.is_absolute() { + Some(path) + } else { + cwd.map(|p| p.join(path)) + } + }; + + for path in env_files { + let Some(path) = path + .and_then(|s| PathBuf::from_str(s).ok()) + .and_then(rebase_path) + else { + continue; + }; + + if let Ok(file) = fs.open_sync(&path).await { + envs.extend(dotenvy::from_read_iter(file).filter_map(Result::ok)) + } else { + warn!("While starting Go debug session: failed to read env file {path:?}"); + }; + } + + // remove envFile now that it's been handled + config.remove("entry"); + Some(()) +} diff --git a/crates/eval/Cargo.toml b/crates/eval/Cargo.toml index 7ecba7c1ec91facef139eb0b8e971a12f76361a7..d5db7f71a4593a66ee8218c053109041035428ab 100644 --- a/crates/eval/Cargo.toml +++ b/crates/eval/Cargo.toml @@ -32,7 +32,7 @@ client.workspace = true collections.workspace = true debug_adapter_extension.workspace = true dirs.workspace = true -dotenv.workspace = true +dotenvy.workspace = true env_logger.workspace = true extension.workspace = true fs.workspace = true diff --git a/crates/eval/src/eval.rs b/crates/eval/src/eval.rs index 5e8dd8961c8c3416fa84303eff722c22c31738e6..39121377bba907dbf38983156e1e0f55d187829a 100644 --- a/crates/eval/src/eval.rs +++ b/crates/eval/src/eval.rs @@ -63,7 +63,7 @@ struct Args { } fn main() { - dotenv::from_filename(CARGO_MANIFEST_DIR.join(".env")).ok(); + dotenvy::from_filename(CARGO_MANIFEST_DIR.join(".env")).ok(); env_logger::init(); From eb74df632bf8f49060ffa2863ae28a12d228a876 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Tue, 1 Jul 2025 18:58:55 +0200 Subject: [PATCH 050/134] debugger: Do not set exception breakpoints in initialization sequence in certain conditions (#33723) As pointed out in https://github.com/probe-rs/probe-rs/issues/3333, we violate the spec by sending setExceptionBreakpoints even when the adapter does not define any exceptions. Release Notes: - N/A --- crates/project/src/debugger/session.rs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/crates/project/src/debugger/session.rs b/crates/project/src/debugger/session.rs index 837bc4b81c5b3bc1fe7e54d9bc4fbbc42eeaede2..b76200aee6e923e81558e2c9e834c6d481f16ce6 100644 --- a/crates/project/src/debugger/session.rs +++ b/crates/project/src/debugger/session.rs @@ -420,6 +420,15 @@ impl RunningMode { .collect::>() }) .unwrap_or_default(); + // From spec (on initialization sequence): + // client sends a setExceptionBreakpoints request if one or more exceptionBreakpointFilters have been defined (or if supportsConfigurationDoneRequest is not true) + // + // Thus we should send setExceptionBreakpoints even if `exceptionFilters` variable is empty (as long as there were some options in the first place). + let should_send_exception_breakpoints = capabilities + .exception_breakpoint_filters + .as_ref() + .map_or(false, |filters| !filters.is_empty()) + || !configuration_done_supported; let supports_exception_filters = capabilities .supports_exception_filter_options .unwrap_or_default(); @@ -461,9 +470,12 @@ impl RunningMode { } })?; - this.send_exception_breakpoints(exception_filters, supports_exception_filters) - .await - .ok(); + if should_send_exception_breakpoints { + this.send_exception_breakpoints(exception_filters, supports_exception_filters) + .await + .ok(); + } + let ret = if configuration_done_supported { this.request(ConfigurationDone {}) } else { From 2ff155d5a2502700f9dd50d785f6f84436d636dd Mon Sep 17 00:00:00 2001 From: Michael Sloan Date: Tue, 1 Jul 2025 11:47:19 -0600 Subject: [PATCH 051/134] Fix language settings formatter regression - formatter list can be a single formatter not wrapped in an array (#33721) Fixes a regression from #33635 Release Notes: - N/A --- crates/collab/src/tests/integration_tests.rs | 18 ++--- .../remote_editing_collaboration_tests.rs | 10 +-- crates/editor/src/editor_tests.rs | 24 +++---- crates/language/src/language_settings.rs | 67 ++++++++++++------- crates/project/src/lsp_store.rs | 4 +- crates/project/src/prettier_store.rs | 1 + 6 files changed, 72 insertions(+), 52 deletions(-) diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 55427b1aa70fe59dd330e274ddade4839c73affd..d78db041f25767822c026f21695534df1b137964 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -22,7 +22,9 @@ use gpui::{ use language::{ Diagnostic, DiagnosticEntry, DiagnosticSourceKind, FakeLspAdapter, Language, LanguageConfig, LanguageMatcher, LineEnding, OffsetRangeExt, Point, Rope, - language_settings::{AllLanguageSettings, Formatter, PrettierSettings, SelectedFormatter}, + language_settings::{ + AllLanguageSettings, Formatter, FormatterList, PrettierSettings, SelectedFormatter, + }, tree_sitter_rust, tree_sitter_typescript, }; use lsp::{LanguageServerId, OneOf}; @@ -4589,13 +4591,14 @@ async fn test_formatting_buffer( cx_a.update(|cx| { SettingsStore::update_global(cx, |store, cx| { store.update_user_settings::(cx, |file| { - file.defaults.formatter = - Some(SelectedFormatter::List(vec![Formatter::External { + file.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single( + Formatter::External { command: "awk".into(), arguments: Some( vec!["{sub(/two/,\"{buffer_path}\")}1".to_string()].into(), ), - }])); + }, + ))); }); }); }); @@ -4695,10 +4698,9 @@ async fn test_prettier_formatting_buffer( cx_b.update(|cx| { SettingsStore::update_global(cx, |store, cx| { store.update_user_settings::(cx, |file| { - file.defaults.formatter = - Some(SelectedFormatter::List(vec![Formatter::LanguageServer { - name: None, - }])); + file.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single( + Formatter::LanguageServer { name: None }, + ))); file.defaults.prettier = Some(PrettierSettings { allowed: true, ..PrettierSettings::default() diff --git a/crates/collab/src/tests/remote_editing_collaboration_tests.rs b/crates/collab/src/tests/remote_editing_collaboration_tests.rs index 0e9b25dc380fee929274d2a84607e55ee6832cb8..7aeb381c02beeb6165e44ccd5bbd72f5744cc964 100644 --- a/crates/collab/src/tests/remote_editing_collaboration_tests.rs +++ b/crates/collab/src/tests/remote_editing_collaboration_tests.rs @@ -14,7 +14,8 @@ use http_client::BlockedHttpClient; use language::{ FakeLspAdapter, Language, LanguageConfig, LanguageMatcher, LanguageRegistry, language_settings::{ - AllLanguageSettings, Formatter, PrettierSettings, SelectedFormatter, language_settings, + AllLanguageSettings, Formatter, FormatterList, PrettierSettings, SelectedFormatter, + language_settings, }, tree_sitter_typescript, }; @@ -504,10 +505,9 @@ async fn test_ssh_collaboration_formatting_with_prettier( cx_b.update(|cx| { SettingsStore::update_global(cx, |store, cx| { store.update_user_settings::(cx, |file| { - file.defaults.formatter = - Some(SelectedFormatter::List(vec![Formatter::LanguageServer { - name: None, - }])); + file.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single( + Formatter::LanguageServer { name: None }, + ))); file.defaults.prettier = Some(PrettierSettings { allowed: true, ..PrettierSettings::default() diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 4f3a9bcd35b2918feb8d82adca21a2a556003776..cefc2a0fc1ee30720d8c5599507e4eebe91c519f 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -25,8 +25,8 @@ use language::{ DiagnosticSourceKind, FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher, LanguageName, Override, Point, language_settings::{ - AllLanguageSettings, AllLanguageSettingsContent, CompletionSettings, - LanguageSettingsContent, LspInsertMode, PrettierSettings, + AllLanguageSettings, AllLanguageSettingsContent, CompletionSettings, FormatterList, + LanguageSettingsContent, LspInsertMode, PrettierSettings, SelectedFormatter, }, tree_sitter_python, }; @@ -10012,9 +10012,9 @@ async fn test_range_format_during_save(cx: &mut TestAppContext) { #[gpui::test] async fn test_document_format_manual_trigger(cx: &mut TestAppContext) { init_test(cx, |settings| { - settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(vec![ + settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single( Formatter::LanguageServer { name: None }, - ])) + ))) }); let fs = FakeFs::new(cx.executor()); @@ -10141,7 +10141,7 @@ async fn test_document_format_manual_trigger(cx: &mut TestAppContext) { async fn test_multiple_formatters(cx: &mut TestAppContext) { init_test(cx, |settings| { settings.defaults.remove_trailing_whitespace_on_save = Some(true); - settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(vec![ + settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![ Formatter::LanguageServer { name: None }, Formatter::CodeActions( [ @@ -10151,7 +10151,7 @@ async fn test_multiple_formatters(cx: &mut TestAppContext) { .into_iter() .collect(), ), - ])) + ]))) }); let fs = FakeFs::new(cx.executor()); @@ -10403,9 +10403,9 @@ async fn test_multiple_formatters(cx: &mut TestAppContext) { #[gpui::test] async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) { init_test(cx, |settings| { - settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(vec![ + settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![ Formatter::LanguageServer { name: None }, - ])) + ]))) }); let fs = FakeFs::new(cx.executor()); @@ -10611,7 +10611,7 @@ async fn test_concurrent_format_requests(cx: &mut TestAppContext) { #[gpui::test] async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) { init_test(cx, |settings| { - settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto) + settings.defaults.formatter = Some(SelectedFormatter::Auto) }); let mut cx = EditorLspTestContext::new_rust( @@ -15878,9 +15878,9 @@ fn completion_menu_entries(menu: &CompletionsMenu) -> Vec { #[gpui::test] async fn test_document_format_with_prettier(cx: &mut TestAppContext) { init_test(cx, |settings| { - settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(vec![ + settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single( Formatter::Prettier, - ])) + ))) }); let fs = FakeFs::new(cx.executor()); @@ -15950,7 +15950,7 @@ async fn test_document_format_with_prettier(cx: &mut TestAppContext) { ); update_test_language_settings(cx, |settings| { - settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto) + settings.defaults.formatter = Some(SelectedFormatter::Auto) }); let format = editor.update_in(cx, |editor, window, cx| { editor.perform_format( diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs index bb143b38422b0b56c8a4713a94ccc68ed3c6d284..ff3a7ffcb488e68aa1ef32fbae10cfd12d73def0 100644 --- a/crates/language/src/language_settings.rs +++ b/crates/language/src/language_settings.rs @@ -21,7 +21,7 @@ use settings::{ replace_subschema, }; use shellexpand; -use std::{borrow::Cow, num::NonZeroU32, path::Path, sync::Arc}; +use std::{borrow::Cow, num::NonZeroU32, path::Path, slice, sync::Arc}; use util::serde::default_true; /// Initializes the language settings. @@ -673,7 +673,7 @@ pub enum FormatOnSave { On, /// Files should not be formatted on save. Off, - List(Vec), + List(FormatterList), } impl JsonSchema for FormatOnSave { @@ -692,7 +692,7 @@ impl JsonSchema for FormatOnSave { }, { "type": "string", - "enum": ["on", "off", "prettier", "language_server"] + "enum": ["on", "off", "language_server"] }, formatter_schema ] @@ -735,11 +735,11 @@ impl<'de> Deserialize<'de> for FormatOnSave { } else if v == "off" { Ok(Self::Value::Off) } else if v == "language_server" { - Ok(Self::Value::List(vec![Formatter::LanguageServer { - name: None, - }])) + Ok(Self::Value::List(FormatterList::Single( + Formatter::LanguageServer { name: None }, + ))) } else { - let ret: Result, _> = + let ret: Result = Deserialize::deserialize(v.into_deserializer()); ret.map(Self::Value::List) } @@ -748,7 +748,7 @@ impl<'de> Deserialize<'de> for FormatOnSave { where A: MapAccess<'d>, { - let ret: Result, _> = + let ret: Result = Deserialize::deserialize(de::value::MapAccessDeserializer::new(map)); ret.map(Self::Value::List) } @@ -756,7 +756,7 @@ impl<'de> Deserialize<'de> for FormatOnSave { where A: SeqAccess<'d>, { - let ret: Result, _> = + let ret: Result = Deserialize::deserialize(de::value::SeqAccessDeserializer::new(map)); ret.map(Self::Value::List) } @@ -793,7 +793,7 @@ pub enum SelectedFormatter { /// or falling back to formatting via language server. #[default] Auto, - List(Vec), + List(FormatterList), } impl JsonSchema for SelectedFormatter { @@ -812,7 +812,7 @@ impl JsonSchema for SelectedFormatter { }, { "type": "string", - "enum": ["auto", "prettier", "language_server"] + "enum": ["auto", "language_server"] }, formatter_schema ] @@ -852,11 +852,11 @@ impl<'de> Deserialize<'de> for SelectedFormatter { if v == "auto" { Ok(Self::Value::Auto) } else if v == "language_server" { - Ok(Self::Value::List(vec![Formatter::LanguageServer { - name: None, - }])) + Ok(Self::Value::List(FormatterList::Single( + Formatter::LanguageServer { name: None }, + ))) } else { - let ret: Result, _> = + let ret: Result = Deserialize::deserialize(v.into_deserializer()); ret.map(SelectedFormatter::List) } @@ -865,7 +865,7 @@ impl<'de> Deserialize<'de> for SelectedFormatter { where A: MapAccess<'d>, { - let ret: Result, _> = + let ret: Result = Deserialize::deserialize(de::value::MapAccessDeserializer::new(map)); ret.map(SelectedFormatter::List) } @@ -873,7 +873,7 @@ impl<'de> Deserialize<'de> for SelectedFormatter { where A: SeqAccess<'d>, { - let ret: Result, _> = + let ret: Result = Deserialize::deserialize(de::value::SeqAccessDeserializer::new(map)); ret.map(SelectedFormatter::List) } @@ -882,6 +882,23 @@ impl<'de> Deserialize<'de> for SelectedFormatter { } } +/// Controls which formatters should be used when formatting code. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(untagged)] +pub enum FormatterList { + Single(Formatter), + Vec(Vec), +} + +impl AsRef<[Formatter]> for FormatterList { + fn as_ref(&self) -> &[Formatter] { + match &self { + Self::Single(single) => slice::from_ref(single), + Self::Vec(v) => v, + } + } +} + /// Controls which formatter should be used when formatting code. If there are multiple formatters, they are executed in the order of declaration. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] @@ -1612,26 +1629,26 @@ mod tests { let settings: LanguageSettingsContent = serde_json::from_str(raw).unwrap(); assert_eq!( settings.formatter, - Some(SelectedFormatter::List(vec![Formatter::LanguageServer { - name: None - }])) + Some(SelectedFormatter::List(FormatterList::Single( + Formatter::LanguageServer { name: None } + ))) ); let raw = "{\"formatter\": [{\"language_server\": {\"name\": null}}]}"; let settings: LanguageSettingsContent = serde_json::from_str(raw).unwrap(); assert_eq!( settings.formatter, - Some(SelectedFormatter::List(vec![Formatter::LanguageServer { - name: None - }])) + Some(SelectedFormatter::List(FormatterList::Vec(vec![ + Formatter::LanguageServer { name: None } + ]))) ); let raw = "{\"formatter\": [{\"language_server\": {\"name\": null}}, \"prettier\"]}"; let settings: LanguageSettingsContent = serde_json::from_str(raw).unwrap(); assert_eq!( settings.formatter, - Some(SelectedFormatter::List(vec![ + Some(SelectedFormatter::List(FormatterList::Vec(vec![ Formatter::LanguageServer { name: None }, Formatter::Prettier - ])) + ]))) ); } diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 9e1a38a6d165c74a9ec1d340927b9875f464c5ec..dc402be2b6db4070e9754b549e847ce653593297 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -1405,7 +1405,7 @@ impl LocalLspStore { let formatters = match (trigger, &settings.format_on_save) { (FormatTrigger::Save, FormatOnSave::Off) => &[], - (FormatTrigger::Save, FormatOnSave::List(formatters)) => formatters.as_slice(), + (FormatTrigger::Save, FormatOnSave::List(formatters)) => formatters.as_ref(), (FormatTrigger::Manual, _) | (FormatTrigger::Save, FormatOnSave::On) => { match &settings.formatter { SelectedFormatter::Auto => { @@ -1417,7 +1417,7 @@ impl LocalLspStore { std::slice::from_ref(&Formatter::LanguageServer { name: None }) } } - SelectedFormatter::List(formatter_list) => formatter_list.as_slice(), + SelectedFormatter::List(formatter_list) => formatter_list.as_ref(), } } }; diff --git a/crates/project/src/prettier_store.rs b/crates/project/src/prettier_store.rs index 68a3ae8778c351b9290dccdc5355f0b3eb22562b..29997545cd484d0bacbd489b3c5fa058daa2f017 100644 --- a/crates/project/src/prettier_store.rs +++ b/crates/project/src/prettier_store.rs @@ -705,6 +705,7 @@ pub fn prettier_plugins_for_language( SelectedFormatter::Auto => Some(&language_settings.prettier.plugins), SelectedFormatter::List(list) => list + .as_ref() .contains(&Formatter::Prettier) .then_some(&language_settings.prettier.plugins), } From f1f19a32fbd8484891e247f5894975d8a5d90334 Mon Sep 17 00:00:00 2001 From: Michael Sloan Date: Tue, 1 Jul 2025 11:56:24 -0600 Subject: [PATCH 052/134] Use version equality constraint for `zed_llm_client` dependency (#33728) Closes #33578 Release Notes: - N/A --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index e7926f025fd4b367d366876ec120b43da487aa38..ae51d0da427a3030c8c1f7cbda97d35daba2b532 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -625,7 +625,7 @@ wasmtime = { version = "29", default-features = false, features = [ wasmtime-wasi = "29" which = "6.0.0" workspace-hack = "0.1.0" -zed_llm_client = "0.8.5" +zed_llm_client = "= 0.8.5" zstd = "0.11" [workspace.dependencies.async-stripe] From 0eee768e7bc46c7cfff9ede83c074d10f236c139 Mon Sep 17 00:00:00 2001 From: Ben Kunkle Date: Tue, 1 Jul 2025 12:58:38 -0500 Subject: [PATCH 053/134] keymap_ui: Separate action input into separate column and highlight as JSON (#33726) Closes #ISSUE Separates the action input in the Keymap UI into it's own column, and wraps the input in an `impl RenderOnce` element that highlights it as JSON. Release Notes: - N/A *or* Added/Fixed/Improved ... --- Cargo.lock | 3 + crates/settings_ui/Cargo.toml | 7 +- crates/settings_ui/src/keybindings.rs | 245 ++++++++++++++++++-------- 3 files changed, 183 insertions(+), 72 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 36d08ec201e47fa87db1df64e698654a227e7c80..27b6329a9f3dd6cb5d2663d95d864b66ebdc3176 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14567,6 +14567,7 @@ dependencies = [ name = "settings_ui" version = "0.1.0" dependencies = [ + "anyhow", "collections", "command_palette", "command_palette_hooks", @@ -14577,6 +14578,7 @@ dependencies = [ "fs", "fuzzy", "gpui", + "language", "log", "menu", "paths", @@ -14586,6 +14588,7 @@ dependencies = [ "serde", "settings", "theme", + "tree-sitter-json", "ui", "util", "workspace", diff --git a/crates/settings_ui/Cargo.toml b/crates/settings_ui/Cargo.toml index 7b01fcc0e6599dfd619d246a5a0a446e9bed4135..6db6d78cd61111edf09829cffd47fdc956c46efc 100644 --- a/crates/settings_ui/Cargo.toml +++ b/crates/settings_ui/Cargo.toml @@ -12,25 +12,28 @@ workspace = true path = "src/settings_ui.rs" [dependencies] +anyhow.workspace = true +collections.workspace = true command_palette.workspace = true command_palette_hooks.workspace = true component.workspace = true -collections.workspace = true db.workspace = true editor.workspace = true feature_flags.workspace = true fs.workspace = true fuzzy.workspace = true gpui.workspace = true +language.workspace = true log.workspace = true menu.workspace = true paths.workspace = true project.workspace = true -search.workspace = true schemars.workspace = true +search.workspace = true serde.workspace = true settings.workspace = true theme.workspace = true +tree-sitter-json.workspace = true ui.workspace = true util.workspace = true workspace-hack.workspace = true diff --git a/crates/settings_ui/src/keybindings.rs b/crates/settings_ui/src/keybindings.rs index 6021f74a4614d43b7ca0451e8988da410a2d4d0f..73b5d06ba0b0be1b8bfa2399af9a1a650330809c 100644 --- a/crates/settings_ui/src/keybindings.rs +++ b/crates/settings_ui/src/keybindings.rs @@ -1,17 +1,19 @@ use std::{ops::Range, sync::Arc}; +use anyhow::{Context as _, anyhow}; use collections::HashSet; -use db::anyhow::anyhow; use editor::{Editor, EditorEvent}; use feature_flags::FeatureFlagViewExt; use fs::Fs; use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::{ - AppContext as _, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, - FontWeight, Global, KeyContext, Keystroke, ModifiersChangedEvent, ScrollStrategy, Subscription, - WeakEntity, actions, div, + AppContext as _, AsyncApp, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, + FontWeight, Global, KeyContext, Keystroke, ModifiersChangedEvent, ScrollStrategy, StyledText, + Subscription, WeakEntity, actions, div, }; +use language::{Language, LanguageConfig}; use settings::KeybindSource; + use util::ResultExt; use ui::{ @@ -167,38 +169,47 @@ impl KeymapEditor { this } - fn update_matches(&mut self, cx: &mut Context) { - let query = self.filter_editor.read(cx).text(cx); - let string_match_candidates = self.string_match_candidates.clone(); - let executor = cx.background_executor().clone(); - let keybind_count = self.keybindings.len(); - let query = command_palette::normalize_action_query(&query); - let fuzzy_match = cx.background_spawn(async move { - fuzzy::match_strings( - &string_match_candidates, - &query, - true, - true, - keybind_count, - &Default::default(), - executor, - ) - .await - }); + fn current_query(&self, cx: &mut Context) -> String { + self.filter_editor.read(cx).text(cx) + } - cx.spawn(async move |this, cx| { - let matches = fuzzy_match.await; - this.update(cx, |this, cx| { - this.selected_index.take(); - this.scroll_to_item(0, ScrollStrategy::Top, cx); - this.matches = matches; - cx.notify(); - }) + fn update_matches(&self, cx: &mut Context) { + let query = self.current_query(cx); + + cx.spawn(async move |this, cx| Self::process_query(this, query, cx).await) + .detach(); + } + + async fn process_query( + this: WeakEntity, + query: String, + cx: &mut AsyncApp, + ) -> Result<(), db::anyhow::Error> { + let query = command_palette::normalize_action_query(&query); + let (string_match_candidates, keybind_count) = this.read_with(cx, |this, _| { + (this.string_match_candidates.clone(), this.keybindings.len()) + })?; + let executor = cx.background_executor().clone(); + let matches = fuzzy::match_strings( + &string_match_candidates, + &query, + true, + true, + keybind_count, + &Default::default(), + executor, + ) + .await; + this.update(cx, |this, cx| { + this.selected_index.take(); + this.scroll_to_item(0, ScrollStrategy::Top, cx); + this.matches = matches; + cx.notify(); }) - .detach(); } fn process_bindings( + json_language: Arc, cx: &mut Context, ) -> (Vec, Vec) { let key_bindings_ptr = cx.key_bindings(); @@ -227,6 +238,9 @@ impl KeymapEditor { let action_name = key_binding.action().name(); unmapped_action_names.remove(&action_name); + let action_input = key_binding + .action_input() + .map(|input| TextWithSyntaxHighlighting::new(input, json_language.clone())); let index = processed_bindings.len(); let string_match_candidate = StringMatchCandidate::new(index, &action_name); @@ -234,7 +248,7 @@ impl KeymapEditor { keystroke_text: keystroke_text.into(), ui_key_binding, action: action_name.into(), - action_input: key_binding.action_input(), + action_input, context: context.into(), source, }); @@ -259,24 +273,61 @@ impl KeymapEditor { (processed_bindings, string_match_candidates) } - fn update_keybindings(self: &mut KeymapEditor, cx: &mut Context) { - let (key_bindings, string_match_candidates) = Self::process_bindings(cx); - self.keybindings = key_bindings; - self.string_match_candidates = Arc::new(string_match_candidates); - self.matches = self - .string_match_candidates - .iter() - .enumerate() - .map(|(ix, candidate)| StringMatch { - candidate_id: ix, - score: 0.0, - positions: vec![], - string: candidate.string.clone(), - }) - .collect(); + fn update_keybindings(&mut self, cx: &mut Context) { + let workspace = self.workspace.clone(); + cx.spawn(async move |this, cx| { + let json_language = Self::load_json_language(workspace, cx).await; + let query = this.update(cx, |this, cx| { + let (key_bindings, string_match_candidates) = + Self::process_bindings(json_language.clone(), cx); + this.keybindings = key_bindings; + this.string_match_candidates = Arc::new(string_match_candidates); + this.matches = this + .string_match_candidates + .iter() + .enumerate() + .map(|(ix, candidate)| StringMatch { + candidate_id: ix, + score: 0.0, + positions: vec![], + string: candidate.string.clone(), + }) + .collect(); + this.current_query(cx) + })?; + // calls cx.notify + Self::process_query(this, query, cx).await + }) + .detach_and_log_err(cx); + } - self.update_matches(cx); - cx.notify(); + async fn load_json_language( + workspace: WeakEntity, + cx: &mut AsyncApp, + ) -> Arc { + let json_language_task = workspace + .read_with(cx, |workspace, cx| { + workspace + .project() + .read(cx) + .languages() + .language_for_name("JSON") + }) + .context("Failed to load JSON language") + .log_err(); + let json_language = match json_language_task { + Some(task) => task.await.context("Failed to load JSON language").log_err(), + None => None, + }; + return json_language.unwrap_or_else(|| { + Arc::new(Language::new( + LanguageConfig { + name: "JSON".into(), + ..Default::default() + }, + Some(tree_sitter_json::LANGUAGE.into()), + )) + }); } fn dispatch_context(&self, _window: &Window, _cx: &Context) -> KeyContext { @@ -409,7 +460,7 @@ struct ProcessedKeybinding { keystroke_text: SharedString, ui_key_binding: Option, action: SharedString, - action_input: Option, + action_input: Option, context: SharedString, source: Option<(KeybindSource, SharedString)>, } @@ -461,8 +512,8 @@ impl Render for KeymapEditor { Table::new() .interactable(&self.table_interaction_state) .striped() - .column_widths([rems(24.), rems(16.), rems(32.), rems(8.)]) - .header(["Command", "Keystrokes", "Context", "Source"]) + .column_widths([rems(16.), rems(16.), rems(16.), rems(32.), rems(8.)]) + .header(["Action", "Arguments", "Keystrokes", "Context", "Source"]) .selected_item_index(self.selected_index) .on_click_row(cx.processor(|this, row_index, _window, _cx| { this.selected_index = Some(row_index); @@ -475,30 +526,26 @@ impl Render for KeymapEditor { .filter_map(|index| { let candidate_id = this.matches.get(index)?.candidate_id; let binding = &this.keybindings[candidate_id]; - let action = h_flex() - .items_start() - .gap_1() - .child(binding.action.clone()) - .when_some( - binding.action_input.clone(), - |this, binding_input| this.child(binding_input), - ); + + let action = binding.action.clone().into_any_element(); let keystrokes = binding.ui_key_binding.clone().map_or( binding.keystroke_text.clone().into_any_element(), IntoElement::into_any_element, ); - let context = binding.context.clone(); + let action_input = binding + .action_input + .clone() + .map_or(gpui::Empty.into_any_element(), |input| { + input.into_any_element() + }); + let context = binding.context.clone().into_any_element(); let source = binding .source .clone() .map(|(_source, name)| name) - .unwrap_or_default(); - Some([ - action.into_any_element(), - keystrokes, - context.into_any_element(), - source.into_any_element(), - ]) + .unwrap_or_default() + .into_any_element(); + Some([action, action_input, keystrokes, context, source]) }) .collect() }), @@ -507,6 +554,58 @@ impl Render for KeymapEditor { } } +#[derive(Debug, Clone, IntoElement)] +struct TextWithSyntaxHighlighting { + text: SharedString, + language: Arc, +} + +impl TextWithSyntaxHighlighting { + pub fn new(text: impl Into, language: Arc) -> Self { + Self { + text: text.into(), + language, + } + } +} + +impl RenderOnce for TextWithSyntaxHighlighting { + fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement { + let text_style = window.text_style(); + let syntax_theme = cx.theme().syntax(); + + let text = self.text.clone(); + + let highlights = self + .language + .highlight_text(&text.as_ref().into(), 0..text.len()); + let mut runs = Vec::with_capacity(highlights.len()); + let mut offset = 0; + + for (highlight_range, highlight_id) in highlights { + // Add un-highlighted text before the current highlight + if highlight_range.start > offset { + runs.push(text_style.to_run(highlight_range.start - offset)); + } + + let mut run_style = text_style.clone(); + if let Some(highlight_style) = highlight_id.style(syntax_theme) { + run_style = run_style.highlight(highlight_style); + } + // add the highlighted range + runs.push(run_style.to_run(highlight_range.len())); + offset = highlight_range.end; + } + + // Add any remaining un-highlighted text + if offset < text.len() { + runs.push(text_style.to_run(text.len() - offset)); + } + + return StyledText::new(text).with_runs(runs); + } +} + struct KeybindingEditorModal { editing_keybind: ProcessedKeybinding, keybind_editor: Entity, @@ -658,7 +757,10 @@ async fn save_keybinding_update( keystrokes: existing_keystrokes, action_name: &existing.action, use_key_equivalents: false, - input: existing.action_input.as_ref().map(|input| input.as_ref()), + input: existing + .action_input + .as_ref() + .map(|input| input.text.as_ref()), }, target_source: existing .source @@ -669,7 +771,10 @@ async fn save_keybinding_update( keystrokes: new_keystrokes, action_name: &existing.action, use_key_equivalents: false, - input: existing.action_input.as_ref().map(|input| input.as_ref()), + input: existing + .action_input + .as_ref() + .map(|input| input.text.as_ref()), }, } } else { From 8d894dd1dff5f2b12f16d41bf74f5456bdcc5501 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 1 Jul 2025 14:10:00 -0400 Subject: [PATCH 054/134] collab: Add logs to Stripe usage sync job (#33731) This PR adds some additional logs to the Stripe usage sync job for monitoring purposes. Release Notes: - N/A --- crates/collab/src/api/billing.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/crates/collab/src/api/billing.rs b/crates/collab/src/api/billing.rs index e96f752c98e64f0c0f5c2562061d564ee674f886..c8df066cbf1bbefd0515000a093d34371842c387 100644 --- a/crates/collab/src/api/billing.rs +++ b/crates/collab/src/api/billing.rs @@ -1404,6 +1404,9 @@ async fn sync_model_request_usage_with_stripe( llm_db: &Arc, stripe_billing: &Arc, ) -> anyhow::Result<()> { + log::info!("Stripe usage sync: Starting"); + let started_at = Utc::now(); + let staff_users = app.db.get_staff_users().await?; let staff_user_ids = staff_users .iter() @@ -1448,6 +1451,10 @@ async fn sync_model_request_usage_with_stripe( .find_price_by_lookup_key("claude-3-7-sonnet-requests-max") .await?; + let usage_meter_count = usage_meters.len(); + + log::info!("Stripe usage sync: Syncing {usage_meter_count} usage meters"); + for (usage_meter, usage) in usage_meters { maybe!(async { let Some((billing_customer, billing_subscription)) = @@ -1504,5 +1511,10 @@ async fn sync_model_request_usage_with_stripe( .log_err(); } + log::info!( + "Stripe usage sync: Synced {usage_meter_count} usage meters in {:?}", + Utc::now() - started_at + ); + Ok(()) } From 6b0668572378378b4173186e28bd594d6e894db7 Mon Sep 17 00:00:00 2001 From: Peter Tripp Date: Tue, 1 Jul 2025 14:21:26 -0400 Subject: [PATCH 055/134] Revert "settings: Remove `version` field migration" (#33729) - Reverts zed-industries/zed#33711 I think we should just make this a breaking change with v0.194.x. Forwards compatibility is hard, we should build abstractions that make this easier (next time). See also: - https://github.com/zed-industries/zed/pull/33372 Release Notes: - N/A --- crates/migrator/src/migrations.rs | 6 + .../src/migrations/m_2025_06_25/settings.rs | 133 ++++++++++++++++++ crates/migrator/src/migrator.rs | 79 +++++++++++ 3 files changed, 218 insertions(+) create mode 100644 crates/migrator/src/migrations/m_2025_06_25/settings.rs diff --git a/crates/migrator/src/migrations.rs b/crates/migrator/src/migrations.rs index 84ef0f0456e65da7dbb604e7733fec8ab1cdd386..4e3839358b6e55b0cbec72f92ca12d9b2b30c168 100644 --- a/crates/migrator/src/migrations.rs +++ b/crates/migrator/src/migrations.rs @@ -82,6 +82,12 @@ pub(crate) mod m_2025_06_16 { pub(crate) use settings::SETTINGS_PATTERNS; } +pub(crate) mod m_2025_06_25 { + mod settings; + + pub(crate) use settings::SETTINGS_PATTERNS; +} + pub(crate) mod m_2025_06_27 { mod settings; diff --git a/crates/migrator/src/migrations/m_2025_06_25/settings.rs b/crates/migrator/src/migrations/m_2025_06_25/settings.rs new file mode 100644 index 0000000000000000000000000000000000000000..5dd6c3093a43b00acff3db6c1e316a3fc6664175 --- /dev/null +++ b/crates/migrator/src/migrations/m_2025_06_25/settings.rs @@ -0,0 +1,133 @@ +use std::ops::Range; +use tree_sitter::{Query, QueryMatch}; + +use crate::MigrationPatterns; + +pub const SETTINGS_PATTERNS: MigrationPatterns = &[ + (SETTINGS_VERSION_PATTERN, remove_version_fields), + ( + SETTINGS_NESTED_VERSION_PATTERN, + remove_nested_version_fields, + ), +]; + +const SETTINGS_VERSION_PATTERN: &str = r#"(document + (object + (pair + key: (string (string_content) @key) + value: (object + (pair + key: (string (string_content) @version_key) + value: (_) @version_value + ) @version_pair + ) + ) + ) + (#eq? @key "agent") + (#eq? @version_key "version") +)"#; + +const SETTINGS_NESTED_VERSION_PATTERN: &str = r#"(document + (object + (pair + key: (string (string_content) @language_models) + value: (object + (pair + key: (string (string_content) @provider) + value: (object + (pair + key: (string (string_content) @version_key) + value: (_) @version_value + ) @version_pair + ) + ) + ) + ) + ) + (#eq? @language_models "language_models") + (#match? @provider "^(anthropic|openai)$") + (#eq? @version_key "version") +)"#; + +fn remove_version_fields( + contents: &str, + mat: &QueryMatch, + query: &Query, +) -> Option<(Range, String)> { + let version_pair_ix = query.capture_index_for_name("version_pair")?; + let version_pair_node = mat.nodes_for_capture_index(version_pair_ix).next()?; + + remove_pair_with_whitespace(contents, version_pair_node) +} + +fn remove_nested_version_fields( + contents: &str, + mat: &QueryMatch, + query: &Query, +) -> Option<(Range, String)> { + let version_pair_ix = query.capture_index_for_name("version_pair")?; + let version_pair_node = mat.nodes_for_capture_index(version_pair_ix).next()?; + + remove_pair_with_whitespace(contents, version_pair_node) +} + +fn remove_pair_with_whitespace( + contents: &str, + pair_node: tree_sitter::Node, +) -> Option<(Range, String)> { + let mut range_to_remove = pair_node.byte_range(); + + // Check if there's a comma after this pair + if let Some(next_sibling) = pair_node.next_sibling() { + if next_sibling.kind() == "," { + range_to_remove.end = next_sibling.end_byte(); + } + } else { + // If no next sibling, check if there's a comma before + if let Some(prev_sibling) = pair_node.prev_sibling() { + if prev_sibling.kind() == "," { + range_to_remove.start = prev_sibling.start_byte(); + } + } + } + + // Include any leading whitespace/newline, including comments + let text_before = &contents[..range_to_remove.start]; + if let Some(last_newline) = text_before.rfind('\n') { + let whitespace_start = last_newline + 1; + let potential_whitespace = &contents[whitespace_start..range_to_remove.start]; + + // Check if it's only whitespace or comments + let mut is_whitespace_or_comment = true; + let mut in_comment = false; + let mut chars = potential_whitespace.chars().peekable(); + + while let Some(ch) = chars.next() { + if in_comment { + if ch == '\n' { + in_comment = false; + } + } else if ch == '/' && chars.peek() == Some(&'/') { + in_comment = true; + chars.next(); // Skip the second '/' + } else if !ch.is_whitespace() { + is_whitespace_or_comment = false; + break; + } + } + + if is_whitespace_or_comment { + range_to_remove.start = whitespace_start; + } + } + + // Also check if we need to include trailing whitespace up to the next line + let text_after = &contents[range_to_remove.end..]; + if let Some(newline_pos) = text_after.find('\n') { + if text_after[..newline_pos].chars().all(|c| c.is_whitespace()) { + range_to_remove.end += newline_pos + 1; + } + } + + Some((range_to_remove, String::new())) +} diff --git a/crates/migrator/src/migrator.rs b/crates/migrator/src/migrator.rs index 06e96a6f865c227579ab2452426b5d8cf46fda7c..be32b2734e66ebd621ff858a79eef322468b11ae 100644 --- a/crates/migrator/src/migrator.rs +++ b/crates/migrator/src/migrator.rs @@ -152,6 +152,10 @@ pub fn migrate_settings(text: &str) -> Result> { migrations::m_2025_06_16::SETTINGS_PATTERNS, &SETTINGS_QUERY_2025_06_16, ), + ( + migrations::m_2025_06_25::SETTINGS_PATTERNS, + &SETTINGS_QUERY_2025_06_25, + ), ( migrations::m_2025_06_27::SETTINGS_PATTERNS, &SETTINGS_QUERY_2025_06_27, @@ -258,6 +262,10 @@ define_query!( SETTINGS_QUERY_2025_06_16, migrations::m_2025_06_16::SETTINGS_PATTERNS ); +define_query!( + SETTINGS_QUERY_2025_06_25, + migrations::m_2025_06_25::SETTINGS_PATTERNS +); define_query!( SETTINGS_QUERY_2025_06_27, migrations::m_2025_06_27::SETTINGS_PATTERNS @@ -1081,6 +1089,77 @@ mod tests { ); } + #[test] + fn test_remove_version_fields() { + assert_migrate_settings( + r#"{ + "language_models": { + "anthropic": { + "version": "1", + "api_url": "https://api.anthropic.com" + }, + "openai": { + "version": "1", + "api_url": "https://api.openai.com/v1" + } + }, + "agent": { + "version": "2", + "enabled": true, + "preferred_completion_mode": "normal", + "button": true, + "dock": "right", + "default_width": 640, + "default_height": 320, + "default_model": { + "provider": "zed.dev", + "model": "claude-sonnet-4" + } + } +}"#, + Some( + r#"{ + "language_models": { + "anthropic": { + "api_url": "https://api.anthropic.com" + }, + "openai": { + "api_url": "https://api.openai.com/v1" + } + }, + "agent": { + "enabled": true, + "preferred_completion_mode": "normal", + "button": true, + "dock": "right", + "default_width": 640, + "default_height": 320, + "default_model": { + "provider": "zed.dev", + "model": "claude-sonnet-4" + } + } +}"#, + ), + ); + + // Test that version fields in other contexts are not removed + assert_migrate_settings( + r#"{ + "language_models": { + "other_provider": { + "version": "1", + "api_url": "https://api.example.com" + } + }, + "other_section": { + "version": "1" + } +}"#, + None, + ); + } + #[test] fn test_flatten_context_server_command() { assert_migrate_settings( From 0e2e5b8b0df07c210feba3fad4264a6fa031acf1 Mon Sep 17 00:00:00 2001 From: Anthony Eid <56899983+Anthony-Eid@users.noreply.github.com> Date: Tue, 1 Jul 2025 15:43:58 -0400 Subject: [PATCH 056/134] debugger: Debug sessions rerun build tasks by default when restarting (#33724) We reworked the debug modal spawning to use the task context from past debug sessions when spawning a debug scenario based on task inventory history. We changed restart session keybinding to rerun session too. Closes #31369 Release Notes: - Restarting a debug session now reruns build tasks that are associated with the session --------- Co-authored-by: Cole Miller --- assets/keymaps/default-linux.json | 4 +- assets/keymaps/default-macos.json | 4 +- crates/debugger_ui/src/debugger_panel.rs | 76 +++++++++++-------- crates/debugger_ui/src/debugger_ui.rs | 30 +++++--- crates/debugger_ui/src/new_process_modal.rs | 60 ++++++++++----- crates/debugger_ui/src/session/running.rs | 36 ++++++++- crates/debugger_ui/src/tests.rs | 1 + .../src/tests/new_process_modal.rs | 9 ++- crates/editor/src/editor.rs | 9 ++- crates/project/src/project.rs | 3 +- crates/project/src/task_inventory.rs | 50 ++++++++---- crates/workspace/src/tasks.rs | 12 ++- crates/workspace/src/workspace.rs | 1 + 13 files changed, 213 insertions(+), 82 deletions(-) diff --git a/assets/keymaps/default-linux.json b/assets/keymaps/default-linux.json index c88df28b99d2408c4b2f99ef98928bac4cb266c8..9d1b040f56d7c095cc150133480159b7b23974c0 100644 --- a/assets/keymaps/default-linux.json +++ b/assets/keymaps/default-linux.json @@ -34,7 +34,7 @@ "ctrl-q": "zed::Quit", "f4": "debugger::Start", "shift-f5": "debugger::Stop", - "ctrl-shift-f5": "debugger::Restart", + "ctrl-shift-f5": "debugger::RerunSession", "f6": "debugger::Pause", "f7": "debugger::StepOver", "ctrl-f11": "debugger::StepInto", @@ -598,7 +598,7 @@ // "foo-bar": ["task::Spawn", { "task_name": "MyTask", "reveal_target": "dock" }] // or by tag: // "foo-bar": ["task::Spawn", { "task_tag": "MyTag" }], - "f5": "debugger::RerunLastSession" + "f5": "debugger::Rerun" } }, { diff --git a/assets/keymaps/default-macos.json b/assets/keymaps/default-macos.json index 7d6ce6e80a981cd8606c7b4e5f2b51d35205c41c..3a4cbcfaccc262d2da20b7e73912228637fd3754 100644 --- a/assets/keymaps/default-macos.json +++ b/assets/keymaps/default-macos.json @@ -5,7 +5,7 @@ "bindings": { "f4": "debugger::Start", "shift-f5": "debugger::Stop", - "shift-cmd-f5": "debugger::Restart", + "shift-cmd-f5": "debugger::RerunSession", "f6": "debugger::Pause", "f7": "debugger::StepOver", "f11": "debugger::StepInto", @@ -652,7 +652,7 @@ "cmd-k shift-up": "workspace::SwapPaneUp", "cmd-k shift-down": "workspace::SwapPaneDown", "cmd-shift-x": "zed::Extensions", - "f5": "debugger::RerunLastSession" + "f5": "debugger::Rerun" } }, { diff --git a/crates/debugger_ui/src/debugger_panel.rs b/crates/debugger_ui/src/debugger_panel.rs index 795b4caf9e43a28c8bf115755332fa9976d89d93..cb48083192d6e615bcc6d4c94be89b467fb031da 100644 --- a/crates/debugger_ui/src/debugger_panel.rs +++ b/crates/debugger_ui/src/debugger_panel.rs @@ -5,7 +5,7 @@ use crate::session::running::breakpoint_list::BreakpointList; use crate::{ ClearAllBreakpoints, Continue, CopyDebugAdapterArguments, Detach, FocusBreakpointList, FocusConsole, FocusFrames, FocusLoadedSources, FocusModules, FocusTerminal, FocusVariables, - NewProcessModal, NewProcessMode, Pause, Restart, StepInto, StepOut, StepOver, Stop, + NewProcessModal, NewProcessMode, Pause, RerunSession, StepInto, StepOut, StepOver, Stop, ToggleExpandItem, ToggleSessionPicker, ToggleThreadPicker, persistence, spawn_task_or_modal, }; use anyhow::{Context as _, Result, anyhow}; @@ -25,7 +25,7 @@ use gpui::{ use itertools::Itertools as _; use language::Buffer; use project::debugger::session::{Session, SessionStateEvent}; -use project::{Fs, ProjectPath, WorktreeId}; +use project::{DebugScenarioContext, Fs, ProjectPath, WorktreeId}; use project::{Project, debugger::session::ThreadStatus}; use rpc::proto::{self}; use settings::Settings; @@ -197,6 +197,7 @@ impl DebugPanel { .and_then(|buffer| buffer.read(cx).file()) .map(|f| f.worktree_id(cx)) }); + let Some(worktree) = worktree .and_then(|id| self.project.read(cx).worktree_for_id(id, cx)) .or_else(|| self.project.read(cx).visible_worktrees(cx).next()) @@ -204,6 +205,7 @@ impl DebugPanel { log::debug!("Could not find a worktree to spawn the debug session in"); return; }; + self.debug_scenario_scheduled_last = true; if let Some(inventory) = self .project @@ -214,7 +216,15 @@ impl DebugPanel { .cloned() { inventory.update(cx, |inventory, _| { - inventory.scenario_scheduled(scenario.clone()); + inventory.scenario_scheduled( + scenario.clone(), + // todo(debugger): Task context is cloned three times + // once in Session,inventory, and in resolve scenario + // we should wrap it in an RC instead to save some memory + task_context.clone(), + worktree_id, + active_buffer.as_ref().map(|buffer| buffer.downgrade()), + ); }) } let task = cx.spawn_in(window, { @@ -225,6 +235,16 @@ impl DebugPanel { let definition = debug_session .update_in(cx, |debug_session, window, cx| { debug_session.running_state().update(cx, |running, cx| { + if scenario.build.is_some() { + running.scenario = Some(scenario.clone()); + running.scenario_context = Some(DebugScenarioContext { + active_buffer: active_buffer + .as_ref() + .map(|entity| entity.downgrade()), + task_context: task_context.clone(), + worktree_id: worktree_id, + }); + }; running.resolve_scenario( scenario, task_context, @@ -273,7 +293,8 @@ impl DebugPanel { return; }; let workspace = self.workspace.clone(); - let Some(scenario) = task_inventory.read(cx).last_scheduled_scenario().cloned() else { + let Some((scenario, context)) = task_inventory.read(cx).last_scheduled_scenario().cloned() + else { window.defer(cx, move |window, cx| { workspace .update(cx, |workspace, cx| { @@ -284,28 +305,22 @@ impl DebugPanel { return; }; - cx.spawn_in(window, async move |this, cx| { - let task_contexts = workspace - .update_in(cx, |workspace, window, cx| { - tasks_ui::task_contexts(workspace, window, cx) - })? - .await; + let DebugScenarioContext { + task_context, + worktree_id, + active_buffer, + } = context; - let task_context = task_contexts.active_context().cloned().unwrap_or_default(); - let worktree_id = task_contexts.worktree(); + let active_buffer = active_buffer.and_then(|buffer| buffer.upgrade()); - this.update_in(cx, |this, window, cx| { - this.start_session( - scenario.clone(), - task_context, - None, - worktree_id, - window, - cx, - ); - }) - }) - .detach(); + self.start_session( + scenario, + task_context, + active_buffer, + worktree_id, + window, + cx, + ); } pub(crate) async fn register_session( @@ -758,16 +773,16 @@ impl DebugPanel { .icon_size(IconSize::XSmall) .on_click(window.listener_for( &running_state, - |this, _, _window, cx| { - this.restart_session(cx); + |this, _, window, cx| { + this.rerun_session(window, cx); }, )) .tooltip({ let focus_handle = focus_handle.clone(); move |window, cx| { Tooltip::for_action_in( - "Restart", - &Restart, + "Rerun Session", + &RerunSession, &focus_handle, window, cx, @@ -1600,12 +1615,13 @@ impl workspace::DebuggerProvider for DebuggerProvider { definition: DebugScenario, context: TaskContext, buffer: Option>, + worktree_id: Option, window: &mut Window, cx: &mut App, ) { self.0.update(cx, |_, cx| { - cx.defer_in(window, |this, window, cx| { - this.start_session(definition, context, buffer, None, window, cx); + cx.defer_in(window, move |this, window, cx| { + this.start_session(definition, context, buffer, worktree_id, window, cx); }) }) } diff --git a/crates/debugger_ui/src/debugger_ui.rs b/crates/debugger_ui/src/debugger_ui.rs index ade1308f0614f193fadd15b5b9a00077ba739b2f..71b3ce1a31a9722d384379f6535617bc0c94f56a 100644 --- a/crates/debugger_ui/src/debugger_ui.rs +++ b/crates/debugger_ui/src/debugger_ui.rs @@ -37,6 +37,7 @@ actions!( Detach, Pause, Restart, + RerunSession, StepInto, StepOver, StepOut, @@ -54,7 +55,8 @@ actions!( ShowStackTrace, ToggleThreadPicker, ToggleSessionPicker, - RerunLastSession, + #[action(deprecated_aliases = ["debugger::RerunLastSession"])] + Rerun, ToggleExpandItem, ] ); @@ -74,17 +76,15 @@ pub fn init(cx: &mut App) { .register_action(|workspace: &mut Workspace, _: &Start, window, cx| { NewProcessModal::show(workspace, window, NewProcessMode::Debug, None, cx); }) - .register_action( - |workspace: &mut Workspace, _: &RerunLastSession, window, cx| { - let Some(debug_panel) = workspace.panel::(cx) else { - return; - }; + .register_action(|workspace: &mut Workspace, _: &Rerun, window, cx| { + let Some(debug_panel) = workspace.panel::(cx) else { + return; + }; - debug_panel.update(cx, |debug_panel, cx| { - debug_panel.rerun_last_session(workspace, window, cx); - }) - }, - ) + debug_panel.update(cx, |debug_panel, cx| { + debug_panel.rerun_last_session(workspace, window, cx); + }) + }) .register_action( |workspace: &mut Workspace, _: &ShutdownDebugAdapters, _window, cx| { workspace.project().update(cx, |project, cx| { @@ -210,6 +210,14 @@ pub fn init(cx: &mut App) { .ok(); } }) + .on_action({ + let active_item = active_item.clone(); + move |_: &RerunSession, window, cx| { + active_item + .update(cx, |item, cx| item.rerun_session(window, cx)) + .ok(); + } + }) .on_action({ let active_item = active_item.clone(); move |_: &Stop, _, cx| { diff --git a/crates/debugger_ui/src/new_process_modal.rs b/crates/debugger_ui/src/new_process_modal.rs index 126eadbd9294fc12fc32bac5b124c6611b480554..e857e336775cfc42fd073ba199f855a967656e12 100644 --- a/crates/debugger_ui/src/new_process_modal.rs +++ b/crates/debugger_ui/src/new_process_modal.rs @@ -23,7 +23,9 @@ use gpui::{ }; use itertools::Itertools as _; use picker::{Picker, PickerDelegate, highlighted_match_with_paths::HighlightedMatch}; -use project::{ProjectPath, TaskContexts, TaskSourceKind, task_store::TaskStore}; +use project::{ + DebugScenarioContext, ProjectPath, TaskContexts, TaskSourceKind, task_store::TaskStore, +}; use settings::{Settings, initial_local_debug_tasks_content}; use task::{DebugScenario, RevealTarget, ZedDebugConfig}; use theme::ThemeSettings; @@ -92,6 +94,7 @@ impl NewProcessModal { cx.spawn_in(window, async move |workspace, cx| { let task_contexts = workspace.update_in(cx, |workspace, window, cx| { + // todo(debugger): get the buffer here (if the active item is an editor) and store it so we can pass it to start_session later tasks_ui::task_contexts(workspace, window, cx) })?; workspace.update_in(cx, |workspace, window, cx| { @@ -1110,7 +1113,11 @@ pub(super) struct TaskMode { pub(super) struct DebugDelegate { task_store: Entity, - candidates: Vec<(Option, DebugScenario)>, + candidates: Vec<( + Option, + DebugScenario, + Option, + )>, selected_index: usize, matches: Vec, prompt: String, @@ -1208,7 +1215,11 @@ impl DebugDelegate { this.delegate.candidates = recent .into_iter() - .map(|scenario| Self::get_scenario_kind(&languages, &dap_registry, scenario)) + .map(|(scenario, context)| { + let (kind, scenario) = + Self::get_scenario_kind(&languages, &dap_registry, scenario); + (kind, scenario, Some(context)) + }) .chain( scenarios .into_iter() @@ -1223,7 +1234,7 @@ impl DebugDelegate { .map(|(kind, scenario)| { let (language, scenario) = Self::get_scenario_kind(&languages, &dap_registry, scenario); - (language.or(Some(kind)), scenario) + (language.or(Some(kind)), scenario, None) }), ) .collect(); @@ -1269,7 +1280,7 @@ impl PickerDelegate for DebugDelegate { let candidates: Vec<_> = candidates .into_iter() .enumerate() - .map(|(index, (_, candidate))| { + .map(|(index, (_, candidate, _))| { StringMatchCandidate::new(index, candidate.label.as_ref()) }) .collect(); @@ -1434,25 +1445,40 @@ impl PickerDelegate for DebugDelegate { .get(self.selected_index()) .and_then(|match_candidate| self.candidates.get(match_candidate.candidate_id).cloned()); - let Some((_, debug_scenario)) = debug_scenario else { + let Some((_, debug_scenario, context)) = debug_scenario else { return; }; - let (task_context, worktree_id) = self - .task_contexts - .as_ref() - .and_then(|task_contexts| { - Some(( - task_contexts.active_context().cloned()?, - task_contexts.worktree(), - )) - }) - .unwrap_or_default(); + let context = context.unwrap_or_else(|| { + self.task_contexts + .as_ref() + .and_then(|task_contexts| { + Some(DebugScenarioContext { + task_context: task_contexts.active_context().cloned()?, + active_buffer: None, + worktree_id: task_contexts.worktree(), + }) + }) + .unwrap_or_default() + }); + let DebugScenarioContext { + task_context, + active_buffer, + worktree_id, + } = context; + let active_buffer = active_buffer.and_then(|buffer| buffer.upgrade()); send_telemetry(&debug_scenario, TelemetrySpawnLocation::ScenarioList, cx); self.debug_panel .update(cx, |panel, cx| { - panel.start_session(debug_scenario, task_context, None, worktree_id, window, cx); + panel.start_session( + debug_scenario, + task_context, + active_buffer, + worktree_id, + window, + cx, + ); }) .ok(); diff --git a/crates/debugger_ui/src/session/running.rs b/crates/debugger_ui/src/session/running.rs index 91e49059e92609d2639d043220fa042cc70b708b..b9f373daa4b6afc96e63817d64b686840a2d0738 100644 --- a/crates/debugger_ui/src/session/running.rs +++ b/crates/debugger_ui/src/session/running.rs @@ -33,7 +33,7 @@ use language::Buffer; use loaded_source_list::LoadedSourceList; use module_list::ModuleList; use project::{ - Project, WorktreeId, + DebugScenarioContext, Project, WorktreeId, debugger::session::{Session, SessionEvent, ThreadId, ThreadStatus}, terminals::TerminalKind, }; @@ -79,6 +79,8 @@ pub struct RunningState { pane_close_subscriptions: HashMap, dock_axis: Axis, _schedule_serialize: Option>, + pub(crate) scenario: Option, + pub(crate) scenario_context: Option, } impl RunningState { @@ -831,6 +833,8 @@ impl RunningState { debug_terminal, dock_axis, _schedule_serialize: None, + scenario: None, + scenario_context: None, } } @@ -1039,7 +1043,7 @@ impl RunningState { let scenario = dap_registry .adapter(&adapter) .with_context(|| anyhow!("{}: is not a valid adapter name", &adapter))?.config_from_zed_format(zed_config) -.await?; + .await?; config = scenario.config; util::merge_non_null_json_value_into(extra_config, &mut config); @@ -1525,6 +1529,34 @@ impl RunningState { }); } + pub fn rerun_session(&mut self, window: &mut Window, cx: &mut Context) { + if let Some((scenario, context)) = self.scenario.take().zip(self.scenario_context.take()) + && scenario.build.is_some() + { + let DebugScenarioContext { + task_context, + active_buffer, + worktree_id, + } = context; + let active_buffer = active_buffer.and_then(|buffer| buffer.upgrade()); + + self.workspace + .update(cx, |workspace, cx| { + workspace.start_debug_session( + scenario, + task_context, + active_buffer, + worktree_id, + window, + cx, + ) + }) + .ok(); + } else { + self.restart_session(cx); + } + } + pub fn restart_session(&self, cx: &mut Context) { self.session().update(cx, |state, cx| { state.restart(None, cx); diff --git a/crates/debugger_ui/src/tests.rs b/crates/debugger_ui/src/tests.rs index 0828f137142a4604b4c3075395a61987a4ea4769..ac3fdf1f18c8250e0d8c3b897682652e694e2170 100644 --- a/crates/debugger_ui/src/tests.rs +++ b/crates/debugger_ui/src/tests.rs @@ -115,6 +115,7 @@ pub fn start_debug_session_with) + 'static>( config.to_scenario(), TaskContext::default(), None, + None, window, cx, ) diff --git a/crates/debugger_ui/src/tests/new_process_modal.rs b/crates/debugger_ui/src/tests/new_process_modal.rs index eb8c7f8063f23b8097efca4a16c071b1649cb903..81c5f7b5983bf351079ec4244e3e7b10170a28ca 100644 --- a/crates/debugger_ui/src/tests/new_process_modal.rs +++ b/crates/debugger_ui/src/tests/new_process_modal.rs @@ -141,7 +141,14 @@ async fn test_debug_session_substitutes_variables_and_relativizes_paths( workspace .update(cx, |workspace, window, cx| { - workspace.start_debug_session(scenario, task_context.clone(), None, window, cx) + workspace.start_debug_session( + scenario, + task_context.clone(), + None, + None, + window, + cx, + ) }) .unwrap(); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index fe904ab4ec09faba7ce91fd3600363bde05339a0..baa8e1a21cd7d1cd9c6f779e871fdeb5be4e8b8c 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -6186,7 +6186,14 @@ impl Editor { workspace.update(cx, |workspace, cx| { dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx); - workspace.start_debug_session(scenario, context, Some(buffer), window, cx); + workspace.start_debug_session( + scenario, + context, + Some(buffer), + None, + window, + cx, + ); }); Some(Task::ready(Ok(()))) } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 060e7c0415ba86052e78ba45e5d8fe581f0761ce..ee144b8b36f68be09d341966a7c397370d4242eb 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -131,7 +131,8 @@ pub use language::Location; #[cfg(any(test, feature = "test-support"))] pub use prettier::FORMAT_SUFFIX as TEST_PRETTIER_FORMAT_SUFFIX; pub use task_inventory::{ - BasicContextProvider, ContextProviderWithTasks, Inventory, TaskContexts, TaskSourceKind, + BasicContextProvider, ContextProviderWithTasks, DebugScenarioContext, Inventory, TaskContexts, + TaskSourceKind, }; pub use buffer_store::ProjectTransaction; diff --git a/crates/project/src/task_inventory.rs b/crates/project/src/task_inventory.rs index 51399bd0ef261a6121765e8b2180d2ba0f828638..d0f1c71daf797681d08a741ea15ab28f4a9289a0 100644 --- a/crates/project/src/task_inventory.rs +++ b/crates/project/src/task_inventory.rs @@ -12,7 +12,7 @@ use anyhow::Result; use collections::{HashMap, HashSet, VecDeque}; use dap::DapRegistry; use fs::Fs; -use gpui::{App, AppContext as _, Context, Entity, SharedString, Task}; +use gpui::{App, AppContext as _, Context, Entity, SharedString, Task, WeakEntity}; use itertools::Itertools; use language::{ Buffer, ContextLocation, ContextProvider, File, Language, LanguageToolchainStore, Location, @@ -31,11 +31,18 @@ use worktree::WorktreeId; use crate::{task_store::TaskSettingsLocation, worktree_store::WorktreeStore}; +#[derive(Clone, Debug, Default)] +pub struct DebugScenarioContext { + pub task_context: TaskContext, + pub worktree_id: Option, + pub active_buffer: Option>, +} + /// Inventory tracks available tasks for a given project. pub struct Inventory { fs: Arc, last_scheduled_tasks: VecDeque<(TaskSourceKind, ResolvedTask)>, - last_scheduled_scenarios: VecDeque, + last_scheduled_scenarios: VecDeque<(DebugScenario, DebugScenarioContext)>, templates_from_settings: InventoryFor, scenarios_from_settings: InventoryFor, } @@ -245,16 +252,29 @@ impl Inventory { }) } - pub fn scenario_scheduled(&mut self, scenario: DebugScenario) { + pub fn scenario_scheduled( + &mut self, + scenario: DebugScenario, + task_context: TaskContext, + worktree_id: Option, + active_buffer: Option>, + ) { self.last_scheduled_scenarios - .retain(|s| s.label != scenario.label); - self.last_scheduled_scenarios.push_back(scenario); + .retain(|(s, _)| s.label != scenario.label); + self.last_scheduled_scenarios.push_back(( + scenario, + DebugScenarioContext { + task_context, + worktree_id, + active_buffer, + }, + )); if self.last_scheduled_scenarios.len() > 5_000 { self.last_scheduled_scenarios.pop_front(); } } - pub fn last_scheduled_scenario(&self) -> Option<&DebugScenario> { + pub fn last_scheduled_scenario(&self) -> Option<&(DebugScenario, DebugScenarioContext)> { self.last_scheduled_scenarios.back() } @@ -265,7 +285,10 @@ impl Inventory { current_resolved_tasks: Vec<(TaskSourceKind, task::ResolvedTask)>, add_current_language_tasks: bool, cx: &mut App, - ) -> Task<(Vec, Vec<(TaskSourceKind, DebugScenario)>)> { + ) -> Task<( + Vec<(DebugScenario, DebugScenarioContext)>, + Vec<(TaskSourceKind, DebugScenario)>, + )> { let mut scenarios = Vec::new(); if let Some(worktree_id) = task_contexts @@ -765,7 +788,7 @@ impl Inventory { } } } - self.last_scheduled_scenarios.retain_mut(|scenario| { + self.last_scheduled_scenarios.retain_mut(|(scenario, _)| { if !previously_existing_scenarios.contains(&scenario.label) { return true; } @@ -1304,7 +1327,7 @@ mod tests { .clone(); inventory.update(cx, |this, _| { - this.scenario_scheduled(scenario.clone()); + this.scenario_scheduled(scenario.clone(), TaskContext::default(), None, None); }); assert_eq!( @@ -1316,7 +1339,8 @@ mod tests { .0 .first() .unwrap() - .clone(), + .clone() + .0, scenario ); @@ -1346,6 +1370,7 @@ mod tests { .0 .first() .unwrap() + .0 .adapter, "Delve", ); @@ -1367,15 +1392,14 @@ mod tests { .unwrap(); }); - assert_eq!( + assert!( inventory .update(cx, |this, cx| { this.list_debug_scenarios(&TaskContexts::default(), vec![], vec![], false, cx) }) .await .0 - .first(), - None + .is_empty(), ); } diff --git a/crates/workspace/src/tasks.rs b/crates/workspace/src/tasks.rs index 4134e7ed743b927e5965ad73d7500f9f4346bd18..26edbd8d03ed37d4bddca65f0a94cc9413760dd9 100644 --- a/crates/workspace/src/tasks.rs +++ b/crates/workspace/src/tasks.rs @@ -3,7 +3,7 @@ use std::process::ExitStatus; use anyhow::Result; use gpui::{AppContext, Context, Entity, Task}; use language::Buffer; -use project::TaskSourceKind; +use project::{TaskSourceKind, WorktreeId}; use remote::ConnectionState; use task::{DebugScenario, ResolvedTask, SpawnInTerminal, TaskContext, TaskTemplate}; use ui::Window; @@ -95,11 +95,19 @@ impl Workspace { scenario: DebugScenario, task_context: TaskContext, active_buffer: Option>, + worktree_id: Option, window: &mut Window, cx: &mut Context, ) { if let Some(provider) = self.debugger_provider.as_mut() { - provider.start_session(scenario, task_context, active_buffer, window, cx) + provider.start_session( + scenario, + task_context, + active_buffer, + worktree_id, + window, + cx, + ) } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 7c5355bfd1be10609ff8bcab9e7cd9cd3e7f7bc6..3100abcca7dc9576253df3b909c69f4fdc27e395 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -146,6 +146,7 @@ pub trait DebuggerProvider { definition: DebugScenario, task_context: TaskContext, active_buffer: Option>, + worktree_id: Option, window: &mut Window, cx: &mut App, ); From b7bfdd33832613d8d89c499289c51ec232be301c Mon Sep 17 00:00:00 2001 From: Cole Miller Date: Tue, 1 Jul 2025 16:02:12 -0400 Subject: [PATCH 057/134] Move language-specific debugging docs to the page for each language (#33692) Release Notes: - N/A --- crates/extension_api/wit/since_v0.6.0/dap.wit | 2 +- crates/task/src/debug_format.rs | 2 +- docs/src/configuring-zed.md | 8 +- docs/src/debugger.md | 364 ++---------------- docs/src/extensions/debugger-extensions.md | 2 +- docs/src/languages/c.md | 23 ++ docs/src/languages/cpp.md | 22 ++ docs/src/languages/go.md | 107 +++++ docs/src/languages/javascript.md | 49 +++ docs/src/languages/python.md | 65 ++++ docs/src/languages/ruby.md | 25 +- docs/src/languages/rust.md | 45 +++ docs/src/languages/typescript.md | 38 ++ 13 files changed, 409 insertions(+), 343 deletions(-) diff --git a/crates/extension_api/wit/since_v0.6.0/dap.wit b/crates/extension_api/wit/since_v0.6.0/dap.wit index a3f07435d15f583ee7b19d922abad395cb29015c..693befe02f9c313455facd4839572528c3408fd1 100644 --- a/crates/extension_api/wit/since_v0.6.0/dap.wit +++ b/crates/extension_api/wit/since_v0.6.0/dap.wit @@ -33,7 +33,7 @@ interface dap { } /// Debug Config is the "highest-level" configuration for a debug session. - /// It comes from a new session modal UI; thus, it is essentially debug-adapter-agnostic. + /// It comes from a new process modal UI; thus, it is essentially debug-adapter-agnostic. /// It is expected of the extension to translate this generic configuration into something that can be debugged by the adapter (debug scenario). record debug-config { /// Name of the debug task diff --git a/crates/task/src/debug_format.rs b/crates/task/src/debug_format.rs index e336fa1fd7d0d61de323fdfc66de64919ff9186c..f95fcf56b649122b6c0243db8699e2400dc98f82 100644 --- a/crates/task/src/debug_format.rs +++ b/crates/task/src/debug_format.rs @@ -243,7 +243,7 @@ pub enum Request { Attach, } -/// This struct represent a user created debug task from the new session modal +/// This struct represent a user created debug task from the new process modal #[derive(Deserialize, Serialize, PartialEq, Eq, Clone, Debug, JsonSchema)] #[serde(rename_all = "snake_case")] pub struct ZedDebugConfig { diff --git a/docs/src/configuring-zed.md b/docs/src/configuring-zed.md index 4587a70ac15bf294728b235e75e73a1a1572fbd1..6318851913c68cfe6228dca9cf321354c0eff195 100644 --- a/docs/src/configuring-zed.md +++ b/docs/src/configuring-zed.md @@ -1944,17 +1944,17 @@ Example: 1. Maps to `Alt` on Linux and Windows and to `Option` on MacOS: -```jsonc +```json { - "multi_cursor_modifier": "alt", + "multi_cursor_modifier": "alt" } ``` 2. Maps `Control` on Linux and Windows and to `Command` on MacOS: -```jsonc +```json { - "multi_cursor_modifier": "cmd_or_ctrl", // alias: "cmd", "ctrl" + "multi_cursor_modifier": "cmd_or_ctrl" // alias: "cmd", "ctrl" } ``` diff --git a/docs/src/debugger.md b/docs/src/debugger.md index 47d9ffaa042cc7681757867ce88804af79a5f621..f10461a1603baeb76b30f60a36f945be0b4895df 100644 --- a/docs/src/debugger.md +++ b/docs/src/debugger.md @@ -3,6 +3,7 @@ Zed uses the [Debug Adapter Protocol (DAP)](https://microsoft.github.io/debug-adapter-protocol/) to provide debugging functionality across multiple programming languages. DAP is a standardized protocol that defines how debuggers, editors, and IDEs communicate with each other. It allows Zed to support various debuggers without needing to implement language-specific debugging logic. +Zed implements the client side of the protocol, and various _debug adapters_ implement the server side. This protocol enables features like setting breakpoints, stepping through code, inspecting variables, and more, in a consistent manner across different programming languages and runtime environments. @@ -10,36 +11,53 @@ and more, in a consistent manner across different programming languages and runt > We currently offer onboarding support for users. We are eager to hear from you if you encounter any issues or have suggestions for improvement for our debugging experience. > You can schedule a call via [Cal.com](https://cal.com/team/zed-research/debugger) -## Supported Debug Adapters +## Supported Languages -Zed supports a variety of debug adapters for different programming languages out of the box: +To debug code written in a specific language, Zed needs to find a debug adapter for that language. Some debug adapters are provided by Zed without additional setup, and some are provided by [language extensions](./extensions/debugger-extensions.md). The following languages currently have debug adapters available: -- JavaScript ([vscode-js-debug](https://github.com/microsoft/vscode-js-debug.git)): Enables debugging of Node.js applications, including setting breakpoints, stepping through code, and inspecting variables in JavaScript. + -- Python ([debugpy](https://github.com/microsoft/debugpy.git)): Provides debugging capabilities for Python applications, supporting features like remote debugging, multi-threaded debugging, and Django/Flask application debugging. +- [C](./languages/c.md#debugging) (built-in) +- [C++](./languages/cpp.md#debugging) (built-in) +- [Go](./languages/go.md#debugging) (built-in) +- [JavaScript](./languages/javascript.md#debugging) (built-in) +- [PHP](./languages/php.md#debugging) (built-in) +- [Python](./languages/python.md#debugging) (built-in) +- [Ruby](./languages/ruby.md#debugging) (provided by extension) +- [Rust](./languages/rust.md#debugging) (built-in) +- [Swift](./languages/swift.md#debugging) (provided by extension) +- [TypeScript](./languages/typescript.md#debugging) (built-in) -- LLDB ([CodeLLDB](https://github.com/vadimcn/codelldb.git)): A powerful debugger for Rust, C, C++, and some other compiled languages, offering low-level debugging features and support for Apple platforms. +> If your language isn't listed, you can contribute by adding a debug adapter for it. Check out our [debugger extensions](./extensions/debugger-extensions.md) documentation for more information. -- GDB ([GDB](https://sourceware.org/gdb/)): The GNU Debugger, which supports debugging for multiple programming languages including C, C++, Go, and Rust, across various platforms. +Follow those links for language- and adapter-specific information and examples, or read on for more about Zed's general debugging features that apply to all adapters. -- Go ([Delve](https://github.com/go-delve/delve)): Delve, a debugger for the Go programming language, offering both local and remote debugging capabilities with full support for Go's runtime and standard library. - -- PHP ([Xdebug](https://xdebug.org/)): Provides debugging and profiling capabilities for PHP applications, including remote debugging and code coverage analysis. +## Getting Started -- Ruby ([rdbg](https://github.com/ruby/debug)): Provides debugging for Ruby. +For most languages, the fastest way to get started is to run {#action debugger::Start} ({#kb debugger::Start}). This opens the _new process modal_, which shows you a contextual list of preconfigured debug tasks for the current project. Debug tasks are created from tests, entry points (like a `main` function), and from other sources — consult the documentation for your language for full information about what's supported. -These adapters enable Zed to provide a consistent debugging experience across multiple languages while leveraging the specific features and capabilities of each debugger. +You can open the same modal by clicking the "plus" button at the top right of the debug panel. -> Is your desired debugger not listed? You can install a [Debug Adapter extension](https://zed.dev/extensions?filter=debug-adapters) to add support for your favorite debugger. -> If that's not enough, you can contribute by creating an extension yourself. Check out our [debugger extensions](extensions/debugger-extensions.md) documentation for more information. +For languages that don't provide preconfigured debug tasks (this includes C, C++, and some extension-supported languages), you can define debug configurations in the `.zed/debug.json` file in your project root. This file should be an array of configuration objects: -## Getting Started - -For basic debugging, you can set up a new configuration by opening the `New Session Modal` either via the `debugger: start` (default: f4) or by clicking the plus icon at the top right of the debug panel. +```json +[ + { + "adapter": "CodeLLDB", + "label": "First configuration" + // ... + }, + { + "adapter": "Debugpy", + "label": "Second configuration" + // ... + } +] +``` -For more advanced use cases, you can create debug configurations by directly editing the `.zed/debug.json` file in your project root directory. +Check the documentation for your language for example configurations covering typical use-cases. Once you've added configurations to `.zed/debug.json`, they'll appear in the list in the new process modal. -You can then use the `New Session Modal` to select a configuration and start debugging. +Zed will also load debug configurations from `.vscode/launch.json`, and show them in the new process modal if no configurations are found in `.zed/debug.json`. ### Launching & Attaching @@ -58,7 +76,7 @@ While configuration fields are debug adapter-dependent, most adapters support th ```json [ { - // The label for the debug configuration and used to identify the debug session inside the debug panel & new session modal + // The label for the debug configuration and used to identify the debug session inside the debug panel & new process modal "label": "Example Start debugger config", // The debug adapter that Zed should use to debug the program "adapter": "Example adapter name", @@ -113,309 +131,7 @@ Build tasks can also refer to the existing tasks by unsubstituted label: ### Automatic scenario creation Given a Zed task, Zed can automatically create a scenario for you. Automatic scenario creation also powers our scenario creation from gutter. -Automatic scenario creation is currently supported for Rust, Go, and Python. JavaScript/TypeScript support is being worked on. - -### Example Configurations - -#### JavaScript - -##### Debug Active File - -```json -[ - { - "label": "Debug with node", - "adapter": "JavaScript", - "program": "$ZED_FILE", - "request": "launch", - "console": "integratedTerminal", - "type": "pwa-node" - } -] -``` - -##### Attach debugger to a server running in web browser (`npx serve`) - -Given an externally-ran web server (e.g., with `npx serve` or `npx live-server`) one can attach to it and open it with a browser. - -```json -[ - { - "label": "Inspect ", - "adapter": "JavaScript", - "type": "pwa-chrome", - "request": "launch", - "url": "http://localhost:5500", // Fill your URL here. - "program": "$ZED_FILE", - "webRoot": "${ZED_WORKTREE_ROOT}" - } -] -``` - -#### Python - -##### Debug Active File - -```json -[ - { - "label": "Python Active File", - "adapter": "Debugpy", - "program": "$ZED_FILE", - "request": "launch" - } -] -``` - -##### Flask App - -For a common Flask Application with a file structure similar to the following: - -``` -.venv/ -app/ - init.py - main.py - routes.py -templates/ - index.html -static/ - style.css -requirements.txt -``` - -…the following configuration can be used: - -```json -[ - { - "label": "Python: Flask", - "adapter": "Debugpy", - "request": "launch", - "module": "app", - "cwd": "$ZED_WORKTREE_ROOT", - "env": { - "FLASK_APP": "app", - "FLASK_DEBUG": "1" - }, - "args": [ - "run", - "--reload", // Enables Flask reloader that watches for file changes - "--debugger" // Enables Flask debugger - ], - "autoReload": { - "enable": true - }, - "jinja": true, - "justMyCode": true - } -] -``` - -#### Rust/C++/C - -> For CodeLLDB, you might want to set `sourceLanguages` in your launch configuration based on the source code language. - -##### Using pre-built binary - -```json -[ - { - "label": "Debug native binary", - "program": "$ZED_WORKTREE_ROOT/build/binary", - "request": "launch", - "adapter": "CodeLLDB" // GDB is available on non-ARM Macs as well as Linux - } -] -``` - -##### Build binary then debug - -```json -[ - { - "label": "Build & Debug native binary", - "build": { - "command": "cargo", - "args": ["build"] - }, - "program": "$ZED_WORKTREE_ROOT/target/debug/binary", - "request": "launch", - "adapter": "CodeLLDB" // GDB is available on non-ARM Macs as well as Linux - } -] -``` - -##### Automatically locate a debug target based on build command - -```json -[ - { - "label": "Build & Debug native binary", - "adapter": "CodeLLDB" // GDB is available on non-ARM Macs as well as Linux - // Zed can infer the path to a debuggee based on the build command - "build": { - "command": "cargo", - "args": ["build"] - }, - } -] -``` - -#### TypeScript - -##### Attach debugger to a server running in web browser (`npx serve`) - -Given an externally-ran web server (e.g., with `npx serve` or `npx live-server`) one can attach to it and open it with a browser. - -```json -[ - { - "label": "Launch Chrome (TypeScript)", - "adapter": "JavaScript", - "type": "pwa-chrome", - "request": "launch", - "url": "http://localhost:5500", - "program": "$ZED_FILE", - "webRoot": "${ZED_WORKTREE_ROOT}", - "sourceMaps": true, - "build": { - "command": "npx", - "args": ["tsc"] - } - } -] -``` - -#### Go - -Zed uses [delve](https://github.com/go-delve/delve?tab=readme-ov-file) to debug Go applications. -Zed will automatically create debug scenarios for `func main` in your main packages, and also -for any tests, so you can use the Play button in the gutter to debug these without configuration. - -##### Debug Go Packages - -To debug a specific package, you can do so by setting the Delve mode to "debug". In this case "program" should be set to the package name. - -```json -[ - { - "label": "Go (Delve)", - "adapter": "Delve", - "program": "$ZED_FILE", - "request": "launch", - "mode": "debug" - } -] -``` - -```json -[ - { - "label": "Run server", - "adapter": "Delve", - "request": "launch", - "mode": "debug", - // For Delve, the program can be a package name - "program": "./cmd/server" - // "args": [], - // "buildFlags": [], - } -] -``` - -##### Debug Go Tests - -To debug the tests for a package, set the Delve mode to "test". -The "program" is still the package name, and you can use the "buildFlags" to do things like set tags, and the "args" to set args on the test binary. (See `go help testflags` for more information on doing that). - -```json -[ - { - "label": "Run integration tests", - "adapter": "Delve", - "request": "launch", - "mode": "test", - "program": ".", - "buildFlags": ["-tags", "integration"] - // To filter down to just the test your cursor is in: - // "args": ["-test.run", "$ZED_SYMBOL"] - } -] -``` - -##### Build and debug separately - -If you need to build your application with a specific command, you can use the "exec" mode of Delve. In this case "program" should point to an executable, -and the "build" command should build that. - -```json -{ - "label": "Debug Prebuilt Unit Tests", - "adapter": "Delve", - "request": "launch", - "mode": "exec", - "program": "${ZED_WORKTREE_ROOT}/__debug_unit", - "args": ["-test.v", "-test.run=${ZED_SYMBOL}"], - "build": { - "command": "go", - "args": [ - "test", - "-c", - "-tags", - "unit", - "-gcflags\"all=-N -l\"", - "-o", - "__debug_unit", - "./pkg/..." - ] - } -} -``` - -##### Attaching to an existing instance of Delve - -You might find yourself needing to connect to an existing instance of Delve that's not necessarily running on your machine; in such case, you can use `tcp_arguments` to instrument Zed's connection to Delve. - -``` -{ - "adapter": "Delve", - "label": "Connect to a running Delve instance", - "program": "/Users/zed/Projects/language_repositories/golang/hello/hello", - "cwd": "/Users/zed/Projects/language_repositories/golang/hello", - "args": [], - "env": {}, - "request": "launch", - "mode": "exec", - "stopOnEntry": false, - "tcp_connection": { "host": "123.456.789.012", "port": 53412 } -} -``` - -In such case Zed won't spawn a new instance of Delve, as it opts to use an existing one. The consequence of this is that _there will be no terminal_ in Zed; you have to interact with the Delve instance directly, as it handles stdin/stdout of the debuggee. - -#### Ruby - -To run a ruby task in the debugger, you will need to configure it in the `.zed/debug.json` file in your project. We don't yet have automatic detection of ruby tasks, nor do we support connecting to an existing process. - -The configuration should look like this: - -```json -[ - { - "adapter": "Ruby", - "label": "Run CLI", - "script": "cli.rb" - // If you want to customize how the script is run (for example using bundle exec) - // use "command" instead. - // "command": "bundle exec cli.rb" - // - // "args": [] - // "env": {} - // "cwd": "" - } -] -``` +Automatic scenario creation is currently supported for Rust, Go, Python, JavaScript, and TypeScript. ## Breakpoints @@ -623,5 +339,5 @@ If you're running into problems with the debugger, please [open a GitHub issue]( There are also some features you can use to gather more information about the problem: -- When you have a session running in the debug panel, you can run the `dev: copy debug adapter arguments` action to copy a JSON blob to the clipboard that describes how Zed initialized the session. This is especially useful when the session failed to start, and is great context to add if you open a GitHub issue. -- You can also use the `dev: open debug adapter logs` action to see a trace of all of Zed's communications with debug adapters during the most recent debug sessions. +- When you have a session running in the debug panel, you can run the {#action dev::CopyDebugAdapterArguments} action to copy a JSON blob to the clipboard that describes how Zed initialized the session. This is especially useful when the session failed to start, and is great context to add if you open a GitHub issue. +- You can also use the {#action dev::OpenDebugAdapterLogs} action to see a trace of all of Zed's communications with debug adapters during the most recent debug sessions. diff --git a/docs/src/extensions/debugger-extensions.md b/docs/src/extensions/debugger-extensions.md index ad8e2c5e2970c21179d42ea32f9bab4414de4e44..4412bf8b9aa576e736b5b6dc25c5f4bc48100b18 100644 --- a/docs/src/extensions/debugger-extensions.md +++ b/docs/src/extensions/debugger-extensions.md @@ -56,7 +56,7 @@ impl zed::Extension for MyExtension { } ``` -`dap_config_to_scenario` is used when the user spawns a session via new session modal UI. At a high level, it takes a generic debug configuration (that isn't specific to any +`dap_config_to_scenario` is used when the user spawns a session via new process modal UI. At a high level, it takes a generic debug configuration (that isn't specific to any debug adapter) and tries to turn it into a concrete debug scenario for your adapter. Put another way, it is supposed to answer the question: "Given a program, a list of arguments, current working directory and environment variables, what would the configuration for spawning this debug adapter look like?". diff --git a/docs/src/languages/c.md b/docs/src/languages/c.md index ff6b1806601b21608a7e4ec3ed96a0a262df6d9e..14a11c0d665e9e5b3a9499284d36b133945ad866 100644 --- a/docs/src/languages/c.md +++ b/docs/src/languages/c.md @@ -4,6 +4,7 @@ C support is available natively in Zed. - Tree-sitter: [tree-sitter/tree-sitter-c](https://github.com/tree-sitter/tree-sitter-c) - Language Server: [clangd/clangd](https://github.com/clangd/clangd) +- Debug Adapter: [CodeLLDB](https://github.com/vadimcn) (primary), [GDB](https://sourceware.org/gdb/) (secondary, not available on Apple silicon) ## Clangd: Force detect as C @@ -61,3 +62,25 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) ``` After building your project, CMake will generate the `compile_commands.json` file in the build directory and clangd will automatically pick it up. + +## Debugging + +You can use CodeLLDB or GDB to debug native binaries. (Make sure that your build process passes `-g` to the C compiler, so that debug information is included in the resulting binary.) See below for examples of debug configurations that you can add to `.zed/debug.json`. + +### Build and Debug Binary + +```json +[ + { + "label": "Debug native binary", + "build": { + "command": "make", + "args": ["-j8"], + "cwd": "$ZED_WORKTREE_ROOT" + } + "program": "$ZED_WORKTREE_ROOT/build/prog", + "request": "launch", + "adapter": "CodeLLDB" + } +] +``` diff --git a/docs/src/languages/cpp.md b/docs/src/languages/cpp.md index ccce575af7a0dbdeace8176a7e490dc750a19f36..1273bce2ac0b6a92cbda8e63cd0f477965500a11 100644 --- a/docs/src/languages/cpp.md +++ b/docs/src/languages/cpp.md @@ -112,3 +112,25 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) ``` After building your project, CMake will generate the `compile_commands.json` file in the build directory and clangd will automatically pick it up. + +## Debugging + +You can use CodeLLDB or GDB to debug native binaries. (Make sure that your build process passes `-g` to the C++ compiler, so that debug information is included in the resulting binary.) See below for examples of debug configurations that you can add to `.zed/debug.json`. + +### Build and Debug Binary + +```json +[ + { + "label": "Debug native binary", + "build": { + "command": "make", + "args": ["-j8"], + "cwd": "$ZED_WORKTREE_ROOT" + } + "program": "$ZED_WORKTREE_ROOT/build/prog", + "request": "launch", + "adapter": "CodeLLDB" + } +] +``` diff --git a/docs/src/languages/go.md b/docs/src/languages/go.md index 6bea4ba04cffc6ff6a4b9a401cfb478edac95d4f..0a12616b1c7dda9eb416717aa16bfeb5f50748d4 100644 --- a/docs/src/languages/go.md +++ b/docs/src/languages/go.md @@ -4,6 +4,7 @@ Go support is available natively in Zed. - Tree-sitter: [tree-sitter/tree-sitter-go](https://github.com/tree-sitter/tree-sitter-go) - Language Server: [golang/tools/tree/master/gopls](https://github.com/golang/tools/tree/master/gopls) +- Debug Adapter: [delve](https://github.com/go-delve/delve) ## Setup @@ -72,6 +73,112 @@ to override these settings. See [gopls inlayHints documentation](https://github.com/golang/tools/blob/master/gopls/doc/inlayHints.md) for more information. +## Debugging + +Zed supports zero-configuration debugging of Go tests and entry points (`func main`). Run {#action debugger::Start} ({#kb debugger::Start}) to see a contextual list of these preconfigured debug tasks. + +For more control, you can add debug configurations to `.zed/debug.json`. See below for examples. + +### Debug Go Packages + +To debug a specific package, you can do so by setting the Delve mode to "debug". In this case "program" should be set to the package name. + +```json +[ + { + "label": "Go (Delve)", + "adapter": "Delve", + "program": "$ZED_FILE", + "request": "launch", + "mode": "debug" + }, + { + "label": "Run server", + "adapter": "Delve", + "request": "launch", + "mode": "debug", + // For Delve, the program can be a package name + "program": "./cmd/server" + // "args": [], + // "buildFlags": [], + } +] +``` + +### Debug Go Tests + +To debug the tests for a package, set the Delve mode to "test". +The "program" is still the package name, and you can use the "buildFlags" to do things like set tags, and the "args" to set args on the test binary. (See `go help testflags` for more information on doing that). + +```json +[ + { + "label": "Run integration tests", + "adapter": "Delve", + "request": "launch", + "mode": "test", + "program": ".", + "buildFlags": ["-tags", "integration"] + // To filter down to just the test your cursor is in: + // "args": ["-test.run", "$ZED_SYMBOL"] + } +] +``` + +### Build and debug separately + +If you need to build your application with a specific command, you can use the "exec" mode of Delve. In this case "program" should point to an executable, +and the "build" command should build that. + +```json +[ + { + "label": "Debug Prebuilt Unit Tests", + "adapter": "Delve", + "request": "launch", + "mode": "exec", + "program": "${ZED_WORKTREE_ROOT}/__debug_unit", + "args": ["-test.v", "-test.run=${ZED_SYMBOL}"], + "build": { + "command": "go", + "args": [ + "test", + "-c", + "-tags", + "unit", + "-gcflags\"all=-N -l\"", + "-o", + "__debug_unit", + "./pkg/..." + ] + } + } +] +``` + +### Attaching to an existing instance of Delve + +You might find yourself needing to connect to an existing instance of Delve that's not necessarily running on your machine; in such case, you can use `tcp_arguments` to instrument Zed's connection to Delve. + +```json +[ + { + "adapter": "Delve", + "label": "Connect to a running Delve instance", + "program": "/Users/zed/Projects/language_repositories/golang/hello/hello", + "cwd": "/Users/zed/Projects/language_repositories/golang/hello", + "args": [], + "env": {}, + "request": "launch", + "mode": "exec", + "stopOnEntry": false, + "tcp_connection": { "host": "123.456.789.012", "port": 53412 } + } +] +``` + +In such case Zed won't spawn a new instance of Delve, as it opts to use an existing one. The consequence of this is that _there will be no terminal_ in Zed; you have to interact with the Delve instance directly, as it handles stdin/stdout of the debuggee. + ## Go Mod - Tree-sitter: [camdencheek/tree-sitter-go-mod](https://github.com/camdencheek/tree-sitter-go-mod) diff --git a/docs/src/languages/javascript.md b/docs/src/languages/javascript.md index b42fa31922a1f44a5fed0a7b69a3c9c59543a7fe..c71071a9b37c74c2226796083af3ae557751da8e 100644 --- a/docs/src/languages/javascript.md +++ b/docs/src/languages/javascript.md @@ -4,6 +4,7 @@ JavaScript support is available natively in Zed. - Tree-sitter: [tree-sitter/tree-sitter-javascript](https://github.com/tree-sitter/tree-sitter-javascript) - Language Server: [typescript-language-server/typescript-language-server](https://github.com/typescript-language-server/typescript-language-server) +- Debug Adapter: [vscode-js-debug](https://github.com/microsoft/vscode-js-debug) ## Code formatting @@ -174,6 +175,54 @@ You can configure ESLint's `workingDirectory` setting: } ``` +## Debugging + +Zed supports debugging JavaScript code out of the box. +The following can be debugged without writing additional configuration: + +- Tasks from `package.json` +- Tests written using several popular frameworks (Jest, Mocha, Vitest, Jasmine) + +Run {#action debugger::Start} ({#kb debugger::Start}) to see a contextual list of these predefined debug tasks. + +As for all languages, configurations from `.vscode/launch.json` are also available for debugging in Zed. + +If your use-case isn't covered by any of these, you can take full control by adding debug configurations to `.zed/debug.json`. See below for example configurations. + +### Debug the current file + +```json +[ + { + "adapter": "JavaScript", + "label": "Debug JS file", + "type": "node", + "request": "launch", + "program": "$ZED_FILE", + "skipFiles": ["/**"] + } +] +``` + +This implicitly runs the current file using `node`. + +### Launch a web app in Chrome + +```json +[ + { + "adapter": "JavaScript", + "label": "Debug app in Chrome", + "type": "chrome", + "request": "launch", + "file": "$ZED_WORKTREE_ROOT/index.html", + "webRoot": "$ZED_WORKTREE_ROOT", + "console": "integratedTerminal", + "skipFiles": ["/**"] + } +] +``` + ## See also - [Yarn documentation](./yarn.md) for a walkthrough of configuring your project to use Yarn. diff --git a/docs/src/languages/python.md b/docs/src/languages/python.md index 05f1491ca73b2adccb04aeca412a2bef9702e22a..5d90065de055b3dd5568f0093ac4ba49f4bb0536 100644 --- a/docs/src/languages/python.md +++ b/docs/src/languages/python.md @@ -6,6 +6,7 @@ Python support is available natively in Zed. - Language Servers: - [microsoft/pyright](https://github.com/microsoft/pyright) - [python-lsp/python-lsp-server](https://github.com/python-lsp/python-lsp-server) (PyLSP) +- Debug Adapter: [debugpy](https://github.com/microsoft/debugpy) ## Language Servers @@ -125,3 +126,67 @@ A common tool for formatting Python code is [Ruff](https://docs.astral.sh/ruff/) TBD: Expand Python Ruff docs. TBD: Ruff pyproject.toml, ruff.toml docs. `ruff.configuration`. --> + +## Debugging + +Zed supports zero-configuration debugging of Python module entry points and pytest tests. +Run {#action debugger::Start} ({#kb debugger::Start}) to see a contextual list for the current project. +For greater control, you can add debug configurations to `.zed/debug.json`. See the examples below. + +### Debug Active File + +```json +[ + { + "label": "Python Active File", + "adapter": "Debugpy", + "program": "$ZED_FILE", + "request": "launch" + } +] +``` + +### Flask App + +For a common Flask Application with a file structure similar to the following: + +``` +.venv/ +app/ + init.py + main.py + routes.py +templates/ + index.html +static/ + style.css +requirements.txt +``` + +…the following configuration can be used: + +```json +[ + { + "label": "Python: Flask", + "adapter": "Debugpy", + "request": "launch", + "module": "app", + "cwd": "$ZED_WORKTREE_ROOT", + "env": { + "FLASK_APP": "app", + "FLASK_DEBUG": "1" + }, + "args": [ + "run", + "--reload", // Enables Flask reloader that watches for file changes + "--debugger" // Enables Flask debugger + ], + "autoReload": { + "enable": true + }, + "jinja": true, + "justMyCode": true + } +] +``` diff --git a/docs/src/languages/ruby.md b/docs/src/languages/ruby.md index 8b3094a3b714b12e310b7b30ff7b5d196e1893d8..67904e35f18f704197ecf7d6d607649b7133d009 100644 --- a/docs/src/languages/ruby.md +++ b/docs/src/languages/ruby.md @@ -9,6 +9,7 @@ Ruby support is available through the [Ruby extension](https://github.com/zed-ex - [ruby-lsp](https://github.com/Shopify/ruby-lsp) - [solargraph](https://github.com/castwide/solargraph) - [rubocop](https://github.com/rubocop/rubocop) +- Debug Adapter: [`rdbg`](https://github.com/ruby/debug) The Ruby extension also provides support for ERB files. @@ -43,15 +44,15 @@ For all supported Ruby language servers (`solargraph`, `ruby-lsp`, `rubocop`, `s You can skip step 1 and force using the system executable by setting `use_bundler` to `false` in your settings: -```jsonc +```json { "lsp": { "": { "settings": { - "use_bundler": false, - }, - }, - }, + "use_bundler": false + } + } + } } ``` @@ -349,21 +350,21 @@ The Ruby extension provides a debug adapter for debugging Ruby code. Zed's name #### Debug a Ruby script -```jsonc +```json [ { "label": "Debug current file", "adapter": "rdbg", "request": "launch", "script": "$ZED_FILE", - "cwd": "$ZED_WORKTREE_ROOT", - }, + "cwd": "$ZED_WORKTREE_ROOT" + } ] ``` #### Debug Rails server -```jsonc +```json [ { "label": "Debug Rails server", @@ -373,8 +374,8 @@ The Ruby extension provides a debug adapter for debugging Ruby code. Zed's name "args": ["server"], "cwd": "$ZED_WORKTREE_ROOT", "env": { - "RUBY_DEBUG_OPEN": "true", - }, - }, + "RUBY_DEBUG_OPEN": "true" + } + } ] ``` diff --git a/docs/src/languages/rust.md b/docs/src/languages/rust.md index ea6f6912cd863e82e9866595554e5ce609fa1133..1ee25a37b5b4f7a1b59ec2aa07330b101619e0ae 100644 --- a/docs/src/languages/rust.md +++ b/docs/src/languages/rust.md @@ -4,6 +4,7 @@ Rust support is available natively in Zed. - Tree-sitter: [tree-sitter/tree-sitter-rust](https://github.com/tree-sitter/tree-sitter-rust) - Language Server: [rust-lang/rust-analyzer](https://github.com/rust-lang/rust-analyzer) +- Debug Adapter: [CodeLLDB](https://github.com/vadimcn/codelldb) (primary), [GDB](https://sourceware.org/gdb/) (secondary, not available on Apple silicon)