cargo hollywood
compiler display
bendn 2023-10-22
parent 6d6d2cf · commit 92c443e
-rw-r--r--src/cargo.rs48
-rw-r--r--src/compiler/mod.rs189
-rw-r--r--src/compiler/ui/mod.rs78
-rw-r--r--src/main.rs4
-rw-r--r--src/test/mod.rs13
-rw-r--r--src/test/ui/inspector.rs14
-rw-r--r--src/test/ui/mod.rs13
-rw-r--r--src/test/ui/progress.rs9
-rw-r--r--src/test/ui/test_list.rs19
-rw-r--r--src/ui/ls.rs (renamed from src/test/ui/ls.rs)4
-rw-r--r--src/ui/mod.rs24
11 files changed, 343 insertions, 72 deletions
diff --git a/src/cargo.rs b/src/cargo.rs
index d0ff7fa..1ca8570 100644
--- a/src/cargo.rs
+++ b/src/cargo.rs
@@ -25,6 +25,7 @@ pub fn test(at: Option<&Path>) -> Result<Receiver<TestMessage>> {
proc.arg("-C");
proc.arg(at.as_os_str());
}
+ // proc.env("RUSTFLAGS", format!("--diagnostic-width={width}"));
proc.args([
"-Zunstable-options",
"test",
@@ -43,33 +44,44 @@ pub fn test(at: Option<&Path>) -> Result<Receiver<TestMessage>> {
let mut tmp = Vec::with_capacity(32);
let mut stdout = [0; 4096];
- std::thread::spawn(move || loop {
- let n = out.read(&mut stdout).unwrap();
- for &byte in &stdout[..n] {
- match byte {
- b'\n' => {
- let val = serde_json::from_slice::<serde_json::Value>(&tmp).unwrap();
- log::debug!("got val: {}", serde_json::to_string_pretty(&val).unwrap());
- let event = match serde_json::value::from_value::<Message>(val.clone()) {
- Err(_) => TestMessage::Event(
- serde_json::value::from_value::<RawTestMessage>(val).unwrap(),
- ),
- Ok(v) => TestMessage::CompilerEvent(v),
- };
- tmp.clear();
- tx.send(event).unwrap();
+ macro_rules! handle {
+ ($n:expr) => {
+ let n = $n;
+ for &byte in &stdout[..n] {
+ match byte {
+ b'\n' => {
+ let val = serde_json::from_slice::<serde_json::Value>(&tmp).unwrap();
+ log::debug!("got val: {}", serde_json::to_string_pretty(&val).unwrap());
+ let event = match serde_json::value::from_value::<Message>(val.clone()) {
+ Err(_) => TestMessage::Event(
+ serde_json::value::from_value::<RawTestMessage>(val).unwrap(),
+ ),
+ Ok(v) => TestMessage::CompilerEvent(v),
+ };
+ tmp.clear();
+ tx.send(event).unwrap();
+ }
+ b => tmp.push(b),
}
- b => tmp.push(b),
}
- }
+ };
+ }
+ std::thread::spawn(move || loop {
+ handle!(out.read(&mut stdout).unwrap());
if let Ok(Some(_)) = proc.try_wait() {
+ while let Ok(n) = out.read(&mut stdout) {
+ if n == 0 {
+ break;
+ }
+ handle!(n);
+ }
tx.send(TestMessage::Finished).unwrap();
log::debug!("proc exited, joining thread");
break;
}
std::thread::sleep(std::time::Duration::from_millis(50));
});
- return Ok(rx);
+ Ok(rx)
}
#[derive(Deserialize)]
diff --git a/src/compiler/mod.rs b/src/compiler/mod.rs
new file mode 100644
index 0000000..b06f8e2
--- /dev/null
+++ b/src/compiler/mod.rs
@@ -0,0 +1,189 @@
+//! compiler output ui
+use anyhow::Result;
+use cargo_metadata::{diagnostic::Diagnostic, CompilerMessage, Message, PackageId};
+use crossbeam::channel::Receiver;
+use crossterm::event::{self, Event, KeyCode};
+use ratatui::prelude::*;
+use std::{
+ ops::ControlFlow,
+ time::{Duration, Instant},
+};
+
+use crate::{
+ cargo::{self, TestMessage},
+ ui::SList,
+};
+
+mod ui;
+
+const BUILT_SCRIPT: u8 = 1;
+const BUILD_SCRIPT_EXECUTED: u8 = 2;
+const FINISHED: u8 = 4;
+
+struct Crate {
+ pid: PackageId,
+ /// bitflag, see above
+ state: u8,
+}
+
+struct State {
+ compiled: SList,
+ crates: Vec<Crate>,
+ diagnostics: Vec<String>,
+ rx: Receiver<TestMessage>,
+ failed: bool,
+}
+
+impl State {
+ fn new(rx: Receiver<TestMessage>) -> Self {
+ Self {
+ compiled: SList::default(),
+ diagnostics: vec![],
+ crates: vec![],
+ failed: false,
+ rx,
+ }
+ }
+
+ fn recv(&mut self) -> RecvStatus {
+ let deadline = Instant::now() + Duration::from_millis(50);
+ while let Ok(event) = self.rx.recv_deadline(deadline) {
+ match event {
+ TestMessage::CompilerEvent(e) => match e {
+ Message::BuildFinished(b) => {
+ return match b.success {
+ true => RecvStatus::Finished,
+ false => RecvStatus::Failed,
+ }
+ }
+ Message::BuildScriptExecuted(f) => {
+ let p = self
+ .crates
+ .iter()
+ .position(|Crate { pid, .. }| pid == &f.package_id)
+ .unwrap();
+ self.crates[p].state |= BUILD_SCRIPT_EXECUTED;
+ }
+ Message::CompilerArtifact(c) => {
+ self.compiled.itemc += 1;
+ if c.target.name == "build-script-build" {
+ self.crates.push(Crate {
+ pid: c.package_id,
+ state: BUILT_SCRIPT,
+ });
+ } else {
+ match self
+ .crates
+ .iter()
+ .position(|Crate { pid, .. }| pid == &c.package_id)
+ {
+ None => self.crates.push(Crate {
+ pid: c.package_id,
+ state: FINISHED,
+ }),
+ Some(n) => self.crates[n].state |= FINISHED,
+ }
+ }
+ }
+ Message::CompilerMessage(CompilerMessage {
+ message:
+ Diagnostic {
+ rendered: Some(rendered),
+ ..
+ },
+ ..
+ }) => {
+ if self.diagnostics.contains(&rendered) {
+ continue;
+ };
+ self.diagnostics.push(rendered);
+ }
+ // Message::CompilerMessage(CompilerMessage { message, .. }) => {
+ // let mut h = ahash::AHasher::default();
+ // message.hash(&mut h);
+ // let v = h.finish();
+ // log::trace!("got {message}");
+ // if self.diagnostics.iter().all(|&(_, hash)| (hash != v)) {
+ // if let Some(span) = message.spans.first() {
+ // let f = std::fs::read_to_string(at.join(span.file_name.clone()))
+ // .unwrap();
+ // let mut e = lerr::Error::new(&f);
+ // e.message(format!(
+ // "{}: {}",
+ // match message.level {
+ // DiagnosticLevel::Help =>
+ // cformat_args!("{green}help{reset}"),
+ // DiagnosticLevel::Note => cformat_args!("{cyan}note{reset}"),
+ // DiagnosticLevel::Warning =>
+ // cformat_args!("{yellow}nit{reset}"),
+ // _ => cformat_args!("{red}error{reset}"),
+ // },
+ // message.message
+ // ));
+ // for span in message.spans {
+ // e.label((
+ // span.byte_start as usize..span.byte_end as usize,
+ // span.label.unwrap_or("here".to_string()),
+ // ));
+ // }
+ // self.diagnostics.push((e.to_string(), v));
+ // continue;
+ // } else {
+ // let mut e = lerr::Error::new("\n");
+ // e.message(format!(
+ // "{}: {}",
+ // match message.level {
+ // DiagnosticLevel::Help =>
+ // cformat_args!("{green}help{reset}"),
+ // DiagnosticLevel::Note => cformat_args!("{cyan}note{reset}"),
+ // _ => cformat_args!("{red}error{reset}"),
+ // },
+ // message.message
+ // ));
+ // self.diagnostics.push((e.to_string(), v));
+ // continue;
+ // }
+ // }
+ _ => {}
+ },
+ e => unreachable!("got bad event {e:?}"),
+ }
+ }
+ RecvStatus::None
+ }
+}
+
+enum RecvStatus {
+ Finished,
+ Failed,
+ None,
+}
+
+pub fn run<B: Backend>(
+ terminal: &mut Terminal<B>,
+ meta: &cargo::Metadata,
+ rx: Receiver<TestMessage>,
+) -> Result<ControlFlow<(), Receiver<TestMessage>>> {
+ let mut state = State::new(rx);
+ loop {
+ terminal.draw(|f| ui::ui(f, &mut state, meta))?;
+ if event::poll(Duration::from_millis(5))? {
+ if let Event::Key(key) = event::read()? {
+ match key.code {
+ KeyCode::Char('q') => return Ok(ControlFlow::Break(())),
+ KeyCode::Down | KeyCode::Char('s') => state.compiled.next(),
+ KeyCode::Up | KeyCode::Char('w') => state.compiled.prev(),
+ _ => {}
+ }
+ }
+ }
+ if state.failed {
+ continue;
+ }
+ match state.recv() {
+ RecvStatus::Failed => state.failed = true,
+ RecvStatus::None => {}
+ RecvStatus::Finished => return Ok(ControlFlow::Continue(state.rx)),
+ };
+ }
+}
diff --git a/src/compiler/ui/mod.rs b/src/compiler/ui/mod.rs
new file mode 100644
index 0000000..ed16ceb
--- /dev/null
+++ b/src/compiler/ui/mod.rs
@@ -0,0 +1,78 @@
+use super::Crate;
+use super::FINISHED;
+use crate::cargo;
+use crate::ui::*;
+
+pub fn ui<B: Backend>(f: &mut Frame<B>, state: &mut super::State, meta: &cargo::Metadata) {
+ let chunks = Layout::default()
+ .direction(Vertical)
+ .constraints([Length(3), Min(1), Length(1)])
+ .split(f.size());
+ f.render_widget(
+ if state.failed {
+ Paragraph::new(ctext!(
+ "{green}compiling {:bold_red}{reset}",
+ meta.package.name
+ ))
+ } else {
+ Paragraph::new(ctext!(
+ "{green}compiling {:bold_cyan}{reset}",
+ meta.package.name
+ ))
+ }
+ .block(
+ Block::default()
+ .borders(Borders::ALL)
+ .border_type(Rounded)
+ .style(Style::default()),
+ ),
+ chunks[0],
+ );
+ let mut l = Vec::with_capacity(state.crates.len());
+ for Crate { state, pid, .. } in &state.crates {
+ let name = pid.repr.split(' ').next().unwrap();
+ if state & FINISHED != 0 {
+ l.pt(ctext!("{green}built {:blue}", name));
+ } else {
+ l.pt(ctext!("{yellow}building {:blue}", name));
+ }
+ }
+ let l = List::new(l)
+ .highlight_style(Style::default().on_light_green().italic())
+ .highlight_symbol("> ")
+ .block(Block::default().borders(Borders::ALL));
+ if state.diagnostics.is_empty() {
+ f.render_stateful_widget(l, chunks[1], &mut state.compiled.state);
+ } else {
+ let chunks = Layout::default()
+ .direction(Horizontal)
+ .constraints([Percentage(60), Percentage(40)])
+ .split(chunks[1]);
+ f.render_stateful_widget(l, chunks[0], &mut state.compiled.state);
+ let o = state.diagnostics.concat();
+ let lines = o.lines().count() as u16;
+ f.render_widget(
+ Paragraph::new(o)
+ .scroll((lines.saturating_sub(chunks[1].height), 0))
+ .block(Block::default().title("diagnostics").borders(Borders::ALL)),
+ chunks[1],
+ );
+ }
+
+ let footer_chunks = Layout::default()
+ .direction(Horizontal)
+ .constraints([Percentage(50), Percentage(50)])
+ .split(chunks[2]);
+ let usage = Paragraph::new(ctext!(
+ "press {green}up{reset} or {red}down{reset} to change selection"
+ ));
+ f.render_widget(usage, footer_chunks[0]);
+ let status = match (|| state.crates.get(state.compiled.state.selected()?))() {
+ Some(c) => Paragraph::new(ctext!(
+ "viewing crate {:blue}",
+ c.pid.repr.split(' ').next().unwrap()
+ )),
+ None => Paragraph::new("listing crates"),
+ };
+ f.render_widget(status, footer_chunks[1]);
+}
diff --git a/src/main.rs b/src/main.rs
index 4e89d88..3a2b50a 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -11,9 +11,11 @@ use crossterm::{
};
use log::Level as RLevel;
use ratatui::prelude::*;
-mod cargo;
+pub mod cargo;
+pub mod compiler;
mod logger;
mod test;
+pub mod ui;
#[derive(Parser)]
/// Kewl cargo addon for dashboards
diff --git a/src/test/mod.rs b/src/test/mod.rs
index f5bdb47..803e1ad 100644
--- a/src/test/mod.rs
+++ b/src/test/mod.rs
@@ -6,6 +6,7 @@ use crossbeam::channel::Receiver;
use crossterm::event::{self, Event, KeyCode};
use ratatui::prelude::*;
use ratatui::Terminal;
+use std::ops::ControlFlow;
use std::path::Path;
use std::time::{Duration, Instant};
@@ -60,8 +61,8 @@ impl TestState {
self.done = true;
return;
}
- TestMessage::CompilerEvent(c) => {
- return;
+ TestMessage::CompilerEvent(e) => {
+ unreachable!("comp module should have handled event {e:?}")
}
};
match event {
@@ -99,8 +100,14 @@ pub fn run<B: Backend>(
meta: &cargo::Metadata,
) -> Result<()> {
let mut state = TestState::new(dir)?;
+ match crate::compiler::run(terminal, meta, state.rx)? {
+ ControlFlow::Break(()) => return Ok(()),
+ ControlFlow::Continue(rx) => {
+ state.rx = rx;
+ }
+ }
loop {
- terminal.draw(|f| ui::ui(f, &mut state, &meta))?;
+ terminal.draw(|f| ui::ui(f, &mut state, meta))?;
if event::poll(Duration::from_millis(5))? {
if let Event::Key(key) = event::read()? {
match state.screen {
diff --git a/src/test/ui/inspector.rs b/src/test/ui/inspector.rs
index f535cf4..820c3dc 100644
--- a/src/test/ui/inspector.rs
+++ b/src/test/ui/inspector.rs
@@ -1,14 +1,6 @@
-use ratatui::{
- layout::Direction::Vertical,
- prelude::*,
- style::Stylize,
- widgets::{Block, BorderType::*, Borders, Paragraph, Wrap},
- Frame,
-};
-
+use crate::ui::*;
use crate::{
cargo::TestEvent,
- ctext,
test::{Screen, TestState},
};
@@ -56,7 +48,7 @@ pub fn inspector<B: Backend>(f: &mut Frame<B>, state: &TestState, chunk: Rect) {
chunks[0],
);
f.render_widget(
- Paragraph::new(<String as ansi_to_tui::IntoText>::into_text(&stdout).unwrap())
+ Paragraph::new(<String as ansi_to_tui::IntoText>::into_text(stdout).unwrap())
.block(stdblock())
.scroll((state.stdout.scroll, 0)),
chunks[1],
@@ -84,7 +76,7 @@ pub fn inspector<B: Backend>(f: &mut Frame<B>, state: &TestState, chunk: Rect) {
chunks[0],
);
f.render_widget(
- Paragraph::new(<String as ansi_to_tui::IntoText>::into_text(&stdout).unwrap())
+ Paragraph::new(<String as ansi_to_tui::IntoText>::into_text(stdout).unwrap())
.block(stdblock())
.scroll((state.stdout.scroll, 0)),
chunks[1],
diff --git a/src/test/ui/mod.rs b/src/test/ui/mod.rs
index 8955f9d..b5236e6 100644
--- a/src/test/ui/mod.rs
+++ b/src/test/ui/mod.rs
@@ -1,21 +1,10 @@
-use ratatui::{
- layout::{
- Constraint::{Length, Min, Percentage},
- Direction::{Horizontal, Vertical},
- },
- prelude::*,
- widgets::{Block, BorderType::Rounded, Borders, Paragraph},
- Frame,
-};
-
mod inspector;
-mod ls;
mod progress;
pub mod stdout;
pub mod test_list;
use super::Screen;
use crate::cargo;
-use crate::ctext;
+use crate::ui::*;
pub fn ui<B: Backend>(f: &mut Frame<B>, state: &mut super::TestState, meta: &cargo::Metadata) {
let chunks = Layout::default()
diff --git a/src/test/ui/progress.rs b/src/test/ui/progress.rs
index c8a82e7..6f5fc6f 100644
--- a/src/test/ui/progress.rs
+++ b/src/test/ui/progress.rs
@@ -1,10 +1,5 @@
-use ratatui::{
- prelude::*,
- widgets::{Block, BorderType::Rounded, Borders, Paragraph},
- Frame,
-};
-
-use crate::{cargo::TestEvent, ctext, test::TestState};
+use crate::ui::*;
+use crate::{cargo::TestEvent, test::TestState};
pub fn progress<B: Backend>(f: &mut Frame<B>, state: &TestState, chunk: Rect) {
let size =
diff --git a/src/test/ui/test_list.rs b/src/test/ui/test_list.rs
index 79d98b6..cb7ddc8 100644
--- a/src/test/ui/test_list.rs
+++ b/src/test/ui/test_list.rs
@@ -1,13 +1,6 @@
-use super::ls::SList;
use crate::cargo::TestEvent;
use crate::test::TestState;
-use ratatui::{
- layout::{Constraint::Percentage, Direction::Horizontal},
- prelude::*,
- style::Stylize,
- widgets::{Block, Borders, List, ListItem},
- Frame,
-};
+use crate::ui::*;
use std::time::Duration;
#[derive(Default)]
pub struct TestList {
@@ -16,16 +9,6 @@ pub struct TestList {
c: SList,
}
-trait RExt<'a> {
- fn pl(&mut self, list: impl Into<Line<'a>>);
-}
-
-impl<'a> RExt<'a> for Vec<ListItem<'a>> {
- fn pl(&mut self, list: impl Into<Line<'a>>) {
- self.push(ListItem::new(list.into()));
- }
-}
-
impl TestList {
fn has(&mut self, n: usize) {
self.all().map(|a| a.has(n));
diff --git a/src/test/ui/ls.rs b/src/ui/ls.rs
index e41521c..6181240 100644
--- a/src/test/ui/ls.rs
+++ b/src/ui/ls.rs
@@ -3,11 +3,11 @@ use ratatui::widgets::ListState;
#[derive(Default)]
pub struct SList {
pub state: ListState,
- itemc: usize,
+ pub itemc: usize,
}
pub const fn incr(what: usize, cap: usize) -> usize {
- if what >= cap - 1 {
+ if what > cap - 1 {
0
} else {
what + 1
diff --git a/src/ui/mod.rs b/src/ui/mod.rs
new file mode 100644
index 0000000..95ee022
--- /dev/null
+++ b/src/ui/mod.rs
@@ -0,0 +1,24 @@
+pub mod ls;
+pub(crate) use crate::ctext;
+pub use ls::SList;
+pub use ratatui::{
+ layout::{Constraint::*, Direction::*},
+ prelude::*,
+ widgets::{Block, BorderType::*, Borders, List, ListItem, Paragraph, Wrap},
+ Frame,
+};
+
+pub trait RExt<'a> {
+ fn pl(&mut self, list: impl Into<Line<'a>>);
+ fn pt(&mut self, list: Text<'a>) {
+ for l in list.lines {
+ self.pl(l);
+ }
+ }
+}
+
+impl<'a> RExt<'a> for Vec<ListItem<'a>> {
+ fn pl(&mut self, list: impl Into<Line<'a>>) {
+ self.push(ListItem::new(list.into()));
+ }
+}