diff --git a/src/langserver/debugger/mod.rs b/src/langserver/debugger/mod.rs new file mode 100644 index 00000000..b2c8d664 --- /dev/null +++ b/src/langserver/debugger/mod.rs @@ -0,0 +1,49 @@ +//! Debug adapter protocol implementation for DreamSeeker. +//! +//! * https://microsoft.github.io/debug-adapter-protocol/ + +mod types; + +use std::error::Error; +use io; +use self::types::*; + +pub fn debugger_main>(mut args: I) { + let mut dreamseeker_exe = None; + + while let Some(arg) = args.next() { + if arg == "--dreamseeker-exe" { + dreamseeker_exe = Some(args.next().expect("must specify a value for --dreamseeker-exe")); + } else { + panic!("unknown argument {:?}", arg); + } + } + + let mut debugger = Debugger { + dreamseeker_exe: dreamseeker_exe.expect("must provide argument `--dreamseeker-exe path/to/dreamseeker.exe`"), + }; + io::run_forever(|message| debugger.handle_input(message)); +} + +struct Debugger { + dreamseeker_exe: String, +} + +impl Debugger { + fn handle_input(&mut self, message: &str) { + // TODO: error handling + self.dispatch_input(message).expect("error in dispatch_input"); + } + + fn dispatch_input(&mut self, message: &str) -> Result<(), Box> { + let protocol_message = serde_json::from_str::(message)?; + match protocol_message.type_.as_str() { + "request" => { + let request = serde_json::from_str::(message)?; + eprintln!("{:?}", request); + } + other => return Err(format!("unknown `type` field {:?}", other).into()) + } + Ok(()) + } +} diff --git a/src/langserver/debugger/types.rs b/src/langserver/debugger/types.rs new file mode 100644 index 00000000..41d13930 --- /dev/null +++ b/src/langserver/debugger/types.rs @@ -0,0 +1,133 @@ +//! Serde types for the Debug Adapter Protocol. +//! +//! * https://microsoft.github.io/debug-adapter-protocol/specification + +use std::collections::HashMap; +use serde_json::Value; + +// ---------------------------------------------------------------------------- +// Base Protocol + +/// Base class of requests, responses, and events. +#[derive(Serialize, Deserialize, Debug)] +pub struct ProtocolMessage { + /// Sequence number (also known as message ID). For protocol messages of type 'request' this ID can be used to cancel the request. + pub seq: i64, + #[serde(rename = "type")] + /// Message type. + /// Values: 'request', 'response', 'event', etc. + pub type_: String, +} + +/// A client or debug adapter initiated request. +#[derive(Serialize, Deserialize, Debug)] +pub struct Request { + #[serde(flatten)] + pub protocol_message: ProtocolMessage, + /// The command to execute. + pub command: String, + /// Object containing arguments for the command. + pub arguments: Option, +} + +/// A debug adapter initiated event. +#[derive(Serialize, Deserialize, Debug)] +pub struct Event { + #[serde(flatten)] + pub protocol_message: ProtocolMessage, + /// Type of event. + pub event: String, + /// Event-specific information. + pub body: Option, +} + +/// Response for a request. +#[derive(Serialize, Deserialize, Debug)] +pub struct Response { + #[serde(flatten)] + pub protocol_message: ProtocolMessage, + + /** + * Sequence number of the corresponding request. + */ + pub request_seq: i64, + + /** + * Outcome of the request. + * If true, the request was successful and the 'body' attribute may contain the result of the request. + * If the value is false, the attribute 'message' contains the error in short form and the 'body' may contain additional information (see 'ErrorResponse.body.error'). + */ + pub success: bool, + + /** + * The command requested. + */ + pub command: String, + + /** + * Contains the raw error in short form if 'success' is false. + * This raw error might be interpreted by the frontend and is not shown in the UI. + * Some predefined values exist. + * Values: + * 'cancelled': request was cancelled. + * etc. + */ + pub message: Option, + + /** + * Contains request result if success is true and optional error details if success is false. + */ + pub body: Option, +} + +/// On error (whenever ‘success’ is false), the body can provide more details. +#[derive(Serialize, Deserialize, Debug)] +pub struct ErrorResponseBody { + /// An optional, structured error message. + pub error: Option, +} + +// ---------------------------------------------------------------------------- +// Types + +#[derive(Serialize, Deserialize, Debug)] +pub struct Message { + /** + * Unique identifier for the message. + */ + pub id: i64, + + /** + * A format string for the message. Embedded variables have the form '{name}'. + * If variable name starts with an underscore character, the variable does not contain user data (PII) and can be safely used for telemetry purposes. + */ + pub format: String, + + /** + * An object used as a dictionary for looking up the variables in the format string. + */ + pub variables: Option>, + + /** + * If true send to telemetry. + */ + #[serde(rename = "sendTelemetry")] + pub send_telemetry: Option, + + /** + * If true show user. + */ + #[serde(rename = "showUser")] + pub show_user: Option, + + /** + * An optional url where additional information about this message can be found. + */ + pub url: Option, + + /** + * An optional label that is presented to the user as the UI for opening the url. + */ + #[serde(rename = "urlLabel")] + pub url_label: Option, +} diff --git a/src/langserver/main.rs b/src/langserver/main.rs index bc8a75ca..9082d359 100644 --- a/src/langserver/main.rs +++ b/src/langserver/main.rs @@ -27,6 +27,8 @@ mod find_references; mod extras; mod completion; +mod debugger; + use std::path::PathBuf; use std::collections::{HashMap, HashSet, VecDeque}; use std::collections::hash_map::Entry; @@ -62,6 +64,16 @@ fn main() { Err(e) => eprintln!("dir check failure: {}", e), } + let mut args = std::env::args(); + let _ = args.next(); // skip executable name + if let Some(arg) = args.next() { + if arg == "--debugger" { + return debugger::debugger_main(args); + } else { + panic!("unknown argument {:?}", arg); + } + } + let context = dm::Context::default(); let mut engine = Engine::new(&context); io::run_forever(|message| engine.handle_input(message));