cargo hollywood
| -rw-r--r-- | src/cargo.rs | 48 | ||||
| -rw-r--r-- | src/compiler/mod.rs | 189 | ||||
| -rw-r--r-- | src/compiler/ui/mod.rs | 78 | ||||
| -rw-r--r-- | src/main.rs | 4 | ||||
| -rw-r--r-- | src/test/mod.rs | 13 | ||||
| -rw-r--r-- | src/test/ui/inspector.rs | 14 | ||||
| -rw-r--r-- | src/test/ui/mod.rs | 13 | ||||
| -rw-r--r-- | src/test/ui/progress.rs | 9 | ||||
| -rw-r--r-- | src/test/ui/test_list.rs | 19 | ||||
| -rw-r--r-- | src/ui/ls.rs (renamed from src/test/ui/ls.rs) | 4 | ||||
| -rw-r--r-- | src/ui/mod.rs | 24 |
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())); + } +} |