Skip to main content

dada_debug/
root.rs

1use std::sync::Arc;
2
3use dada_ir_ast::{DebugEvent, DebugEventPayload, span::AbsoluteOffset};
4use serde::Serialize;
5use url::Url;
6
7use crate::server::State;
8
9/// Struct passed into the handlebars template to allow it to generate root event listing.
10#[derive(Serialize)]
11pub struct RootEvent {
12    url: String,
13    start: usize,
14    end: usize,
15    line_start: usize,
16    col_start: usize,
17    line_end: usize,
18    col_end: usize,
19    text: Option<String>,
20    payload: RootEventPayload,
21}
22
23#[derive(Serialize)]
24#[serde(tag = "type")]
25enum RootEventPayload {
26    Diagnostic {
27        message: String,
28    },
29    CheckLog {
30        index: usize,
31        root_event_info: Option<serde_json::Value>,
32        total_events: Option<usize>,
33    },
34}
35
36// basic handler that responds with a static string
37pub async fn root(state: &State) -> anyhow::Result<String> {
38    let events = root_events(&state.debug_events.lock().unwrap())?;
39    crate::hbs::render("index", &events)
40}
41
42pub async fn root_data(state: &State) -> anyhow::Result<Vec<RootEvent>> {
43    let events = state.debug_events.lock().unwrap();
44    root_events(&events)
45}
46
47fn root_events(events: &[Arc<DebugEvent>]) -> anyhow::Result<Vec<RootEvent>> {
48    let mut output = Vec::with_capacity(events.len());
49    for (event, index) in events.iter().zip(0..) {
50        let payload = match &event.payload {
51            DebugEventPayload::Diagnostic(diagnostic) => RootEventPayload::Diagnostic {
52                message: diagnostic.message.clone(),
53            },
54            DebugEventPayload::CheckLog(log_value) => {
55                // Extract root_event_info and total_events from the log_value
56                let root_event_info = log_value.get("root_event_info").cloned();
57                let total_events = log_value
58                    .get("total_events")
59                    .and_then(|v| v.as_u64())
60                    .map(|v| v as usize);
61
62                RootEventPayload::CheckLog {
63                    index,
64                    root_event_info,
65                    total_events,
66                }
67            }
68        };
69        let (text, line_start, col_start, line_end, col_end) =
70            extract_span(&event.url, event.start, event.end)?;
71        output.push(RootEvent {
72            url: event.url.to_string(),
73            start: event.start.as_usize(),
74            end: event.end.as_usize(),
75            line_start,
76            col_start,
77            line_end,
78            col_end,
79            text,
80            payload,
81        });
82    }
83    Ok(output)
84}
85
86fn extract_span(
87    url: &Url,
88    start: AbsoluteOffset,
89    end: AbsoluteOffset,
90) -> anyhow::Result<(Option<String>, usize, usize, usize, usize)> {
91    // special case a path like `/prelude.dada`
92    let path_without_leading_slash = url.path().trim_start_matches('/');
93    if !path_without_leading_slash.contains('/') {
94        return Ok((None, 0, 0, 0, 0));
95    }
96
97    // otherwise try to load the contents and create an excerpt
98    let contents = match std::fs::read_to_string(url.path()) {
99        Ok(s) => s,
100        Err(e) => anyhow::bail!("failed to read `{}`: {e}", url.path()),
101    };
102    let start = start.as_usize();
103    let end = end.as_usize();
104    let text = &contents[start..end];
105    let text = if text.len() > 65 {
106        let first_40 = &text[..40];
107        let last_20 = &text[text.len() - 20..];
108        format!("{first_40} ... {last_20}")
109    } else {
110        text.to_string()
111    };
112
113    let (line_start, col_start) = line_column(&contents, start);
114    let (line_end, col_end) = line_column(&contents, end);
115
116    Ok((Some(text), line_start, col_start, line_end, col_end))
117}
118
119fn line_column(text: &str, offset: usize) -> (usize, usize) {
120    let mut line = 1;
121    let mut col = 1;
122    for ch in text[..offset].chars() {
123        if ch == '\n' {
124            line += 1;
125            col = 1;
126        } else {
127            col += 1;
128        }
129    }
130    (line, col)
131}