From a6a2465954822be296f1b45f7732eed059dddc1f Mon Sep 17 00:00:00 2001 From: Agus Zubiaga Date: Thu, 18 Sep 2025 15:28:41 -0300 Subject: [PATCH 1/6] edit prediction: Fix sub overflow in identifiers_in_range (#38438) Release Notes: - N/A Co-authored-by: Bennet --- crates/edit_prediction_context/src/excerpt.rs | 8 +-- .../edit_prediction_context/src/reference.rs | 71 +++++++++++++++++-- 2 files changed, 71 insertions(+), 8 deletions(-) diff --git a/crates/edit_prediction_context/src/excerpt.rs b/crates/edit_prediction_context/src/excerpt.rs index 3fde142093efd095129f51a83f836953a76d20cb..d27e75bd129147986d2359585d5e048704d8828f 100644 --- a/crates/edit_prediction_context/src/excerpt.rs +++ b/crates/edit_prediction_context/src/excerpt.rs @@ -114,13 +114,13 @@ impl EditPredictionExcerpt { options, }; - if let Some(excerpt_ranges) = excerpt_selector.select_tree_sitter_nodes() { - if excerpt_ranges.size >= options.min_bytes { - return Some(excerpt_ranges); + if let Some(excerpt) = excerpt_selector.select_tree_sitter_nodes() { + if excerpt.size >= options.min_bytes { + return Some(excerpt); } log::debug!( "tree-sitter excerpt was {} bytes, smaller than min of {}, falling back on line-based selection", - excerpt_ranges.size, + excerpt.size, options.min_bytes ); } else { diff --git a/crates/edit_prediction_context/src/reference.rs b/crates/edit_prediction_context/src/reference.rs index 975f15c81f44dad9ee1d3105f6a5863a4685b164..abb7dd75dd569f7a14bea807bdc64441d0e64871 100644 --- a/crates/edit_prediction_context/src/reference.rs +++ b/crates/edit_prediction_context/src/reference.rs @@ -1,6 +1,7 @@ use language::BufferSnapshot; use std::collections::HashMap; use std::ops::Range; +use util::RangeExt; use crate::{ declaration::Identifier, @@ -78,8 +79,8 @@ pub fn identifiers_in_range( .highlights_config .as_ref(); - for capture in mat.captures { - if let Some(config) = config { + if let Some(config) = config { + for capture in mat.captures { if config.identifier_capture_indices.contains(&capture.index) { let node_range = capture.node.byte_range(); @@ -88,9 +89,13 @@ pub fn identifiers_in_range( continue; } + if !range.contains_inclusive(&node_range) { + continue; + } + let identifier_text = - // TODO we changed this to saturating_sub for now, but we should fix the actually issue - &range_text[node_range.start.saturating_sub(range.start)..node_range.end.saturating_sub(range.start)]; + &range_text[node_range.start - range.start..node_range.end - range.start]; + references.push(Reference { identifier: Identifier { name: identifier_text.into(), @@ -108,3 +113,61 @@ pub fn identifiers_in_range( } references } + +#[cfg(test)] +mod test { + use gpui::{TestAppContext, prelude::*}; + use indoc::indoc; + use language::{BufferSnapshot, Language, LanguageConfig, LanguageMatcher, tree_sitter_rust}; + + use crate::reference::{ReferenceRegion, identifiers_in_range}; + + #[gpui::test] + fn test_identifier_node_truncated(cx: &mut TestAppContext) { + let code = indoc! { r#" + fn main() { + add(1, 2); + } + + fn add(a: i32, b: i32) -> i32 { + a + b + } + "# }; + let buffer = create_buffer(code, cx); + + let range = 0..35; + let references = identifiers_in_range( + range.clone(), + &code[range], + ReferenceRegion::Breadcrumb, + &buffer, + ); + assert_eq!(references.len(), 2); + assert_eq!(references[0].identifier.name.as_ref(), "main"); + assert_eq!(references[1].identifier.name.as_ref(), "add"); + } + + fn create_buffer(text: &str, cx: &mut TestAppContext) -> BufferSnapshot { + let buffer = + cx.new(|cx| language::Buffer::local(text, cx).with_language(rust_lang().into(), cx)); + buffer.read_with(cx, |buffer, _| buffer.snapshot()) + } + + fn rust_lang() -> Language { + Language::new( + LanguageConfig { + name: "Rust".into(), + matcher: LanguageMatcher { + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + ..Default::default() + }, + Some(tree_sitter_rust::LANGUAGE.into()), + ) + .with_highlights_query(include_str!("../../languages/src/rust/highlights.scm")) + .unwrap() + .with_outline_query(include_str!("../../languages/src/rust/outline.scm")) + .unwrap() + } +} From c58763a5267228fd71e70dd9fed3b716f6d3c009 Mon Sep 17 00:00:00 2001 From: Bartosz Kaszubowski Date: Thu, 18 Sep 2025 20:34:13 +0200 Subject: [PATCH 2/6] git_ui: Reduce spacing between action icon and label (#38445) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Why Opinionated change: A bit uneven spacing between Git action icon and label, in comparison to the border on the right in the segmented action button was triggering my UI OCD a bit. 😅 # How Remove the right margin from icon and icon + counter children of the segmented Git action button in Git Panel. The default spacing from the button layout seems to be enough to separate them from the left-side label. # Release Notes - Reduced spacing between Git action icon and label in Git Panel # Test plan I have tested few cases, and made sure that the spacing is still present, but icon (or icon and counter) does not feel too separated/detached from the label. ### Before Screenshot 2025-09-18 at 20 11 16 Screenshot 2025-09-18 at 20 13 19 ### After Screenshot 2025-09-18 at 19 53 14 Screenshot 2025-09-18 at 19 53 34 Screenshot 2025-09-18 at 19 56 23 --- crates/git_ui/src/git_ui.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/git_ui/src/git_ui.rs b/crates/git_ui/src/git_ui.rs index cede717d53b257be2570c4b0c067fb46341c0fc5..da2e2ca032aa005ad619eabf094ae6981975b050 100644 --- a/crates/git_ui/src/git_ui.rs +++ b/crates/git_ui/src/git_ui.rs @@ -645,7 +645,6 @@ mod remote_button { this.child( h_flex() .ml_neg_0p5() - .mr_1() .when(behind_count > 0, |this| { this.child(Icon::new(IconName::ArrowDown).size(IconSize::XSmall)) .child(count(behind_count)) @@ -660,7 +659,6 @@ mod remote_button { this.child( h_flex() .ml_neg_0p5() - .mr_1() .child(Icon::new(left_icon).size(IconSize::XSmall)), ) }) From 5fccde9b1bf539dacbf892279b84f8b008438868 Mon Sep 17 00:00:00 2001 From: Cole Miller Date: Thu, 18 Sep 2025 14:45:02 -0400 Subject: [PATCH 3/6] python: Install basedpyright if the basedpyright-langserver binary is missing (#38426) Potential fix for #38377 Release Notes: - N/A --------- Co-authored-by: Peter Tripp --- crates/language/src/language.rs | 5 +--- crates/languages/src/python.rs | 48 ++++++++++++++++++++++----------- 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 291b5d3957cc7274bdd07b5260890c23b7a253d1..2af5657ea776ddd85bf9495d3c1f32c2d0c69ac2 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -55,7 +55,7 @@ use std::{ str, sync::{ Arc, LazyLock, - atomic::{AtomicU64, AtomicUsize, Ordering::SeqCst}, + atomic::{AtomicUsize, Ordering::SeqCst}, }, }; use syntax_map::{QueryCursorHandle, SyntaxSnapshot}; @@ -168,7 +168,6 @@ pub struct CachedLspAdapter { pub disk_based_diagnostics_progress_token: Option, language_ids: HashMap, pub adapter: Arc, - pub reinstall_attempt_count: AtomicU64, cached_binary: ServerBinaryCache, } @@ -185,7 +184,6 @@ impl Debug for CachedLspAdapter { &self.disk_based_diagnostics_progress_token, ) .field("language_ids", &self.language_ids) - .field("reinstall_attempt_count", &self.reinstall_attempt_count) .finish_non_exhaustive() } } @@ -204,7 +202,6 @@ impl CachedLspAdapter { language_ids, adapter, cached_binary: Default::default(), - reinstall_attempt_count: AtomicU64::new(0), }) } diff --git a/crates/languages/src/python.rs b/crates/languages/src/python.rs index 4905cd22cd64488a0f0da9ede6834602fc5834bc..4451054cfd21be5d07c9df64b55a37ffc190670f 100644 --- a/crates/languages/src/python.rs +++ b/crates/languages/src/python.rs @@ -1559,7 +1559,7 @@ impl LspInstaller for PyLspAdapter { util::command::new_smol_command(pip_path.as_path()) .arg("install") .arg("python-lsp-server") - .arg("-U") + .arg("--upgrade") .output() .await? .status @@ -1570,7 +1570,7 @@ impl LspInstaller for PyLspAdapter { util::command::new_smol_command(pip_path.as_path()) .arg("install") .arg("python-lsp-server[all]") - .arg("-U") + .arg("--upgrade") .output() .await? .status @@ -1581,7 +1581,7 @@ impl LspInstaller for PyLspAdapter { util::command::new_smol_command(pip_path) .arg("install") .arg("pylsp-mypy") - .arg("-U") + .arg("--upgrade") .output() .await? .status @@ -1589,6 +1589,10 @@ impl LspInstaller for PyLspAdapter { "pylsp-mypy installation failed" ); let pylsp = venv.join(BINARY_DIR).join("pylsp"); + ensure!( + delegate.which(pylsp.as_os_str()).await.is_some(), + "pylsp installation was incomplete" + ); Ok(LanguageServerBinary { path: pylsp, env: None, @@ -1603,6 +1607,7 @@ impl LspInstaller for PyLspAdapter { ) -> Option { let venv = self.base_venv(delegate).await.ok()?; let pylsp = venv.join(BINARY_DIR).join("pylsp"); + delegate.which(pylsp.as_os_str()).await?; Some(LanguageServerBinary { path: pylsp, env: None, @@ -1641,9 +1646,11 @@ impl BasedPyrightLspAdapter { .arg("venv") .arg("basedpyright-venv") .current_dir(work_dir) - .spawn()? + .spawn() + .context("spawning child")? .output() - .await?; + .await + .context("getting child output")?; } Ok(path.into()) @@ -1885,11 +1892,14 @@ impl LspInstaller for BasedPyrightLspAdapter { let path = Path::new(toolchain?.path.as_ref()) .parent()? .join(Self::BINARY_NAME); - path.exists().then(|| LanguageServerBinary { - path, - arguments: vec!["--stdio".into()], - env: None, - }) + delegate + .which(path.as_os_str()) + .await + .map(|_| LanguageServerBinary { + path, + arguments: vec!["--stdio".into()], + env: None, + }) } } @@ -1905,16 +1915,21 @@ impl LspInstaller for BasedPyrightLspAdapter { util::command::new_smol_command(pip_path.as_path()) .arg("install") .arg("basedpyright") - .arg("-U") + .arg("--upgrade") .output() - .await? + .await + .context("getting pip install output")? .status .success(), "basedpyright installation failed" ); - let pylsp = venv.join(BINARY_DIR).join(Self::BINARY_NAME); + let path = venv.join(BINARY_DIR).join(Self::BINARY_NAME); + ensure!( + delegate.which(path.as_os_str()).await.is_some(), + "basedpyright installation was incomplete" + ); Ok(LanguageServerBinary { - path: pylsp, + path, env: None, arguments: vec!["--stdio".into()], }) @@ -1926,9 +1941,10 @@ impl LspInstaller for BasedPyrightLspAdapter { delegate: &dyn LspAdapterDelegate, ) -> Option { let venv = self.base_venv(delegate).await.ok()?; - let pylsp = venv.join(BINARY_DIR).join(Self::BINARY_NAME); + let path = venv.join(BINARY_DIR).join(Self::BINARY_NAME); + delegate.which(path.as_os_str()).await?; Some(LanguageServerBinary { - path: pylsp, + path, env: None, arguments: vec!["--stdio".into()], }) From 6b8ed5bf28e1cacd0f72319adbc589065849ac93 Mon Sep 17 00:00:00 2001 From: Jaeyong Sung Date: Fri, 19 Sep 2025 03:46:26 +0900 Subject: [PATCH 4/6] docs: Fix typo in Python configuration example (#38434) Release Notes: - N/A --------- Co-authored-by: Marshall Bowers --- docs/src/languages/python.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/src/languages/python.md b/docs/src/languages/python.md index f4960bc8e8d3643bab6310020ab7d7834a6a85b3..faca1185768d09b5dcb6e485c146b0e1973cf870 100644 --- a/docs/src/languages/python.md +++ b/docs/src/languages/python.md @@ -81,11 +81,13 @@ These are disabled by default, but can be enabled in your settings. For example: { "languages": { "Python": { - "language_servers": { + "language_servers": [ // Disable basedpyright and enable Ty, and otherwise // use the default configuration. - "ty", "!basedpyright", ".." - } + "ty", + "!basedpyright", + "..." + ] } } } From fc0eb882f74e19b578d8943087abbfe413e3a2b6 Mon Sep 17 00:00:00 2001 From: Matt Date: Thu, 18 Sep 2025 15:05:55 -0500 Subject: [PATCH 5/6] debugger_ui: Update new process modal to include more context about its source (#36650) Closes #36280 Release Notes: - Added additional context to debug task selection Adding additional context when selecting a debug task to help with projects that have multiple config files with similar names for tasks. I think there is room for improvement, especially adding context for a LanguageTask type. I started but it looked like it would need to add a path value to that and wanted to make sure this was a good idea before working on that. Also any thoughts on the wording if you do like this format? --- image --------- Co-authored-by: Anthony Co-authored-by: Anthony --- crates/debugger_ui/src/debugger_panel.rs | 4 + crates/debugger_ui/src/new_process_modal.rs | 188 +++++++++++++----- .../src/tests/new_process_modal.rs | 78 +++++++- crates/project/src/task_inventory.rs | 1 + crates/tasks_ui/src/modal.rs | 2 +- 5 files changed, 217 insertions(+), 56 deletions(-) diff --git a/crates/debugger_ui/src/debugger_panel.rs b/crates/debugger_ui/src/debugger_panel.rs index 12a31a7360bb6e7fb1ff6fdcaf18f73d679693a3..f1a1b4dc738f82f729832c60648392af8b9921ed 100644 --- a/crates/debugger_ui/src/debugger_panel.rs +++ b/crates/debugger_ui/src/debugger_panel.rs @@ -134,6 +134,10 @@ impl DebugPanel { .map(|session| session.read(cx).running_state().clone()) } + pub fn project(&self) -> &Entity { + &self.project + } + pub fn load( workspace: WeakEntity, cx: &mut AsyncWindowContext, diff --git a/crates/debugger_ui/src/new_process_modal.rs b/crates/debugger_ui/src/new_process_modal.rs index eeed36ac1df18d5a0d1b33d6c3567c7df6a4b9c0..f1fa4738e30e5ed24e7815b61571b03e5a16252e 100644 --- a/crates/debugger_ui/src/new_process_modal.rs +++ b/crates/debugger_ui/src/new_process_modal.rs @@ -1,6 +1,6 @@ use anyhow::{Context as _, bail}; use collections::{FxHashMap, HashMap, HashSet}; -use language::LanguageRegistry; +use language::{LanguageName, LanguageRegistry}; use std::{ borrow::Cow, path::{Path, PathBuf}, @@ -22,7 +22,7 @@ use itertools::Itertools as _; use picker::{Picker, PickerDelegate, highlighted_match_with_paths::HighlightedMatch}; use project::{DebugScenarioContext, Project, TaskContexts, TaskSourceKind, task_store::TaskStore}; use settings::Settings; -use task::{DebugScenario, RevealTarget, ZedDebugConfig}; +use task::{DebugScenario, RevealTarget, VariableName, ZedDebugConfig}; use theme::ThemeSettings; use ui::{ ActiveTheme, Button, ButtonCommon, ButtonSize, CheckboxWithLabel, Clickable, Color, Context, @@ -978,6 +978,7 @@ pub(super) struct DebugDelegate { task_store: Entity, candidates: Vec<( Option, + Option, DebugScenario, Option, )>, @@ -1005,28 +1006,89 @@ impl DebugDelegate { } } - fn get_scenario_kind( + fn get_task_subtitle( + &self, + task_kind: &Option, + context: &Option, + cx: &mut App, + ) -> Option { + match task_kind { + Some(TaskSourceKind::Worktree { + id: worktree_id, + directory_in_worktree, + .. + }) => self + .debug_panel + .update(cx, |debug_panel, cx| { + let project = debug_panel.project().read(cx); + let worktrees: Vec<_> = project.visible_worktrees(cx).collect(); + + let mut path = if worktrees.len() > 1 + && let Some(worktree) = project.worktree_for_id(*worktree_id, cx) + { + let worktree_path = worktree.read(cx).abs_path(); + let full_path = worktree_path.join(directory_in_worktree); + full_path + } else { + directory_in_worktree.clone() + }; + + match path + .components() + .next_back() + .and_then(|component| component.as_os_str().to_str()) + { + Some(".zed") => { + path.push("debug.json"); + } + Some(".vscode") => { + path.push("launch.json"); + } + _ => {} + } + Some(path.display().to_string()) + }) + .unwrap_or_else(|_| Some(directory_in_worktree.display().to_string())), + Some(TaskSourceKind::AbsPath { abs_path, .. }) => { + Some(abs_path.to_string_lossy().into_owned()) + } + Some(TaskSourceKind::Lsp { language_name, .. }) => { + Some(format!("LSP: {language_name}")) + } + Some(TaskSourceKind::Language { .. }) => None, + _ => context.clone().and_then(|ctx| { + ctx.task_context + .task_variables + .get(&VariableName::RelativeFile) + .map(|f| format!("in {f}")) + .or_else(|| { + ctx.task_context + .task_variables + .get(&VariableName::Dirname) + .map(|d| format!("in {d}/")) + }) + }), + } + } + + fn get_scenario_language( languages: &Arc, dap_registry: &DapRegistry, scenario: DebugScenario, - ) -> (Option, DebugScenario) { + ) -> (Option, DebugScenario) { let language_names = languages.language_names(); - let language = dap_registry - .adapter_language(&scenario.adapter) - .map(|language| TaskSourceKind::Language { name: language.0 }); + let language_name = dap_registry.adapter_language(&scenario.adapter); - let language = language.or_else(|| { + let language_name = language_name.or_else(|| { scenario.label.split_whitespace().find_map(|word| { language_names .iter() .find(|name| name.as_ref().eq_ignore_ascii_case(word)) - .map(|name| TaskSourceKind::Language { - name: name.to_owned().into(), - }) + .cloned() }) }); - (language, scenario) + (language_name, scenario) } pub fn tasks_loaded( @@ -1080,9 +1142,9 @@ impl DebugDelegate { this.delegate.candidates = recent .into_iter() .map(|(scenario, context)| { - let (kind, scenario) = - Self::get_scenario_kind(&languages, dap_registry, scenario); - (kind, scenario, Some(context)) + let (language_name, scenario) = + Self::get_scenario_language(&languages, dap_registry, scenario); + (None, language_name, scenario, Some(context)) }) .chain( scenarios @@ -1097,9 +1159,9 @@ impl DebugDelegate { }) .filter(|(_, scenario)| valid_adapters.contains(&scenario.adapter)) .map(|(kind, scenario)| { - let (language, scenario) = - Self::get_scenario_kind(&languages, dap_registry, scenario); - (language.or(Some(kind)), scenario, None) + let (language_name, scenario) = + Self::get_scenario_language(&languages, dap_registry, scenario); + (Some(kind), language_name, scenario, None) }), ) .collect(); @@ -1145,7 +1207,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(); @@ -1314,7 +1376,7 @@ impl PickerDelegate for DebugDelegate { .get(self.selected_index()) .and_then(|match_candidate| self.candidates.get(match_candidate.candidate_id).cloned()); - let Some((kind, debug_scenario, context)) = debug_scenario else { + let Some((kind, _, debug_scenario, context)) = debug_scenario else { return; }; @@ -1447,6 +1509,7 @@ impl PickerDelegate for DebugDelegate { cx: &mut Context>, ) -> Option { let hit = &self.matches.get(ix)?; + let (task_kind, language_name, _scenario, context) = &self.candidates[hit.candidate_id]; let highlighted_location = HighlightedMatch { text: hit.string.clone(), @@ -1454,33 +1517,40 @@ impl PickerDelegate for DebugDelegate { char_count: hit.string.chars().count(), color: Color::Default, }; - let task_kind = &self.candidates[hit.candidate_id].0; - - let icon = match task_kind { - Some(TaskSourceKind::UserInput) => Some(Icon::new(IconName::Terminal)), - Some(TaskSourceKind::AbsPath { .. }) => Some(Icon::new(IconName::Settings)), - Some(TaskSourceKind::Worktree { .. }) => Some(Icon::new(IconName::FileTree)), - Some(TaskSourceKind::Lsp { - language_name: name, - .. - }) - | Some(TaskSourceKind::Language { name }) => file_icons::FileIcons::get(cx) - .get_icon_for_type(&name.to_lowercase(), cx) - .map(Icon::from_path), - None => Some(Icon::new(IconName::HistoryRerun)), - } - .map(|icon| icon.color(Color::Muted).size(IconSize::Small)); - let indicator = if matches!(task_kind, Some(TaskSourceKind::Lsp { .. })) { - Some(Indicator::icon( - Icon::new(IconName::BoltFilled) - .color(Color::Muted) - .size(IconSize::Small), - )) - } else { - None + + let subtitle = self.get_task_subtitle(task_kind, context, cx); + + let language_icon = language_name.as_ref().and_then(|lang| { + file_icons::FileIcons::get(cx) + .get_icon_for_type(&lang.0.to_lowercase(), cx) + .map(Icon::from_path) + }); + + let (icon, indicator) = match task_kind { + Some(TaskSourceKind::UserInput) => (Some(Icon::new(IconName::Terminal)), None), + Some(TaskSourceKind::AbsPath { .. }) => (Some(Icon::new(IconName::Settings)), None), + Some(TaskSourceKind::Worktree { .. }) => (Some(Icon::new(IconName::FileTree)), None), + Some(TaskSourceKind::Lsp { language_name, .. }) => ( + file_icons::FileIcons::get(cx) + .get_icon_for_type(&language_name.to_lowercase(), cx) + .map(Icon::from_path), + Some(Indicator::icon( + Icon::new(IconName::BoltFilled) + .color(Color::Muted) + .size(IconSize::Small), + )), + ), + Some(TaskSourceKind::Language { name }) => ( + file_icons::FileIcons::get(cx) + .get_icon_for_type(&name.to_lowercase(), cx) + .map(Icon::from_path), + None, + ), + None => (Some(Icon::new(IconName::HistoryRerun)), None), }; - let icon = icon.map(|icon| { - IconWithIndicator::new(icon, indicator) + + let icon = language_icon.or(icon).map(|icon| { + IconWithIndicator::new(icon.color(Color::Muted).size(IconSize::Small), indicator) .indicator_border_color(Some(cx.theme().colors().border_transparent)) }); @@ -1490,7 +1560,18 @@ impl PickerDelegate for DebugDelegate { .start_slot::(icon) .spacing(ListItemSpacing::Sparse) .toggle_state(selected) - .child(highlighted_location.render(window, cx)), + .child( + v_flex() + .items_start() + .child(highlighted_location.render(window, cx)) + .when_some(subtitle, |this, subtitle_text| { + this.child( + Label::new(subtitle_text) + .size(LabelSize::Small) + .color(Color::Muted), + ) + }), + ), ) } } @@ -1539,4 +1620,17 @@ impl NewProcessModal { } }) } + + pub(crate) fn debug_picker_candidate_subtitles(&self, cx: &mut App) -> Vec { + self.debug_picker.update(cx, |picker, cx| { + picker + .delegate + .candidates + .iter() + .filter_map(|(task_kind, _, _, context)| { + picker.delegate.get_task_subtitle(task_kind, context, cx) + }) + .collect() + }) + } } diff --git a/crates/debugger_ui/src/tests/new_process_modal.rs b/crates/debugger_ui/src/tests/new_process_modal.rs index bfc445cf67329b7190f8e5b8d353415fb53fcd74..80e27ee6bdeb1d1a2627ad7aa46bf68c38464510 100644 --- a/crates/debugger_ui/src/tests/new_process_modal.rs +++ b/crates/debugger_ui/src/tests/new_process_modal.rs @@ -10,6 +10,7 @@ use text::Point; use util::path; use crate::NewProcessMode; +use crate::new_process_modal::NewProcessModal; use crate::tests::{init_test, init_test_workspace}; #[gpui::test] @@ -178,13 +179,7 @@ async fn test_save_debug_scenario_to_file(executor: BackgroundExecutor, cx: &mut workspace .update(cx, |workspace, window, cx| { - crate::new_process_modal::NewProcessModal::show( - workspace, - window, - NewProcessMode::Debug, - None, - cx, - ); + NewProcessModal::show(workspace, window, NewProcessMode::Debug, None, cx); }) .unwrap(); @@ -192,7 +187,7 @@ async fn test_save_debug_scenario_to_file(executor: BackgroundExecutor, cx: &mut let modal = workspace .update(cx, |workspace, _, cx| { - workspace.active_modal::(cx) + workspace.active_modal::(cx) }) .unwrap() .expect("Modal should be active"); @@ -281,6 +276,73 @@ async fn test_save_debug_scenario_to_file(executor: BackgroundExecutor, cx: &mut pretty_assertions::assert_eq!(expected_content, debug_json_content); } +#[gpui::test] +async fn test_debug_modal_subtitles_with_multiple_worktrees( + executor: BackgroundExecutor, + cx: &mut TestAppContext, +) { + init_test(cx); + + let fs = FakeFs::new(executor.clone()); + + fs.insert_tree( + path!("/workspace1"), + json!({ + ".zed": { + "debug.json": r#"[ + { + "adapter": "fake-adapter", + "label": "Debug App 1", + "request": "launch", + "program": "./app1", + "cwd": "." + }, + { + "adapter": "fake-adapter", + "label": "Debug Tests 1", + "request": "launch", + "program": "./test1", + "cwd": "." + } + ]"# + }, + "main.rs": "fn main() {}" + }), + ) + .await; + + let project = Project::test(fs.clone(), [path!("/workspace1").as_ref()], cx).await; + + let workspace = init_test_workspace(&project, cx).await; + let cx = &mut VisualTestContext::from_window(*workspace, cx); + + workspace + .update(cx, |workspace, window, cx| { + NewProcessModal::show(workspace, window, NewProcessMode::Debug, None, cx); + }) + .unwrap(); + + cx.run_until_parked(); + + let modal = workspace + .update(cx, |workspace, _, cx| { + workspace.active_modal::(cx) + }) + .unwrap() + .expect("Modal should be active"); + + cx.executor().run_until_parked(); + + let subtitles = modal.update_in(cx, |modal, _, cx| { + modal.debug_picker_candidate_subtitles(cx) + }); + + assert_eq!( + subtitles.as_slice(), + [path!(".zed/debug.json"), path!(".zed/debug.json")] + ); +} + #[gpui::test] async fn test_dap_adapter_config_conversion_and_validation(cx: &mut TestAppContext) { init_test(cx); diff --git a/crates/project/src/task_inventory.rs b/crates/project/src/task_inventory.rs index 818a5c58cff7a57705a6498581e62f010ff6dcf9..4d85c8f2622c7e287c41f28089845b99d7b2ec4d 100644 --- a/crates/project/src/task_inventory.rs +++ b/crates/project/src/task_inventory.rs @@ -388,6 +388,7 @@ impl Inventory { .into_iter() .flat_map(|worktree| self.worktree_templates_from_settings(worktree)) .collect::>(); + let task_source_kind = language.as_ref().map(|language| TaskSourceKind::Language { name: language.name().into(), }); diff --git a/crates/tasks_ui/src/modal.rs b/crates/tasks_ui/src/modal.rs index e14b42af2b673ae161b5434e0c4f9b3f096e316a..3522e9522a6d32d729e7f0dca6731b2052f63f94 100644 --- a/crates/tasks_ui/src/modal.rs +++ b/crates/tasks_ui/src/modal.rs @@ -493,7 +493,7 @@ impl PickerDelegate for TasksModalDelegate { language_name: name, .. } - | TaskSourceKind::Language { name } => file_icons::FileIcons::get(cx) + | TaskSourceKind::Language { name, .. } => file_icons::FileIcons::get(cx) .get_icon_for_type(&name.to_lowercase(), cx) .map(Icon::from_path), } From e3e0522e32d5b3ddec85f18c3638fdb388f66145 Mon Sep 17 00:00:00 2001 From: Anthony Eid <56899983+Anthony-Eid@users.noreply.github.com> Date: Thu, 18 Sep 2025 16:15:25 -0400 Subject: [PATCH 6/6] debugger: Fix debug scenario picker showing history in reverse order (#38452) Closes #37859 Release Notes: - debugger: Fix sort order of pasted launched debug sessions in debugger launch modal --- crates/project/src/task_inventory.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/project/src/task_inventory.rs b/crates/project/src/task_inventory.rs index 4d85c8f2622c7e287c41f28089845b99d7b2ec4d..da6d87ac702b6e0e9b1ec459e810dc1ca37eeb7e 100644 --- a/crates/project/src/task_inventory.rs +++ b/crates/project/src/task_inventory.rs @@ -258,7 +258,7 @@ impl Inventory { ) { self.last_scheduled_scenarios .retain(|(s, _)| s.label != scenario.label); - self.last_scheduled_scenarios.push_back(( + self.last_scheduled_scenarios.push_front(( scenario, DebugScenarioContext { task_context,