Debug console tweaks (#29586)

Conrad Irwin , Anthony Eid , and Cole Miller created

Closes #ISSUE

Release Notes:

- N/A

---------

Co-authored-by: Anthony Eid <hello@anthonyeid.me>
Co-authored-by: Cole Miller <m@cole-miller.net>

Change summary

crates/debugger_ui/src/session/running/console.rs |  67 ++---
crates/project/src/debugger/dap_command.rs        |   4 
crates/project/src/debugger/dap_store.rs          |  16 
crates/project/src/debugger/session.rs            | 197 ++++++++--------
4 files changed, 135 insertions(+), 149 deletions(-)

Detailed changes

crates/debugger_ui/src/session/running/console.rs 🔗

@@ -45,7 +45,7 @@ impl Console {
             let mut editor = Editor::multi_line(window, cx);
             editor.move_to_end(&editor::actions::MoveToEnd, window, cx);
             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_breakpoints(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_indent_guides(false, 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
         });
         let focus_handle = cx.focus_handle();
@@ -146,6 +148,23 @@ impl Console {
             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| {
             session
                 .evaluate(
@@ -160,6 +179,10 @@ impl Console {
     }
 
     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 text_style = TextStyle {
             color: if self.console.read(cx).read_only(cx) {
@@ -174,44 +197,16 @@ impl Console {
             line_height: relative(settings.buffer_line_height.value()),
             ..Default::default()
         };
-
-        EditorElement::new(
-            &self.console,
-            EditorStyle {
-                background: cx.theme().colors().editor_background,
-                local_player: cx.theme().players().local(),
-                text: text_style,
-                ..Default::default()
-            },
-        )
+        EditorStyle {
+            background: cx.theme().colors().editor_background,
+            local_player: cx.theme().players().local(),
+            text: text_style,
+            ..Default::default()
+        }
     }
 
     fn render_query_bar(&self, cx: &Context<Self>) -> impl IntoElement {
-        let settings = ThemeSettings::get_global(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()
-            },
-        )
+        EditorElement::new(&self.query_bar, self.editor_style(cx))
     }
 }
 

crates/project/src/debugger/dap_command.rs 🔗

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

crates/project/src/debugger/dap_store.rs 🔗

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

crates/project/src/debugger/session.rs 🔗

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