Skip to main content

shape_ast/error/parse_error/
source_context.rs

1//! Source context rendering types for error display
2
3use crate::error::SourceLocation;
4
5/// Source context for rendering
6#[derive(Debug, Clone, Default)]
7pub struct SourceContext {
8    /// Lines around the error (typically 1-3 lines before, error line, 1-3 after)
9    pub lines: Vec<SourceLine>,
10    /// Index of the error line in the lines vector
11    pub error_line_index: usize,
12}
13
14impl SourceContext {
15    pub fn new(lines: Vec<SourceLine>, error_line_index: usize) -> Self {
16        Self {
17            lines,
18            error_line_index,
19        }
20    }
21
22    /// Build source context from source text and location
23    pub fn from_source(
24        source: &str,
25        location: &SourceLocation,
26        span_end: Option<(usize, usize)>,
27    ) -> Self {
28        let lines: Vec<&str> = source.lines().collect();
29        let error_line_idx = location.line.saturating_sub(1);
30
31        // Get context lines (2 before, error line, 2 after)
32        let start_idx = error_line_idx.saturating_sub(2);
33        let end_idx = (error_line_idx + 3).min(lines.len());
34
35        let context_lines: Vec<SourceLine> = (start_idx..end_idx)
36            .map(|i| {
37                let content = lines.get(i).unwrap_or(&"").to_string();
38                let highlights = if i == error_line_idx {
39                    let end_col = span_end
40                        .filter(|(el, _)| *el == location.line)
41                        .map(|(_, ec)| ec)
42                        .or(location.length.map(|l| location.column + l))
43                        .unwrap_or(location.column + 1);
44
45                    vec![Highlight {
46                        start: location.column,
47                        end: end_col,
48                        style: HighlightStyle::Primary,
49                        label: None,
50                    }]
51                } else {
52                    vec![]
53                };
54
55                SourceLine {
56                    number: i + 1,
57                    content,
58                    highlights,
59                }
60            })
61            .collect();
62
63        Self {
64            lines: context_lines,
65            error_line_index: error_line_idx.saturating_sub(start_idx),
66        }
67    }
68}
69
70/// A single source line with metadata
71#[derive(Debug, Clone)]
72pub struct SourceLine {
73    /// Line number (1-based)
74    pub number: usize,
75    /// The line content
76    pub content: String,
77    /// Highlights/underlines on this line
78    pub highlights: Vec<Highlight>,
79}
80
81/// A highlight/underline on a source line
82#[derive(Debug, Clone)]
83pub struct Highlight {
84    /// Start column (1-based)
85    pub start: usize,
86    /// End column (exclusive, 1-based)
87    pub end: usize,
88    /// Style of highlight
89    pub style: HighlightStyle,
90    /// Optional label
91    pub label: Option<String>,
92}
93
94impl Highlight {
95    pub fn primary(start: usize, end: usize) -> Self {
96        Self {
97            start,
98            end,
99            style: HighlightStyle::Primary,
100            label: None,
101        }
102    }
103
104    pub fn secondary(start: usize, end: usize) -> Self {
105        Self {
106            start,
107            end,
108            style: HighlightStyle::Secondary,
109            label: None,
110        }
111    }
112
113    pub fn with_label(mut self, label: impl Into<String>) -> Self {
114        self.label = Some(label.into());
115        self
116    }
117}
118
119#[derive(Debug, Clone, Copy, PartialEq)]
120pub enum HighlightStyle {
121    Primary,    // Main error location (^^^)
122    Secondary,  // Related location (---)
123    Suggestion, // Where a fix would go
124}