From f1fe505649e7a5c4c1b66d5eb520d9ddfa232855 Mon Sep 17 00:00:00 2001 From: Anthony Eid <56899983+Anthony-Eid@users.noreply.github.com> Date: Wed, 14 May 2025 00:50:58 +0200 Subject: [PATCH] debugger: Show language icons in debug scenario picker (#30662) We attempt to resolve the language name in this order 1. Based on debug adapter if they're for a singular language e.g. Delve 2. File extension if it exists 3. If a language name exists within a debug scenario's label In the future I want to use locators to also determine the language as well and refresh scenario list when a new scenario has been saved Release Notes: - N/A --- crates/dap/src/adapters.rs | 7 ++- crates/dap/src/registry.rs | 6 ++ crates/dap_adapters/src/go.rs | 7 ++- crates/dap_adapters/src/php.rs | 7 ++- crates/dap_adapters/src/python.rs | 7 ++- crates/dap_adapters/src/ruby.rs | 7 ++- crates/debugger_ui/src/new_session_modal.rs | 66 ++++++++++++++++++--- crates/language/src/language_registry.rs | 22 +++++++ 8 files changed, 115 insertions(+), 14 deletions(-) diff --git a/crates/dap/src/adapters.rs b/crates/dap/src/adapters.rs index 6506d096c6..009ddea125 100644 --- a/crates/dap/src/adapters.rs +++ b/crates/dap/src/adapters.rs @@ -8,7 +8,7 @@ pub use dap_types::{StartDebuggingRequestArguments, StartDebuggingRequestArgumen use futures::io::BufReader; use gpui::{AsyncApp, SharedString}; pub use http_client::{HttpClient, github::latest_github_release}; -use language::LanguageToolchainStore; +use language::{LanguageName, LanguageToolchainStore}; use node_runtime::NodeRuntime; use serde::{Deserialize, Serialize}; use settings::WorktreeId; @@ -418,6 +418,11 @@ pub trait DebugAdapter: 'static + Send + Sync { user_installed_path: Option, cx: &mut AsyncApp, ) -> Result; + + /// Returns the language name of an adapter if it only supports one language + fn adapter_language_name(&self) -> Option { + None + } } #[cfg(any(test, feature = "test-support"))] diff --git a/crates/dap/src/registry.rs b/crates/dap/src/registry.rs index 5e3c2949b9..dc7f269240 100644 --- a/crates/dap/src/registry.rs +++ b/crates/dap/src/registry.rs @@ -2,6 +2,7 @@ use anyhow::Result; use async_trait::async_trait; use collections::FxHashMap; use gpui::{App, Global, SharedString}; +use language::LanguageName; use parking_lot::RwLock; use task::{DebugRequest, DebugScenario, SpawnInTerminal, TaskTemplate}; @@ -59,6 +60,11 @@ impl DapRegistry { ); } + pub fn adapter_language(&self, adapter_name: &str) -> Option { + self.adapter(adapter_name) + .and_then(|adapter| adapter.adapter_language_name()) + } + pub fn add_locator(&self, locator: Arc) { let _previous_value = self.0.write().locators.insert(locator.name(), locator); debug_assert!( diff --git a/crates/dap_adapters/src/go.rs b/crates/dap_adapters/src/go.rs index f0416ba919..5cc132acd9 100644 --- a/crates/dap_adapters/src/go.rs +++ b/crates/dap_adapters/src/go.rs @@ -1,5 +1,6 @@ use dap::{StartDebuggingRequestArguments, adapters::DebugTaskDefinition}; -use gpui::AsyncApp; +use gpui::{AsyncApp, SharedString}; +use language::LanguageName; use std::{collections::HashMap, ffi::OsStr, path::PathBuf}; use crate::*; @@ -43,6 +44,10 @@ impl DebugAdapter for GoDebugAdapter { DebugAdapterName(Self::ADAPTER_NAME.into()) } + fn adapter_language_name(&self) -> Option { + Some(SharedString::new_static("Go").into()) + } + async fn get_binary( &self, delegate: &dyn DapDelegate, diff --git a/crates/dap_adapters/src/php.rs b/crates/dap_adapters/src/php.rs index 016e65f9a6..7eef069333 100644 --- a/crates/dap_adapters/src/php.rs +++ b/crates/dap_adapters/src/php.rs @@ -1,6 +1,7 @@ use adapters::latest_github_release; use dap::adapters::{DebugTaskDefinition, TcpArguments}; -use gpui::AsyncApp; +use gpui::{AsyncApp, SharedString}; +use language::LanguageName; use std::{collections::HashMap, path::PathBuf, sync::OnceLock}; use util::ResultExt; @@ -119,6 +120,10 @@ impl DebugAdapter for PhpDebugAdapter { DebugAdapterName(Self::ADAPTER_NAME.into()) } + fn adapter_language_name(&self) -> Option { + Some(SharedString::new_static("PHP").into()) + } + async fn get_binary( &self, delegate: &dyn DapDelegate, diff --git a/crates/dap_adapters/src/python.rs b/crates/dap_adapters/src/python.rs index c4c3dd40ec..1ea50527bd 100644 --- a/crates/dap_adapters/src/python.rs +++ b/crates/dap_adapters/src/python.rs @@ -1,6 +1,7 @@ use crate::*; use dap::{DebugRequest, StartDebuggingRequestArguments, adapters::DebugTaskDefinition}; -use gpui::AsyncApp; +use gpui::{AsyncApp, SharedString}; +use language::LanguageName; use std::{collections::HashMap, ffi::OsStr, path::PathBuf, sync::OnceLock}; use util::ResultExt; @@ -165,6 +166,10 @@ impl DebugAdapter for PythonDebugAdapter { DebugAdapterName(Self::ADAPTER_NAME.into()) } + fn adapter_language_name(&self) -> Option { + Some(SharedString::new_static("Python").into()) + } + async fn get_binary( &self, delegate: &dyn DapDelegate, diff --git a/crates/dap_adapters/src/ruby.rs b/crates/dap_adapters/src/ruby.rs index b7c0b45217..8483b0bdb8 100644 --- a/crates/dap_adapters/src/ruby.rs +++ b/crates/dap_adapters/src/ruby.rs @@ -6,7 +6,8 @@ use dap::{ self, DapDelegate, DebugAdapter, DebugAdapterBinary, DebugAdapterName, DebugTaskDefinition, }, }; -use gpui::AsyncApp; +use gpui::{AsyncApp, SharedString}; +use language::LanguageName; use std::path::PathBuf; use util::command::new_smol_command; @@ -25,6 +26,10 @@ impl DebugAdapter for RubyDebugAdapter { DebugAdapterName(Self::ADAPTER_NAME.into()) } + fn adapter_language_name(&self) -> Option { + Some(SharedString::new_static("Ruby").into()) + } + async fn get_binary( &self, delegate: &dyn DapDelegate, diff --git a/crates/debugger_ui/src/new_session_modal.rs b/crates/debugger_ui/src/new_session_modal.rs index 0238db4c76..974964d323 100644 --- a/crates/debugger_ui/src/new_session_modal.rs +++ b/crates/debugger_ui/src/new_session_modal.rs @@ -1,8 +1,10 @@ use collections::FxHashMap; +use language::LanguageRegistry; use std::{ borrow::Cow, ops::Not, path::{Path, PathBuf}, + sync::Arc, time::Duration, usize, }; @@ -81,6 +83,7 @@ impl NewSessionModal { return; }; let task_store = workspace.project().read(cx).task_store().clone(); + let languages = workspace.app_state().languages.clone(); cx.spawn_in(window, async move |workspace, cx| { workspace.update_in(cx, |workspace, window, cx| { @@ -131,9 +134,12 @@ impl NewSessionModal { } this.launch_picker.update(cx, |picker, cx| { - picker - .delegate - .task_contexts_loaded(task_contexts, window, cx); + picker.delegate.task_contexts_loaded( + task_contexts, + languages, + window, + cx, + ); picker.refresh(window, cx); cx.notify(); }); @@ -944,9 +950,49 @@ impl DebugScenarioDelegate { } } + fn get_scenario_kind( + languages: &Arc, + dap_registry: &DapRegistry, + scenario: DebugScenario, + ) -> (Option, DebugScenario) { + let language_names = languages.language_names(); + let language = dap_registry + .adapter_language(&scenario.adapter) + .map(|language| TaskSourceKind::Language { + name: language.into(), + }); + + let language = language.or_else(|| { + scenario + .request + .as_ref() + .and_then(|request| match request { + DebugRequest::Launch(launch) => launch + .program + .rsplit_once(".") + .and_then(|split| languages.language_name_for_extension(split.1)) + .map(|name| TaskSourceKind::Language { name: name.into() }), + _ => None, + }) + .or_else(|| { + scenario.label.split_whitespace().find_map(|word| { + language_names + .iter() + .find(|name| name.eq_ignore_ascii_case(word)) + .map(|name| TaskSourceKind::Language { + name: name.to_owned().into(), + }) + }) + }) + }); + + (language, scenario) + } + pub fn task_contexts_loaded( &mut self, task_contexts: TaskContexts, + languages: Arc, _window: &mut Window, cx: &mut Context>, ) { @@ -967,14 +1013,16 @@ impl DebugScenarioDelegate { self.last_used_candidate_index = Some(recent.len() - 1); } + let dap_registry = cx.global::(); + self.candidates = recent .into_iter() - .map(|scenario| (None, scenario)) - .chain( - scenarios - .into_iter() - .map(|(kind, scenario)| (Some(kind), scenario)), - ) + .map(|scenario| Self::get_scenario_kind(&languages, &dap_registry, scenario)) + .chain(scenarios.into_iter().map(|(kind, scenario)| { + let (language, scenario) = + Self::get_scenario_kind(&languages, &dap_registry, scenario); + (language.or(Some(kind)), scenario) + })) .collect(); } } diff --git a/crates/language/src/language_registry.rs b/crates/language/src/language_registry.rs index 6581782c90..23336ba020 100644 --- a/crates/language/src/language_registry.rs +++ b/crates/language/src/language_registry.rs @@ -68,6 +68,12 @@ impl From for SharedString { } } +impl From for LanguageName { + fn from(value: SharedString) -> Self { + LanguageName(value) + } +} + impl AsRef for LanguageName { fn as_ref(&self) -> &str { self.0.as_ref() @@ -627,6 +633,22 @@ impl LanguageRegistry { async move { rx.await? } } + pub fn language_name_for_extension(self: &Arc, extension: &str) -> Option { + self.state.try_read().and_then(|state| { + state + .available_languages + .iter() + .find(|language| { + language + .matcher() + .path_suffixes + .iter() + .any(|suffix| *suffix == extension) + }) + .map(|language| language.name.clone()) + }) + } + pub fn language_for_name_or_extension( self: &Arc, string: &str,