mcfunction_debug_adapter/
api.rs

1// McFunction-Debugger is a debugger for Minecraft's *.mcfunction files that does not require any
2// Minecraft mods.
3//
4// © Copyright (C) 2021-2023 Adrodoc <adrodoc55@googlemail.com> & skess42 <skagaros@gmail.com>
5//
6// This file is part of McFunction-Debugger.
7//
8// McFunction-Debugger is free software: you can redistribute it and/or modify it under the terms of
9// the GNU General Public License as published by the Free Software Foundation, either version 3 of
10// the License, or (at your option) any later version.
11//
12// McFunction-Debugger is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
13// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15//
16// You should have received a copy of the GNU General Public License along with McFunction-Debugger.
17// If not, see <http://www.gnu.org/licenses/>.
18
19use crate::{
20    error::{PartialErrorResponse, RequestError},
21    get_command, Outbox,
22};
23use async_trait::async_trait;
24use debug_adapter_protocol::{
25    events::Event,
26    requests::{
27        ContinueRequestArguments, DisconnectRequestArguments, EvaluateRequestArguments,
28        InitializeRequestArguments, LaunchRequestArguments, NextRequestArguments,
29        PauseRequestArguments, Request, ScopesRequestArguments, SetBreakpointsRequestArguments,
30        StackTraceRequestArguments, StepInRequestArguments, StepOutRequestArguments,
31        TerminateRequestArguments, VariablesRequestArguments,
32    },
33    responses::{
34        ContinueResponseBody, ErrorResponse, ErrorResponseBody, EvaluateResponseBody,
35        ScopesResponseBody, SetBreakpointsResponseBody, StackTraceResponseBody, SuccessResponse,
36        ThreadsResponseBody, VariablesResponseBody,
37    },
38    types::Capabilities,
39    SequenceNumber,
40};
41use tokio::sync::mpsc::UnboundedReceiver;
42use typed_builder::TypedBuilder;
43
44pub trait DebugAdapterContext {
45    fn fire_event(&mut self, event: impl Into<Event> + Send);
46
47    fn start_cancellable_progress(
48        &mut self,
49        title: String,
50        message: Option<String>,
51    ) -> ProgressContext;
52
53    fn end_cancellable_progress(&mut self, progress_id: String, message: Option<String>);
54
55    fn shutdown(&mut self);
56}
57
58pub struct ProgressContext {
59    pub progress_id: String,
60    cancel_receiver: UnboundedReceiver<SequenceNumber>,
61    outbox: Outbox,
62}
63impl ProgressContext {
64    pub(super) fn new(
65        progress_id: String,
66        cancel_receiver: UnboundedReceiver<SequenceNumber>,
67        outbox: Outbox,
68    ) -> ProgressContext {
69        ProgressContext {
70            progress_id,
71            cancel_receiver,
72            outbox,
73        }
74    }
75
76    pub async fn next_cancel_request(&mut self) -> Option<CancelRequest> {
77        let request_id = self.cancel_receiver.recv().await?;
78        Some(CancelRequest {
79            outbox: self.outbox.clone(),
80            request_id,
81            response_sent: false,
82        })
83    }
84}
85impl Drop for ProgressContext {
86    fn drop(&mut self) {
87        while let Ok(open_request) = self.cancel_receiver.try_recv() {
88            self.outbox
89                .respond_unknown_progress(open_request, self.progress_id.to_string())
90        }
91    }
92}
93
94pub struct CancelRequest {
95    outbox: Outbox,
96    request_id: SequenceNumber,
97    response_sent: bool,
98}
99impl CancelRequest {
100    pub fn respond(mut self, response: Result<(), CancelErrorResponse>) {
101        self.respond_without_consuming(response)
102    }
103
104    fn respond_without_consuming(&mut self, response: Result<(), CancelErrorResponse>) {
105        // Prevent sending response in drop if it was already sent manually
106        if self.response_sent {
107            return;
108        }
109        self.response_sent = true;
110        let result = response
111            .map(|()| SuccessResponse::Cancel)
112            .map_err(Into::into);
113        self.outbox.respond(self.request_id, result);
114    }
115}
116impl Drop for CancelRequest {
117    fn drop(&mut self) {
118        self.respond_without_consuming(Ok(()))
119    }
120}
121
122#[derive(Clone, Debug, Eq, PartialEq, TypedBuilder)]
123pub struct CancelErrorResponse {
124    /// Contains the raw error in short form if 'success' is false.
125    /// This raw error might be interpreted by the frontend and is not shown in the
126    /// UI.
127    /// Some predefined values exist.
128    /// Values:
129    /// 'cancelled': request was cancelled.
130    /// etc.
131    pub message: String,
132
133    #[builder(default)]
134    pub body: ErrorResponseBody,
135
136    #[builder(default, setter(skip))]
137    private: (),
138}
139impl From<CancelErrorResponse> for ErrorResponse {
140    fn from(value: CancelErrorResponse) -> Self {
141        ErrorResponse::builder()
142            .command("cancel".to_string())
143            .message(value.message)
144            .body(value.body)
145            .build()
146    }
147}
148
149#[async_trait]
150pub trait DebugAdapter {
151    type Message: Send + 'static;
152    type CustomError;
153
154    fn map_custom_error(e: Self::CustomError) -> RequestError<Self::CustomError> {
155        RequestError::Terminate(e)
156    }
157
158    async fn handle_other_message(
159        &mut self,
160        _message: Self::Message,
161        _context: impl DebugAdapterContext + Send,
162    ) -> Result<(), Self::CustomError> {
163        Ok(())
164    }
165
166    async fn handle_client_request(
167        &mut self,
168        request: Request,
169        context: impl DebugAdapterContext + Send,
170    ) -> Result<SuccessResponse, RequestError<Self::CustomError>> {
171        match request {
172            Request::ConfigurationDone => self
173                .configuration_done(context)
174                .await
175                .map(|()| SuccessResponse::ConfigurationDone),
176            Request::Continue(args) => self
177                .continue_(args, context)
178                .await
179                .map(SuccessResponse::Continue),
180            Request::Disconnect(args) => self
181                .disconnect(args, context)
182                .await
183                .map(|()| SuccessResponse::Disconnect),
184            Request::Evaluate(args) => self
185                .evaluate(args, context)
186                .await
187                .map(SuccessResponse::Evaluate),
188            Request::Initialize(args) => self
189                .initialize(args, context)
190                .await
191                .map(SuccessResponse::Initialize),
192            Request::Launch(args) => self
193                .launch(args, context)
194                .await
195                .map(|()| SuccessResponse::Launch),
196            Request::Next(args) => self
197                .next(args, context)
198                .await
199                .map(|()| SuccessResponse::Next),
200            Request::Pause(args) => self
201                .pause(args, context)
202                .await
203                .map(|()| SuccessResponse::Pause),
204            Request::Scopes(args) => self
205                .scopes(args, context)
206                .await
207                .map(SuccessResponse::Scopes),
208            Request::SetBreakpoints(args) => self
209                .set_breakpoints(args, context)
210                .await
211                .map(SuccessResponse::SetBreakpoints),
212            Request::StackTrace(args) => self
213                .stack_trace(args, context)
214                .await
215                .map(SuccessResponse::StackTrace),
216            Request::StepIn(args) => self
217                .step_in(args, context)
218                .await
219                .map(|()| SuccessResponse::StepIn),
220            Request::StepOut(args) => self
221                .step_out(args, context)
222                .await
223                .map(|()| SuccessResponse::StepOut),
224            Request::Terminate(args) => self
225                .terminate(args, context)
226                .await
227                .map(|()| SuccessResponse::Terminate),
228            Request::Threads => self.threads(context).await.map(SuccessResponse::Threads),
229            Request::Variables(args) => self
230                .variables(args, context)
231                .await
232                .map(SuccessResponse::Variables),
233            _ => {
234                let command = get_command(&request);
235                Err(RequestError::Respond(PartialErrorResponse::new(format!(
236                    "Unsupported request {}",
237                    command
238                ))))
239            }
240        }
241    }
242
243    async fn configuration_done(
244        &mut self,
245        _context: impl DebugAdapterContext + Send,
246    ) -> Result<(), RequestError<Self::CustomError>> {
247        Err(RequestError::Respond(PartialErrorResponse::new(
248            "Unsupported request 'configurationDone'".to_string(),
249        )))
250    }
251
252    async fn continue_(
253        &mut self,
254        _args: ContinueRequestArguments,
255        _context: impl DebugAdapterContext + Send,
256    ) -> Result<ContinueResponseBody, RequestError<Self::CustomError>>;
257
258    async fn disconnect(
259        &mut self,
260        _args: DisconnectRequestArguments,
261        mut context: impl DebugAdapterContext + Send,
262    ) -> Result<(), RequestError<Self::CustomError>> {
263        context.shutdown();
264        Ok(())
265    }
266
267    async fn evaluate(
268        &mut self,
269        _args: EvaluateRequestArguments,
270        _context: impl DebugAdapterContext + Send,
271    ) -> Result<EvaluateResponseBody, RequestError<Self::CustomError>> {
272        Err(RequestError::Respond(PartialErrorResponse::new(
273            "Unsupported request 'evaluate'".to_string(),
274        )))
275    }
276
277    async fn initialize(
278        &mut self,
279        _args: InitializeRequestArguments,
280        _context: impl DebugAdapterContext + Send,
281    ) -> Result<Capabilities, RequestError<Self::CustomError>> {
282        Err(RequestError::Respond(PartialErrorResponse::new(
283            "Unsupported request 'initialize'".to_string(),
284        )))
285    }
286
287    async fn launch(
288        &mut self,
289        _args: LaunchRequestArguments,
290        _context: impl DebugAdapterContext + Send,
291    ) -> Result<(), RequestError<Self::CustomError>> {
292        Err(RequestError::Respond(PartialErrorResponse::new(
293            "Unsupported request 'launch'".to_string(),
294        )))
295    }
296
297    async fn next(
298        &mut self,
299        _args: NextRequestArguments,
300        _context: impl DebugAdapterContext + Send,
301    ) -> Result<(), RequestError<Self::CustomError>> {
302        Err(RequestError::Respond(PartialErrorResponse::new(
303            "Unsupported request 'next'".to_string(),
304        )))
305    }
306
307    async fn pause(
308        &mut self,
309        _args: PauseRequestArguments,
310        _context: impl DebugAdapterContext + Send,
311    ) -> Result<(), RequestError<Self::CustomError>> {
312        Err(RequestError::Respond(PartialErrorResponse::new(
313            "Unsupported request 'pause'".to_string(),
314        )))
315    }
316
317    async fn scopes(
318        &mut self,
319        _args: ScopesRequestArguments,
320        _context: impl DebugAdapterContext + Send,
321    ) -> Result<ScopesResponseBody, RequestError<Self::CustomError>> {
322        Err(RequestError::Respond(PartialErrorResponse::new(
323            "Unsupported request 'scopes'".to_string(),
324        )))
325    }
326
327    async fn set_breakpoints(
328        &mut self,
329        _args: SetBreakpointsRequestArguments,
330        _context: impl DebugAdapterContext + Send,
331    ) -> Result<SetBreakpointsResponseBody, RequestError<Self::CustomError>> {
332        Err(RequestError::Respond(PartialErrorResponse::new(
333            "Unsupported request 'setBreakpoints'".to_string(),
334        )))
335    }
336
337    async fn stack_trace(
338        &mut self,
339        _args: StackTraceRequestArguments,
340        _context: impl DebugAdapterContext + Send,
341    ) -> Result<StackTraceResponseBody, RequestError<Self::CustomError>> {
342        Err(RequestError::Respond(PartialErrorResponse::new(
343            "Unsupported request 'stackTrace'".to_string(),
344        )))
345    }
346
347    async fn step_in(
348        &mut self,
349        _args: StepInRequestArguments,
350        _context: impl DebugAdapterContext + Send,
351    ) -> Result<(), RequestError<Self::CustomError>> {
352        Err(RequestError::Respond(PartialErrorResponse::new(
353            "Unsupported request 'stepIn'".to_string(),
354        )))
355    }
356
357    async fn step_out(
358        &mut self,
359        _args: StepOutRequestArguments,
360        _context: impl DebugAdapterContext + Send,
361    ) -> Result<(), RequestError<Self::CustomError>> {
362        Err(RequestError::Respond(PartialErrorResponse::new(
363            "Unsupported request 'stepOut'".to_string(),
364        )))
365    }
366
367    async fn terminate(
368        &mut self,
369        _args: TerminateRequestArguments,
370        _context: impl DebugAdapterContext + Send,
371    ) -> Result<(), RequestError<Self::CustomError>> {
372        Err(RequestError::Respond(PartialErrorResponse::new(
373            "Unsupported request 'terminate'".to_string(),
374        )))
375    }
376
377    async fn threads(
378        &mut self,
379        _context: impl DebugAdapterContext + Send,
380    ) -> Result<ThreadsResponseBody, RequestError<Self::CustomError>> {
381        Err(RequestError::Respond(PartialErrorResponse::new(
382            "Unsupported request 'threads'".to_string(),
383        )))
384    }
385
386    async fn variables(
387        &mut self,
388        _args: VariablesRequestArguments,
389        _context: impl DebugAdapterContext + Send,
390    ) -> Result<VariablesResponseBody, RequestError<Self::CustomError>> {
391        Err(RequestError::Respond(PartialErrorResponse::new(
392            "Unsupported request 'variables'".to_string(),
393        )))
394    }
395}