lexer_rs/fmt_context.rs
1//a Imports
2use crate::UserPosn;
3
4//a FmtContext
5//tt FmtContext
6/// This trait is provided by types that wish to support context for
7/// (e.g.) error messages
8///
9/// It requires the type to have the ability to map from a line number
10/// to a position within the file/stream/text of the type, and to
11/// provide the length of any specific line nummber.e
12///
13/// With those supplied methods, the trait provides the 'fmt_context'
14/// method, which outputs to a formatter (which can be an &mut String
15/// even) the lines of the text ahead of a provided span of start and
16/// end positions within the stream.
17///
18/// Currently the format of the context is fixed - the number of lines
19/// ahead is fixed a a maximum of four, the lines are always numbered
20/// with aa line number of up to 4 digits, and so on.
21pub trait FmtContext<P> {
22 /// Return the length of the specified line
23 fn line_length(&self, line: usize) -> usize;
24
25 /// Format the line of text (potentially with coloring and so on).
26 ///
27 /// This formatting must preserve the columnn numbers of characters
28 /// if context markers are to line up correctly
29 fn fmt_line(&self, f: &mut dyn std::fmt::Write, line: usize) -> std::fmt::Result;
30
31 /// Format a line of text with highlight on certain columns
32 fn fmt_context_single_line(
33 &self,
34 f: &mut dyn std::fmt::Write,
35 start: &P,
36 num_cols: usize, // number of columns to highlight
37 ) -> std::fmt::Result
38 where
39 P: UserPosn,
40 {
41 let line = start.line();
42 let first_col = start.column();
43 if line > 1 {
44 write!(f, " | ")?;
45 self.fmt_line(f, line - 1)?;
46 writeln!(f)?;
47 }
48 write!(f, "{:4}| ", line)?;
49 self.fmt_line(f, line)?;
50 writeln!(f)?;
51 write!(f, " | ")?;
52 for _ in 1..(first_col) {
53 f.write_char(' ')?;
54 }
55 if num_cols == 0 {
56 f.write_char('^')?;
57 } else {
58 for _ in 0..num_cols {
59 f.write_char('^')?;
60 }
61 }
62 writeln!(f)?;
63 writeln!(f, " | ")
64 }
65
66 /// Format multiple lines of text, highlighting certain lines
67 fn fmt_context_multiple_lines(
68 &self,
69 f: &mut dyn std::fmt::Write,
70 start: &P,
71 end: &P,
72 ) -> std::fmt::Result
73 where
74 P: UserPosn,
75 {
76 let first_line = start.line();
77 let first_line = if first_line > 1 {
78 first_line - 1
79 } else {
80 first_line
81 };
82 let last_line = end.line() + {
83 if end.column() == 0 {
84 0
85 } else {
86 1
87 }
88 };
89 // Change to a Range
90 let num_lines = {
91 if last_line <= first_line {
92 1
93 } else {
94 last_line + 1 - first_line
95 }
96 };
97
98 let (start_skip, end_skip) = {
99 if num_lines > 4 {
100 (4, num_lines - 4)
101 } else {
102 (1, 0)
103 }
104 };
105
106 let mut ellipses_output = false;
107 for i in 0..num_lines {
108 let l = first_line + i;
109 if i >= start_skip && i <= end_skip {
110 if !ellipses_output {
111 writeln!(f, " |...")?;
112 ellipses_output = true;
113 }
114 continue;
115 }
116 if l >= start.line() && l <= end.line() {
117 write!(f, "{:4}| ", l)?;
118 } else {
119 write!(f, " | ")?;
120 }
121 self.fmt_line(f, l)?;
122 writeln!(f)?;
123 }
124 Ok(())
125 }
126
127 /// Format text with highlighting between start and end
128 ///
129 /// This is the main method used by clients of the trait
130 fn fmt_context(&self, fmt: &mut dyn std::fmt::Write, start: &P, end: &P) -> std::fmt::Result
131 where
132 P: UserPosn,
133 {
134 if start.line() == end.line() || (start.line() + 1 == end.line() && end.column() == 0) {
135 let num_cols = {
136 if start.line() == end.line() {
137 end.column() - start.column()
138 } else {
139 self.line_length(start.line())
140 }
141 };
142 self.fmt_context_single_line(fmt, start, num_cols)
143 } else {
144 self.fmt_context_multiple_lines(fmt, start, end)
145 }
146 }
147}