This commit is contained in:
Jana Dönszelmann 2026-02-24 23:54:15 +01:00
parent 01774cb9c2
commit 5c6ced8ca0
No known key found for this signature in database
4 changed files with 69 additions and 21 deletions

View file

@ -8,7 +8,7 @@ name = "lv"
path = "./src/main.rs" path = "./src/main.rs"
[dependencies] [dependencies]
clap = {version="4.5", features=["derive"]} clap = {version="4.5", features=["derive", "string"]}
jiff = {version = "0.2", features = ["serde"]} jiff = {version = "0.2", features = ["serde"]}
ratatui = {version = "0.30.0", features=["unstable-rendered-line-info"]} ratatui = {version = "0.30.0", features=["unstable-rendered-line-info"]}
ratatui-themes = { version = "0.2", features = ["serde"] } ratatui-themes = { version = "0.2", features = ["serde"] }

View file

@ -1,20 +1,68 @@
use std::{ use std::{
env::temp_dir, env::temp_dir,
ffi::OsString, ffi::OsString,
fmt::{Debug, Display},
fs::{self, File}, fs::{self, File},
mem,
path::PathBuf, path::PathBuf,
process::{Command, exit}, process::{Command, exit},
str::FromStr,
}; };
mod tui; mod tui;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand, ValueEnum, builder::PossibleValue};
use jiff::Zoned; use jiff::Zoned;
use ratatui_themes::ThemeName;
#[repr(transparent)]
#[derive(Clone)]
pub struct Theme(ThemeName);
impl Debug for Theme {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl Display for Theme {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0.slug())
}
}
impl FromStr for Theme {
type Err = <ThemeName as FromStr>::Err;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(s.parse()?))
}
}
impl ValueEnum for Theme {
fn value_variants<'a>() -> &'a [Self] {
// Safety: repr transparent
unsafe { mem::transmute(ThemeName::all()) }
}
fn to_possible_value(&self) -> Option<clap::builder::PossibleValue> {
Some(PossibleValue::new(self.to_string()))
}
}
#[derive(Subcommand, Debug)] #[derive(Subcommand, Debug)]
enum Preset { enum Preset {
/// Explore logs /// Explore logs
Show, Show {
/// Path where the compiler source code lives, for links in the TUI to work.
#[arg(long = "compiler-root")]
compiler_root: Option<PathBuf>,
/// Path where the compiler source code lives, for links in the TUI to work.
#[arg(default_value_t = Theme(ThemeName::OneDarkPro))]
#[arg(long = "theme")]
theme: Theme,
},
/// Get all the typesystem related logs /// Get all the typesystem related logs
Types, Types,
@ -49,11 +97,6 @@ struct Args {
#[arg(long = "logs-dir")] #[arg(long = "logs-dir")]
logs_dir: PathBuf, logs_dir: PathBuf,
/// Path where the compiler source code lives, for links in the TUI to work.
#[arg(global = true)]
#[arg(long = "compiler-root")]
compiler_root: Option<PathBuf>,
#[arg(trailing_var_arg = true)] #[arg(trailing_var_arg = true)]
#[arg(allow_hyphen_values = true)] #[arg(allow_hyphen_values = true)]
#[arg(global = true)] #[arg(global = true)]
@ -65,12 +108,14 @@ fn main() {
preset, preset,
logs_dir, logs_dir,
rest, rest,
compiler_root,
} = Args::parse(); } = Args::parse();
let rustc_log = match preset { let rustc_log = match preset {
Preset::Show => { Preset::Show {
tui::run(logs_dir, compiler_root); compiler_root,
theme: Theme(theme)
}=> {
tui::run(logs_dir, compiler_root, theme);
exit(0); exit(0);
} }
Preset::Types => { Preset::Types => {

View file

@ -1,5 +1,6 @@
use itertools::Itertools; use itertools::Itertools;
use ratatui_themes::{Theme, ThemeName}; use ratatui_themes::{Theme, ThemeName};
use serde_json::de;
use std::{ use std::{
fs::{self, DirEntry}, fs::{self, DirEntry},
io, io,
@ -23,7 +24,7 @@ use ratatui::{
crossterm::event::{self, Event, KeyCode, KeyModifiers}, crossterm::event::{self, Event, KeyCode, KeyModifiers},
layout::{Constraint, HorizontalAlignment, Layout, Rect}, layout::{Constraint, HorizontalAlignment, Layout, Rect},
style::Style, style::Style,
text::{Span, Text}, text::{Line, Span, Text},
widgets::{ widgets::{
Block, Clear, List, ListItem, ListState, Padding, Paragraph, StatefulWidget, Widget, Wrap, Block, Clear, List, ListItem, ListState, Padding, Paragraph, StatefulWidget, Widget, Wrap,
}, },
@ -35,9 +36,9 @@ pub mod model;
pub mod processing; pub mod processing;
pub mod reader; pub mod reader;
pub fn run(logs_dir: PathBuf, compiler_root: Option<PathBuf>) { pub fn run(logs_dir: PathBuf, compiler_root: Option<PathBuf>, theme: ThemeName) {
let terminal = ratatui::init(); let terminal = ratatui::init();
let theme = Theme::new(ThemeName::OneDarkPro); let theme = Theme::new(theme);
let app_result = App::new(logs_dir, compiler_root, theme).run(terminal); let app_result = App::new(logs_dir, compiler_root, theme).run(terminal);
ratatui::restore(); ratatui::restore();
@ -458,12 +459,13 @@ impl Widget for &mut App {
{ {
let full_file_path = canonical_rustc_root.join(&file); let full_file_path = canonical_rustc_root.join(&file);
Hyperlink::new( Hyperlink::new(
format!("In file: {}", file.display()), Line::from(format!("In file: {}", file.display())).style(default),
format!("file://{}:{line}", full_file_path.display()), format!("file://{}:{line}", full_file_path.display()),
) )
.render(first_line, buf); .render(first_line, buf);
} else { } else {
Span::from(format!("In file: {}:{line}", file.display())) Line::from(format!("In file: {}:{line}", file.display()))
.style(default)
.render(first_line, buf); .render(first_line, buf);
} }
} }
@ -489,7 +491,7 @@ impl Widget for &mut App {
(res, height) (res, height)
}); });
let list = ListView::new(builder, items.len()); let list = ListView::new(builder, items.len()).style(default);
StatefulWidget::render(list, footer_area, buf, &mut lv.footer_list); StatefulWidget::render(list, footer_area, buf, &mut lv.footer_list);
} }
Tab::Empty => {} Tab::Empty => {}

View file

@ -91,10 +91,11 @@ impl LogEntry {
pub fn line_text(&self, accessed: bool, inline_depth: usize) -> Line<'static> { pub fn line_text(&self, accessed: bool, inline_depth: usize) -> Line<'static> {
const NO_MESSAGE: &str = "<no message>"; const NO_MESSAGE: &str = "<no message>";
const SPACES_BEFORE: &str = " ";
let indent = " >".repeat(inline_depth);
match self { match self {
LogEntry::Single { raw } => format!( LogEntry::Single { raw } => format!(
" ┃{}{}", "{SPACES_BEFORE}┃{indent}{}",
" >".repeat(inline_depth),
raw.fields.message().unwrap_or(NO_MESSAGE) raw.fields.message().unwrap_or(NO_MESSAGE)
) )
.into(), .into(),
@ -105,13 +106,13 @@ impl LogEntry {
&& let Some(s) = val.as_str() && let Some(s) = val.as_str()
{ {
Line::from(format!( Line::from(format!(
"{:3}⭣{:3}⇊ ┃↪ {s}", "{:4}⭣{:4}⇊ ┃{indent}↪ {s}",
sub_entries.len(), sub_entries.len(),
self.count().wrapping_sub(1) self.count().wrapping_sub(1)
)) ))
} else { } else {
format!( format!(
"{}", "{SPACES_BEFORE}┃{indent}{}",
enter.fields.message().unwrap_or(NO_MESSAGE) enter.fields.message().unwrap_or(NO_MESSAGE)
) )
.into() .into()