Krolyxon
Commit 35ed5a3

Branches

Tags

Refs →

bench51cratesbench51-guisrcpanesbuild.rs

188 lines · 6.5 KB · Rust

1//! Build Output: Keil-style messages, the listing and the Intel HEX file.
2
3use bench51_core::syntax::tokens;
4use eframe::egui::text::LayoutJob;
5use eframe::egui::{self, Align, CursorIcon, FontId, Layout, RichText, Sense, TextFormat, Ui};
6
7use crate::state::{Action, BuildTab, LogKind, Mode, State};
8use crate::theme::{ACCENT, HERO, palette};
9use crate::widgets;
10
11pub fn show(ui: &mut Ui, st: &mut State) {
12 let p = palette(ui.ctx());
13 let summary = st.doc().map(|d| match &d.session.last_asm {
14 Some((src, out)) if out.ok() => {
15 (out.code_size().to_string(), "bytes", false, src != &d.text)
16 }
17 Some((src, out)) => (
18 out.diagnostics.len().to_string(),
19 "errors",
20 true,
21 src != &d.text,
22 ),
23 None => (String::new(), "not built", false, false),
24 });
25 ui.horizontal(|ui| {
26 if let Some((n, unit, err, changed)) = &summary {
27 let color = if *err { ACCENT } else { p.text_display };
28 // The PC is the hero while debugging; one per screen.
29 if n.is_empty() {
30 } else if st.mode == Mode::Edit {
31 ui.label(p.hero(n, HERO).color(color));
32 } else {
33 ui.label(p.data(n).size(18.0).color(color));
34 }
35 ui.vertical(|ui| {
36 ui.add_space(if st.mode == Mode::Edit { 14.0 } else { 2.0 });
37 ui.label(p.label(*unit));
38 if *changed {
39 ui.label(p.label("[source changed]").color(p.text_disabled));
40 }
41 });
42 }
43 ui.with_layout(Layout::right_to_left(Align::Min), |ui| {
44 widgets::segmented(
45 ui,
46 &mut st.build_tab,
47 &[
48 (BuildTab::Messages, "Messages"),
49 (BuildTab::Listing, "Listing"),
50 (BuildTab::Hex, "HEX"),
51 ],
52 );
53 });
54 });
55 ui.add_space(8.0);
56 match st.build_tab {
57 BuildTab::Messages => messages(ui, st),
58 BuildTab::Listing => listing(ui, st),
59 BuildTab::Hex => hex(ui, st),
60 }
61}
62
63fn messages(ui: &mut Ui, st: &mut State) {
64 let p = palette(ui.ctx());
65 if st.log.is_empty() {
66 widgets::empty_state(
67 ui,
68 "No messages",
69 "Build output appears here. F7 assembles.",
70 );
71 return;
72 }
73 let mut goto = None;
74 egui::ScrollArea::vertical()
75 .stick_to_bottom(true)
76 .auto_shrink([false; 2])
77 .show(ui, |ui| {
78 ui.spacing_mut().item_spacing.y = 3.0;
79 for line in &st.log {
80 let color = match line.kind {
81 LogKind::Info => p.text_secondary,
82 LogKind::Ok => p.text_primary,
83 LogKind::Error => ACCENT,
84 };
85 let text = RichText::new(&line.text)
86 .font(FontId::monospace(12.5))
87 .color(color);
88 if let Some(target) = line.target {
89 let r = ui
90 .add(egui::Label::new(text).sense(Sense::click()))
91 .on_hover_cursor(CursorIcon::PointingHand);
92 if r.clicked() {
93 goto = Some(target);
94 }
95 } else {
96 ui.label(text);
97 }
98 }
99 });
100 if let Some((doc, line, col)) = goto {
101 st.actions.push(Action::Goto { doc, line, col });
102 }
103}
104
105fn rows(ui: &mut Ui, id: &str, lines: &[&str], mut row: impl FnMut(&str) -> LayoutJob) {
106 let row_h = ui.fonts_mut(|f| f.row_height(&FontId::monospace(12.5))) + 2.0;
107 egui::ScrollArea::both()
108 .id_salt(id)
109 .auto_shrink([false; 2])
110 .show_rows(ui, row_h, lines.len(), |ui, range| {
111 ui.spacing_mut().item_spacing.y = 0.0;
112 for i in range {
113 ui.add(egui::Label::new(row(lines[i])).extend());
114 }
115 });
116}
117
118fn listing(ui: &mut Ui, st: &State) {
119 let p = palette(ui.ctx());
120 let Some((src, out)) = st.doc().and_then(|d| d.session.last_asm.as_ref()) else {
121 widgets::empty_state(ui, "No listing", "Press F7 to assemble.");
122 return;
123 };
124 let text = out.listing(src);
125 let lines: Vec<&str> = text.lines().collect();
126 let size = 12.5;
127 rows(ui, "listing", &lines, |l| {
128 let font = FontId::monospace(size);
129 if l.starts_with("***") {
130 return LayoutJob::simple_singleline(l.to_string(), font, ACCENT);
131 }
132 let is_code = l.len() > 25 && l.is_char_boundary(25);
133 if is_code && (l.as_bytes()[0].is_ascii_hexdigit() || l.starts_with(' ')) {
134 let (head, rest) = l.split_at(25);
135 let mut job = LayoutJob::default();
136 let head_color = if l.starts_with(' ') {
137 p.text_disabled
138 } else {
139 p.text_secondary
140 };
141 job.append(head, 0.0, TextFormat::simple(font, head_color));
142 for (r, tok) in tokens(rest) {
143 let (color, _) = p.syntax(tok);
144 job.append(
145 &rest[r],
146 0.0,
147 TextFormat::simple(FontId::monospace(size), color),
148 );
149 }
150 return job;
151 }
152 LayoutJob::simple_singleline(l.to_string(), font, p.text_secondary)
153 });
154}
155
156fn hex(ui: &mut Ui, st: &State) {
157 let p = palette(ui.ctx());
158 let Some((_, out)) = st.doc().and_then(|d| d.session.last_asm.as_ref()) else {
159 widgets::empty_state(ui, "No HEX file", "Press F7 to assemble.");
160 return;
161 };
162 if !out.ok() {
163 widgets::empty_state(ui, "No HEX file", "Fix the errors to generate Intel HEX.");
164 return;
165 }
166 let text = out.intel_hex();
167 let lines: Vec<&str> = text.lines().collect();
168 rows(ui, "hex", &lines, |l| {
169 let font = FontId::monospace(12.5);
170 let mut job = LayoutJob::default();
171 if l.len() < 11 {
172 job.append(l, 0.0, TextFormat::simple(font, p.text_primary));
173 return job;
174 }
175 let n = l.len();
176 for (part, color) in [
177 (&l[..1], p.text_disabled),
178 (&l[1..3], p.text_secondary),
179 (&l[3..7], p.text_display),
180 (&l[7..9], p.text_secondary),
181 (&l[9..n - 2], p.text_primary),
182 (&l[n - 2..], p.text_disabled),
183 ] {
184 job.append(part, 0.0, TextFormat::simple(font.clone(), color));
185 }
186 job
187 });
188}