use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use crate::common::error::IpcError;
#[derive(Debug, Serialize, Deserialize)]
pub struct Request {
pub id: u64,
pub command: Command,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Response {
pub id: u64,
pub success: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<IpcError>,
}
impl Response {
pub fn success(id: u64, result: serde_json::Value) -> Self {
Self {
id,
success: true,
result: Some(result),
error: None,
}
}
pub fn error(id: u64, error: IpcError) -> Self {
Self {
id,
success: false,
result: None,
error: Some(error),
}
}
pub fn ok(id: u64) -> Self {
Self {
id,
success: true,
result: Some(serde_json::json!({})),
error: None,
}
}
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum Command {
Start {
program: PathBuf,
args: Vec<String>,
adapter: Option<String>,
stop_on_entry: bool,
#[serde(default)]
initial_breakpoints: Vec<String>,
},
Attach {
pid: u32,
adapter: Option<String>,
},
Detach,
Stop,
Restart,
Status,
BreakpointAdd {
location: BreakpointLocation,
condition: Option<String>,
hit_count: Option<u32>,
},
BreakpointRemove {
id: Option<u32>,
all: bool,
},
BreakpointList,
BreakpointEnable { id: u32 },
BreakpointDisable { id: u32 },
Continue,
Next,
StepIn,
StepOut,
Pause,
StackTrace {
thread_id: Option<i64>,
limit: usize,
},
Locals { frame_id: Option<i64> },
Evaluate {
expression: String,
frame_id: Option<i64>,
context: EvaluateContext,
},
Scopes { frame_id: i64 },
Variables { reference: i64 },
Threads,
ThreadSelect { id: i64 },
FrameSelect { number: usize },
FrameUp,
FrameDown,
Context { lines: usize },
Await { timeout_secs: u64 },
GetOutput {
tail: Option<usize>,
clear: bool,
},
SubscribeOutput,
Shutdown,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum BreakpointLocation {
Line { file: PathBuf, line: u32 },
Function { name: String },
}
impl BreakpointLocation {
pub fn parse(s: &str) -> Result<Self, crate::common::Error> {
if let Some(colon_idx) = s.rfind(':') {
let (file_part, line_part) = s.split_at(colon_idx);
let line_str = &line_part[1..];
if !line_str.is_empty() && line_str.chars().all(|c| c.is_ascii_digit()) {
let line: u32 = line_str.parse().map_err(|_| {
crate::common::Error::InvalidLocation(format!(
"invalid line number: {}",
line_str
))
})?;
return Ok(Self::Line {
file: PathBuf::from(file_part),
line,
});
}
}
Ok(Self::Function {
name: s.to_string(),
})
}
}
impl std::fmt::Display for BreakpointLocation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Line { file, line } => write!(f, "{}:{}", file.display(), line),
Self::Function { name } => write!(f, "{}", name),
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum EvaluateContext {
#[default]
Watch,
Repl,
Hover,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct StatusResult {
pub daemon_running: bool,
pub session_active: bool,
pub state: Option<String>,
pub program: Option<String>,
pub adapter: Option<String>,
pub stopped_thread: Option<i64>,
pub stopped_reason: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BreakpointInfo {
pub id: u32,
pub verified: bool,
pub source: Option<String>,
pub line: Option<u32>,
pub message: Option<String>,
pub enabled: bool,
pub condition: Option<String>,
pub hit_count: Option<u32>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct StackFrameInfo {
pub id: i64,
pub name: String,
pub source: Option<String>,
pub line: Option<u32>,
pub column: Option<u32>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ThreadInfo {
pub id: i64,
pub name: String,
pub state: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct VariableInfo {
pub name: String,
pub value: String,
pub type_name: Option<String>,
pub variables_reference: i64,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct StopResult {
pub reason: String,
pub description: Option<String>,
#[serde(default)]
pub thread_id: Option<i64>,
#[serde(default)]
pub all_threads_stopped: bool,
#[serde(default)]
pub hit_breakpoint_ids: Vec<u32>,
pub source: Option<String>,
pub line: Option<u32>,
pub column: Option<u32>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct EvaluateResult {
pub result: String,
pub type_name: Option<String>,
pub variables_reference: i64,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ContextResult {
pub thread_id: i64,
pub source: Option<String>,
pub line: u32,
pub column: Option<u32>,
pub function: Option<String>,
pub source_lines: Vec<SourceLine>,
pub locals: Vec<VariableInfo>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct SourceLine {
pub number: u32,
pub content: String,
pub is_current: bool,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_file_line() {
let loc = BreakpointLocation::parse("src/main.rs:42").unwrap();
match loc {
BreakpointLocation::Line { file, line } => {
assert_eq!(file, PathBuf::from("src/main.rs"));
assert_eq!(line, 42);
}
_ => panic!("Expected Line variant"),
}
}
#[test]
fn test_parse_function() {
let loc = BreakpointLocation::parse("main").unwrap();
match loc {
BreakpointLocation::Function { name } => {
assert_eq!(name, "main");
}
_ => panic!("Expected Function variant"),
}
}
#[test]
fn test_parse_namespaced_function() {
let loc = BreakpointLocation::parse("mymod::MyStruct::method").unwrap();
match loc {
BreakpointLocation::Function { name } => {
assert_eq!(name, "mymod::MyStruct::method");
}
_ => panic!("Expected Function variant"),
}
}
#[cfg(windows)]
#[test]
fn test_parse_windows_path() {
let loc = BreakpointLocation::parse(r"C:\Users\test\src\main.rs:42").unwrap();
match loc {
BreakpointLocation::Line { file, line } => {
assert_eq!(file, PathBuf::from(r"C:\Users\test\src\main.rs"));
assert_eq!(line, 42);
}
_ => panic!("Expected Line variant"),
}
}
}