Unnamed repository; edit this file 'description' to name the repository.
dap: handle progress events (#15449)
Advertise and handle DAP progressStart, progressUpdate, and progressEnd events so adapters can report long-running work without falling back to opaque output. This follows the Debug Adapter Protocol initialize capability and progress event semantics: https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Initialize https://microsoft.github.io/debug-adapter-protocol/specification#Events_ProgressStart https://microsoft.github.io/debug-adapter-protocol/specification#Events_ProgressUpdate https://microsoft.github.io/debug-adapter-protocol/specification#Events_ProgressEnd
Matouš Dzivjak 4 weeks ago
parent e1032d1 · commit b7a18f1
-rw-r--r--helix-dap-types/src/lib.rs57
-rw-r--r--helix-dap/src/client.rs24
-rw-r--r--helix-dap/src/lib.rs61
-rw-r--r--helix-view/src/handlers/dap.rs40
4 files changed, 177 insertions, 5 deletions
diff --git a/helix-dap-types/src/lib.rs b/helix-dap-types/src/lib.rs
index 11eda181..cee06aa5 100644
--- a/helix-dap-types/src/lib.rs
+++ b/helix-dap-types/src/lib.rs
@@ -923,6 +923,63 @@ pub mod events {
}
#[derive(Debug)]
+ pub enum ProgressStart {}
+
+ impl Event for ProgressStart {
+ type Body = ProgressStartBody;
+ const EVENT: &'static str = "progressStart";
+ }
+
+ #[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
+ #[serde(rename_all = "camelCase")]
+ pub struct ProgressStartBody {
+ pub progress_id: String,
+ pub title: String,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub request_id: Option<u64>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub cancellable: Option<bool>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub message: Option<String>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub percentage: Option<u8>,
+ }
+
+ #[derive(Debug)]
+ pub enum ProgressUpdate {}
+
+ impl Event for ProgressUpdate {
+ type Body = ProgressUpdateBody;
+ const EVENT: &'static str = "progressUpdate";
+ }
+
+ #[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
+ #[serde(rename_all = "camelCase")]
+ pub struct ProgressUpdateBody {
+ pub progress_id: String,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub message: Option<String>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub percentage: Option<u8>,
+ }
+
+ #[derive(Debug)]
+ pub enum ProgressEnd {}
+
+ impl Event for ProgressEnd {
+ type Body = ProgressEndBody;
+ const EVENT: &'static str = "progressEnd";
+ }
+
+ #[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
+ #[serde(rename_all = "camelCase")]
+ pub struct ProgressEndBody {
+ pub progress_id: String,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub message: Option<String>,
+ }
+
+ #[derive(Debug)]
pub enum Breakpoint {}
impl Event for Breakpoint {
diff --git a/helix-dap/src/client.rs b/helix-dap/src/client.rs
index bef0556c..1ecd949f 100644
--- a/helix-dap/src/client.rs
+++ b/helix-dap/src/client.rs
@@ -2,7 +2,7 @@ use crate::{
registry::DebugAdapterId,
requests::{DisconnectArguments, TerminateArguments},
transport::{Payload, Request, Response, Transport},
- Error, Result,
+ Error, ProgressMap, ProgressState, Result,
};
use helix_core::syntax::config::{DebugAdapterConfig, DebuggerQuirks};
use helix_dap_types::*;
@@ -40,6 +40,7 @@ pub struct Client {
// thread_id -> frames
pub stack_frames: HashMap<ThreadId, Vec<StackFrame>>,
pub thread_states: ThreadStates,
+ pub progress: ProgressMap,
pub thread_id: Option<ThreadId>,
/// Currently active frame for the current thread.
pub active_frame: Option<usize>,
@@ -89,6 +90,7 @@ impl Client {
socket: None,
stack_frames: HashMap::new(),
thread_states: HashMap::new(),
+ progress: HashMap::new(),
thread_id: None,
active_frame: None,
quirks: DebuggerQuirks::default(),
@@ -348,6 +350,24 @@ impl Client {
self.caps.as_ref().expect("debugger not yet initialized!")
}
+ pub fn progress_start(&mut self, event: events::ProgressStartBody) -> String {
+ let status = ProgressState::new(event.title, event.message, event.percentage);
+ let status_line = status.status_line();
+ self.progress.insert(event.progress_id, status);
+ status_line
+ }
+
+ pub fn progress_update(&mut self, event: events::ProgressUpdateBody) -> Option<String> {
+ let status = self.progress.get_mut(&event.progress_id)?;
+ status.update(event.message, event.percentage);
+ Some(status.status_line())
+ }
+
+ pub fn progress_end(&mut self, event: events::ProgressEndBody) -> Option<String> {
+ let status = self.progress.remove(&event.progress_id)?;
+ Some(status.end_status_line(event.message.as_deref()))
+ }
+
pub async fn initialize(&mut self, adapter_id: String) -> Result<()> {
let args = requests::InitializeArguments {
client_id: Some("hx".to_owned()),
@@ -361,7 +381,7 @@ impl Client {
supports_variable_paging: Some(false),
supports_run_in_terminal_request: Some(true),
supports_memory_references: Some(false),
- supports_progress_reporting: Some(false),
+ supports_progress_reporting: Some(true),
supports_invalidated_event: Some(false),
};
diff --git a/helix-dap/src/lib.rs b/helix-dap/src/lib.rs
index 907ff965..745abc9f 100644
--- a/helix-dap/src/lib.rs
+++ b/helix-dap/src/lib.rs
@@ -7,6 +7,7 @@ pub use helix_dap_types::*;
pub use transport::{Payload, Response, Transport};
use serde::de::DeserializeOwned;
+use std::collections::HashMap;
use thiserror::Error;
#[derive(Error, Debug)]
@@ -75,9 +76,9 @@ pub enum Event {
LoadedSource(<events::LoadedSource as events::Event>::Body),
Process(<events::Process as events::Event>::Body),
Capabilities(<events::Capabilities as events::Event>::Body),
- // ProgressStart(),
- // ProgressUpdate(),
- // ProgressEnd(),
+ ProgressStart(<events::ProgressStart as events::Event>::Body),
+ ProgressUpdate(<events::ProgressUpdate as events::Event>::Body),
+ ProgressEnd(<events::ProgressEnd as events::Event>::Body),
// Invalidated(),
Memory(<events::Memory as events::Event>::Body),
}
@@ -100,6 +101,9 @@ impl Event {
events::LoadedSource::EVENT => Self::LoadedSource(parse_value(body)?),
events::Process::EVENT => Self::Process(parse_value(body)?),
events::Capabilities::EVENT => Self::Capabilities(parse_value(body)?),
+ events::ProgressStart::EVENT => Self::ProgressStart(parse_value(body)?),
+ events::ProgressUpdate::EVENT => Self::ProgressUpdate(parse_value(body)?),
+ events::ProgressEnd::EVENT => Self::ProgressEnd(parse_value(body)?),
events::Memory::EVENT => Self::Memory(parse_value(body)?),
_ => return Err(Error::Unhandled),
};
@@ -114,3 +118,54 @@ where
{
serde_json::from_value(value).map_err(|err| err.into())
}
+
+#[derive(Debug, Clone)]
+pub struct ProgressState {
+ title: String,
+ message: Option<String>,
+ percentage: Option<u8>,
+}
+
+impl ProgressState {
+ pub fn new(title: String, message: Option<String>, percentage: Option<u8>) -> Self {
+ Self {
+ title,
+ message,
+ percentage,
+ }
+ }
+
+ pub fn update(&mut self, message: Option<String>, percentage: Option<u8>) {
+ if let Some(message) = message {
+ self.message = Some(message);
+ }
+ if let Some(percentage) = percentage {
+ self.percentage = Some(percentage);
+ }
+ }
+
+ pub fn status_line(&self) -> String {
+ let mut status = format!("Debug: {}", self.title);
+ if let Some(message) = self.message.as_deref() {
+ status.push_str(" - ");
+ status.push_str(message);
+ }
+ if let Some(percentage) = self.percentage {
+ status.push_str(&format!(" ({}%)", percentage));
+ }
+ status
+ }
+
+ pub fn end_status_line(&self, message: Option<&str>) -> String {
+ let mut status = format!("Debug: {}", self.title);
+ if let Some(message) = message.or(self.message.as_deref()) {
+ status.push_str(" - ");
+ status.push_str(message);
+ } else {
+ status.push_str(" finished");
+ }
+ status
+ }
+}
+
+pub type ProgressMap = HashMap<String, ProgressState>;
diff --git a/helix-view/src/handlers/dap.rs b/helix-view/src/handlers/dap.rs
index b913ce8e..f8f3c1b8 100644
--- a/helix-view/src/handlers/dap.rs
+++ b/helix-view/src/handlers/dap.rs
@@ -332,6 +332,46 @@ impl Editor {
log::info!("{}", output);
self.set_status(format!("{} {}", prefix, output));
}
+ Event::ProgressStart(body) => {
+ let status = {
+ let debugger = match self.debug_adapters.get_client_mut(id) {
+ Some(debugger) => debugger,
+ None => return false,
+ };
+
+ debugger.progress_start(body)
+ };
+
+ self.set_status(status);
+ }
+ Event::ProgressUpdate(body) => {
+ let status = {
+ let debugger = match self.debug_adapters.get_client_mut(id) {
+ Some(debugger) => debugger,
+ None => return false,
+ };
+
+ debugger.progress_update(body)
+ };
+
+ if let Some(status) = status {
+ self.set_status(status);
+ }
+ }
+ Event::ProgressEnd(body) => {
+ let status = {
+ let debugger = match self.debug_adapters.get_client_mut(id) {
+ Some(debugger) => debugger,
+ None => return false,
+ };
+
+ debugger.progress_end(body)
+ };
+
+ if let Some(status) = status {
+ self.set_status(status);
+ }
+ }
Event::Initialized(_) => {
self.set_status("Debugger initialized...");
let debugger = match self.debug_adapters.get_client_mut(id) {