Debug console tweaks (#29586)

Closes #ISSUE

Release Notes:

- N/A

---------

Co-authored-by: Anthony Eid <hello@anthonyeid.me>
Co-authored-by: Cole Miller <m@cole-miller.net>
This commit is contained in:
Conrad Irwin 2025-04-28 21:53:57 -06:00 committed by GitHub
parent 2beefc8158
commit ab180855de
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 135 additions and 149 deletions

View file

@ -45,7 +45,7 @@ impl Console {
let mut editor = Editor::multi_line(window, cx); let mut editor = Editor::multi_line(window, cx);
editor.move_to_end(&editor::actions::MoveToEnd, window, cx); editor.move_to_end(&editor::actions::MoveToEnd, window, cx);
editor.set_read_only(true); editor.set_read_only(true);
editor.set_show_gutter(true, cx); editor.set_show_gutter(false, cx);
editor.set_show_runnables(false, cx); editor.set_show_runnables(false, cx);
editor.set_show_breakpoints(false, cx); editor.set_show_breakpoints(false, cx);
editor.set_show_code_actions(false, cx); editor.set_show_code_actions(false, cx);
@ -57,6 +57,8 @@ impl Console {
editor.set_show_wrap_guides(false, cx); editor.set_show_wrap_guides(false, cx);
editor.set_show_indent_guides(false, cx); editor.set_show_indent_guides(false, cx);
editor.set_show_edit_predictions(Some(false), window, cx); editor.set_show_edit_predictions(Some(false), window, cx);
editor.set_use_modal_editing(false);
editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
editor editor
}); });
let focus_handle = cx.focus_handle(); let focus_handle = cx.focus_handle();
@ -146,6 +148,23 @@ impl Console {
expression expression
}); });
self.add_messages(
[OutputEvent {
category: None,
output: format!("> {expression}"),
group: None,
variables_reference: None,
source: None,
line: None,
column: None,
data: None,
location_reference: None,
}]
.iter(),
window,
cx,
);
self.session.update(cx, |session, cx| { self.session.update(cx, |session, cx| {
session session
.evaluate( .evaluate(
@ -160,6 +179,10 @@ impl Console {
} }
fn render_console(&self, cx: &Context<Self>) -> impl IntoElement { fn render_console(&self, cx: &Context<Self>) -> impl IntoElement {
EditorElement::new(&self.console, self.editor_style(cx))
}
fn editor_style(&self, cx: &Context<Self>) -> EditorStyle {
let settings = ThemeSettings::get_global(cx); let settings = ThemeSettings::get_global(cx);
let text_style = TextStyle { let text_style = TextStyle {
color: if self.console.read(cx).read_only(cx) { color: if self.console.read(cx).read_only(cx) {
@ -174,44 +197,16 @@ impl Console {
line_height: relative(settings.buffer_line_height.value()), line_height: relative(settings.buffer_line_height.value()),
..Default::default() ..Default::default()
}; };
EditorStyle {
EditorElement::new( background: cx.theme().colors().editor_background,
&self.console, local_player: cx.theme().players().local(),
EditorStyle { text: text_style,
background: cx.theme().colors().editor_background, ..Default::default()
local_player: cx.theme().players().local(), }
text: text_style,
..Default::default()
},
)
} }
fn render_query_bar(&self, cx: &Context<Self>) -> impl IntoElement { fn render_query_bar(&self, cx: &Context<Self>) -> impl IntoElement {
let settings = ThemeSettings::get_global(cx); EditorElement::new(&self.query_bar, self.editor_style(cx))
let text_style = TextStyle {
color: if self.console.read(cx).read_only(cx) {
cx.theme().colors().text_disabled
} else {
cx.theme().colors().text
},
font_family: settings.ui_font.family.clone(),
font_features: settings.ui_font.features.clone(),
font_fallbacks: settings.ui_font.fallbacks.clone(),
font_size: TextSize::Editor.rems(cx).into(),
font_weight: settings.ui_font.weight,
line_height: relative(1.3),
..Default::default()
};
EditorElement::new(
&self.query_bar,
EditorStyle {
background: cx.theme().colors().editor_background,
local_player: cx.theme().players().local(),
text: text_style,
..Default::default()
},
)
} }
} }

View file

@ -14,7 +14,7 @@ use rpc::proto;
use serde_json::Value; use serde_json::Value;
use util::ResultExt; use util::ResultExt;
pub(crate) trait LocalDapCommand: 'static + Send + Sync + std::fmt::Debug { pub trait LocalDapCommand: 'static + Send + Sync + std::fmt::Debug {
type Response: 'static + Send + std::fmt::Debug; type Response: 'static + Send + std::fmt::Debug;
type DapRequest: 'static + Send + dap::requests::Request; type DapRequest: 'static + Send + dap::requests::Request;
@ -30,7 +30,7 @@ pub(crate) trait LocalDapCommand: 'static + Send + Sync + std::fmt::Debug {
) -> Result<Self::Response>; ) -> Result<Self::Response>;
} }
pub(crate) trait DapCommand: LocalDapCommand { pub trait DapCommand: LocalDapCommand {
type ProtoRequest: 'static + Send; type ProtoRequest: 'static + Send;
type ProtoResponse: 'static + Send; type ProtoResponse: 'static + Send;
const CACHEABLE: bool = false; const CACHEABLE: bool = false;

View file

@ -1,5 +1,6 @@
use super::{ use super::{
breakpoint_store::BreakpointStore, breakpoint_store::BreakpointStore,
dap_command::EvaluateCommand,
locators, locators,
session::{self, Session, SessionStateEvent}, session::{self, Session, SessionStateEvent},
}; };
@ -897,19 +898,18 @@ impl DapStore {
.clone() .clone()
.unwrap_or_else(|| snapshot.text_for_range(range.clone()).collect()); .unwrap_or_else(|| snapshot.text_for_range(range.clone()).collect());
let Ok(eval_task) = session.update(cx, |session, cx| { let Ok(eval_task) = session.update(cx, |session, _| {
session.evaluate( session.mode.request_dap(EvaluateCommand {
expression, expression,
Some(EvaluateArgumentsContext::Variables), frame_id: Some(stack_frame_id),
Some(stack_frame_id), source: None,
None, context: Some(EvaluateArgumentsContext::Variables),
cx, })
)
}) else { }) else {
continue; continue;
}; };
if let Some(response) = eval_task.await { if let Some(response) = eval_task.await.log_err() {
inlay_hints.push(InlayHint { inlay_hints.push(InlayHint {
position: snapshot.anchor_after(range.end), position: snapshot.anchor_after(range.end),
label: InlayHintLabel::String(format!(": {}", response.result)), label: InlayHintLabel::String(format!(": {}", response.result)),

View file

@ -20,9 +20,7 @@ use dap::{
client::{DebugAdapterClient, SessionId}, client::{DebugAdapterClient, SessionId},
messages::{Events, Message}, messages::{Events, Message},
}; };
use dap::{ use dap::{ExceptionBreakpointsFilter, ExceptionFilterOptions, OutputEventCategory};
EvaluateResponse, ExceptionBreakpointsFilter, ExceptionFilterOptions, OutputEventCategory,
};
use futures::channel::oneshot; use futures::channel::oneshot;
use futures::{FutureExt, future::Shared}; use futures::{FutureExt, future::Shared};
use gpui::{ use gpui::{
@ -115,7 +113,7 @@ impl From<dap::Thread> for Thread {
} }
} }
enum Mode { pub enum Mode {
Building, Building,
Running(LocalMode), Running(LocalMode),
} }
@ -127,6 +125,7 @@ pub struct LocalMode {
pub(crate) breakpoint_store: Entity<BreakpointStore>, pub(crate) breakpoint_store: Entity<BreakpointStore>,
tmp_breakpoint: Option<SourceBreakpoint>, tmp_breakpoint: Option<SourceBreakpoint>,
worktree: WeakEntity<Worktree>, worktree: WeakEntity<Worktree>,
executor: BackgroundExecutor,
} }
fn client_source(abs_path: &Path) -> dap::Source { fn client_source(abs_path: &Path) -> dap::Source {
@ -179,6 +178,7 @@ impl LocalMode {
worktree, worktree,
tmp_breakpoint: None, tmp_breakpoint: None,
binary, binary,
executor: cx.background_executor().clone(),
}) })
} }
@ -190,14 +190,11 @@ impl LocalMode {
let tasks: Vec<_> = paths let tasks: Vec<_> = paths
.into_iter() .into_iter()
.map(|path| { .map(|path| {
self.request( self.request(dap_command::SetBreakpoints {
dap_command::SetBreakpoints { source: client_source(path),
source: client_source(path), source_modified: None,
source_modified: None, breakpoints: vec![],
breakpoints: vec![], })
},
cx.background_executor().clone(),
)
}) })
.collect(); .collect();
@ -229,14 +226,11 @@ impl LocalMode {
.map(Into::into) .map(Into::into)
.collect(); .collect();
let task = self.request( let task = self.request(dap_command::SetBreakpoints {
dap_command::SetBreakpoints { source: client_source(&abs_path),
source: client_source(&abs_path), source_modified: Some(matches!(reason, BreakpointUpdatedReason::FileSaved)),
source_modified: Some(matches!(reason, BreakpointUpdatedReason::FileSaved)), breakpoints,
breakpoints, });
},
cx.background_executor().clone(),
);
cx.background_spawn(async move { cx.background_spawn(async move {
match task.await { match task.await {
@ -250,7 +244,6 @@ impl LocalMode {
&self, &self,
filters: Vec<ExceptionBreakpointsFilter>, filters: Vec<ExceptionBreakpointsFilter>,
supports_filter_options: bool, supports_filter_options: bool,
cx: &App,
) -> Task<Result<Vec<dap::Breakpoint>>> { ) -> Task<Result<Vec<dap::Breakpoint>>> {
let arg = if supports_filter_options { let arg = if supports_filter_options {
SetExceptionBreakpoints::WithOptions { SetExceptionBreakpoints::WithOptions {
@ -268,7 +261,7 @@ impl LocalMode {
filters: filters.into_iter().map(|filter| filter.filter).collect(), filters: filters.into_iter().map(|filter| filter.filter).collect(),
} }
}; };
self.request(arg, cx.background_executor().clone()) self.request(arg)
} }
fn send_source_breakpoints( fn send_source_breakpoints(
@ -293,14 +286,11 @@ impl LocalMode {
}; };
breakpoint_tasks.push( breakpoint_tasks.push(
self.request( self.request(dap_command::SetBreakpoints {
dap_command::SetBreakpoints { source: client_source(&path),
source: client_source(&path), source_modified: Some(false),
source_modified: Some(false), breakpoints,
breakpoints, })
},
cx.background_executor().clone(),
)
.map(|result| result.map_err(|e| (path, e))), .map(|result| result.map_err(|e| (path, e))),
); );
} }
@ -331,18 +321,12 @@ impl LocalMode {
// Of relevance: https://github.com/microsoft/vscode/issues/4902#issuecomment-368583522 // Of relevance: https://github.com/microsoft/vscode/issues/4902#issuecomment-368583522
let launch = match raw.request { let launch = match raw.request {
dap::StartDebuggingRequestArgumentsRequest::Launch => self.request( dap::StartDebuggingRequestArgumentsRequest::Launch => self.request(Launch {
Launch { raw: raw.configuration,
raw: raw.configuration, }),
}, dap::StartDebuggingRequestArgumentsRequest::Attach => self.request(Attach {
cx.background_executor().clone(), raw: raw.configuration,
), }),
dap::StartDebuggingRequestArgumentsRequest::Attach => self.request(
Attach {
raw: raw.configuration,
},
cx.background_executor().clone(),
),
}; };
let configuration_done_supported = ConfigurationDone::is_supported(capabilities); let configuration_done_supported = ConfigurationDone::is_supported(capabilities);
@ -396,17 +380,11 @@ impl LocalMode {
} }
})?; })?;
cx.update(|cx| { this.send_exception_breakpoints(exception_filters, supports_exception_filters)
this.send_exception_breakpoints( .await
exception_filters, .ok();
supports_exception_filters,
cx,
)
})?
.await
.ok();
let ret = if configuration_done_supported { let ret = if configuration_done_supported {
this.request(ConfigurationDone {}, cx.background_executor().clone()) this.request(ConfigurationDone {})
} else { } else {
Task::ready(Ok(())) Task::ready(Ok(()))
} }
@ -421,11 +399,7 @@ impl LocalMode {
}) })
} }
fn request<R: LocalDapCommand>( fn request<R: LocalDapCommand>(&self, request: R) -> Task<Result<R::Response>>
&self,
request: R,
executor: BackgroundExecutor,
) -> Task<Result<R::Response>>
where where
<R::DapRequest as dap::requests::Request>::Response: 'static, <R::DapRequest as dap::requests::Request>::Response: 'static,
<R::DapRequest as dap::requests::Request>::Arguments: 'static + Send, <R::DapRequest as dap::requests::Request>::Arguments: 'static + Send,
@ -434,32 +408,22 @@ impl LocalMode {
let request_clone = request.clone(); let request_clone = request.clone();
let connection = self.client.clone(); let connection = self.client.clone();
let request_task = executor.spawn(async move { self.executor.spawn(async move {
let args = request_clone.to_dap(); let args = request_clone.to_dap();
connection.request::<R::DapRequest>(args).await let response = connection.request::<R::DapRequest>(args).await?;
}); request.response_from_dap(response)
executor.spawn(async move {
let response = request.response_from_dap(request_task.await?);
response
}) })
} }
} }
impl Mode { impl Mode {
fn request_dap<R: DapCommand>( pub(super) fn request_dap<R: DapCommand>(&self, request: R) -> Task<Result<R::Response>>
&self,
request: R,
cx: &mut Context<Session>,
) -> Task<Result<R::Response>>
where where
<R::DapRequest as dap::requests::Request>::Response: 'static, <R::DapRequest as dap::requests::Request>::Response: 'static,
<R::DapRequest as dap::requests::Request>::Arguments: 'static + Send, <R::DapRequest as dap::requests::Request>::Arguments: 'static + Send,
{ {
match self { match self {
Mode::Running(debug_adapter_client) => { Mode::Running(debug_adapter_client) => debug_adapter_client.request(request),
debug_adapter_client.request(request, cx.background_executor().clone())
}
Mode::Building => Task::ready(Err(anyhow!( Mode::Building => Task::ready(Err(anyhow!(
"no adapter running to send request: {:?}", "no adapter running to send request: {:?}",
request request
@ -539,7 +503,7 @@ type IsEnabled = bool;
pub struct OutputToken(pub usize); pub struct OutputToken(pub usize);
/// Represents a current state of a single debug adapter and provides ways to mutate it. /// Represents a current state of a single debug adapter and provides ways to mutate it.
pub struct Session { pub struct Session {
mode: Mode, pub mode: Mode,
definition: DebugTaskDefinition, definition: DebugTaskDefinition,
pub(super) capabilities: Capabilities, pub(super) capabilities: Capabilities,
id: SessionId, id: SessionId,
@ -871,7 +835,7 @@ impl Session {
let request = Initialize { adapter_id }; let request = Initialize { adapter_id };
match &self.mode { match &self.mode {
Mode::Running(local_mode) => { Mode::Running(local_mode) => {
let capabilities = local_mode.request(request, cx.background_executor().clone()); let capabilities = local_mode.request(request);
cx.spawn(async move |this, cx| { cx.spawn(async move |this, cx| {
let capabilities = capabilities.await?; let capabilities = capabilities.await?;
@ -1196,6 +1160,17 @@ impl Session {
} }
} }
pub async fn request2<T: DapCommand + PartialEq + Eq + Hash>(
&self,
request: T,
) -> Result<T::Response> {
if !T::is_supported(&self.capabilities) {
anyhow::bail!("DAP request {:?} is not supported", request);
}
self.mode.request_dap(request).await
}
fn request_inner<T: DapCommand + PartialEq + Eq + Hash>( fn request_inner<T: DapCommand + PartialEq + Eq + Hash>(
capabilities: &Capabilities, capabilities: &Capabilities,
mode: &Mode, mode: &Mode,
@ -1223,7 +1198,7 @@ impl Session {
}); });
} }
let request = mode.request_dap(request, cx); let request = mode.request_dap(request);
cx.spawn(async move |this, cx| { cx.spawn(async move |this, cx| {
let result = request.await; let result = request.await;
this.update(cx, |this, cx| process_result(this, result, cx)) this.update(cx, |this, cx| process_result(this, result, cx))
@ -1377,7 +1352,7 @@ impl Session {
.supports_exception_filter_options .supports_exception_filter_options
.unwrap_or_default(); .unwrap_or_default();
local local
.send_exception_breakpoints(exception_filters, supports_exception_filters, cx) .send_exception_breakpoints(exception_filters, supports_exception_filters)
.detach_and_log_err(cx); .detach_and_log_err(cx);
} else { } else {
debug_assert!(false, "Not implemented"); debug_assert!(false, "Not implemented");
@ -1878,35 +1853,51 @@ impl Session {
frame_id: Option<u64>, frame_id: Option<u64>,
source: Option<Source>, source: Option<Source>,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> Task<Option<EvaluateResponse>> { ) -> Task<()> {
self.request( let request = self.mode.request_dap(EvaluateCommand {
EvaluateCommand { expression,
expression, context,
context, frame_id,
frame_id, source,
source, });
}, cx.spawn(async move |this, cx| {
|this, response, cx| { let response = request.await;
let response = response.log_err()?; this.update(cx, |this, cx| {
this.output_token.0 += 1; match response {
this.output.push_back(dap::OutputEvent { Ok(response) => {
category: None, this.output_token.0 += 1;
output: response.result.clone(), this.output.push_back(dap::OutputEvent {
group: None, category: None,
variables_reference: Some(response.variables_reference), output: format!("< {}", &response.result),
source: None, group: None,
line: None, variables_reference: Some(response.variables_reference),
column: None, source: None,
data: None, line: None,
location_reference: None, column: None,
}); data: None,
location_reference: None,
});
}
Err(e) => {
this.output_token.0 += 1;
this.output.push_back(dap::OutputEvent {
category: None,
output: format!("{}", e),
group: None,
variables_reference: None,
source: None,
line: None,
column: None,
data: None,
location_reference: None,
});
}
};
this.invalidate_command_type::<ScopesCommand>(); this.invalidate_command_type::<ScopesCommand>();
cx.notify(); cx.notify();
Some(response) })
}, .ok();
cx, })
)
} }
pub fn location( pub fn location(