pub mod compliance;
pub mod components;
pub mod crypto;
pub mod export;
pub mod licenses;
pub mod quality;
pub mod source;
pub mod text;
pub mod vulnerabilities;
use crossterm::{
event::DisableMouseCapture,
execute,
terminal::{LeaveAlternateScreen, disable_raw_mode},
};
pub(crate) fn restore_terminal() {
let _ = disable_raw_mode();
let _ = execute!(std::io::stdout(), LeaveAlternateScreen, DisableMouseCapture);
}
pub(crate) fn install_panic_hook() {
static INSTALL: std::sync::Once = std::sync::Once::new();
INSTALL.call_once(|| {
let previous = std::panic::take_hook();
std::panic::set_hook(Box::new(move |info| {
restore_terminal();
previous(info);
}));
});
}
pub(crate) const fn floor_char_boundary(s: &str, index: usize) -> usize {
if index >= s.len() {
s.len()
} else {
let bytes = s.as_bytes();
let mut i = index;
while i > 0 && bytes[i] & 0b1100_0000 == 0b1000_0000 {
i -= 1;
}
i
}
}
pub(crate) struct TerminalGuard;
impl TerminalGuard {
pub(crate) fn enter() -> std::io::Result<Self> {
crossterm::terminal::enable_raw_mode()?;
crossterm::execute!(
std::io::stdout(),
crossterm::terminal::EnterAlternateScreen,
crossterm::event::EnableMouseCapture
)?;
Ok(Self)
}
}
impl Drop for TerminalGuard {
fn drop(&mut self) {
let _ = crossterm::terminal::disable_raw_mode();
let _ = crossterm::execute!(
std::io::stdout(),
crossterm::terminal::LeaveAlternateScreen,
crossterm::event::DisableMouseCapture,
crossterm::cursor::Show
);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct TabWindow {
pub start: usize,
pub end: usize,
pub clipped_left: bool,
pub clipped_right: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TabHit {
Tab(usize),
PrevMarker,
NextMarker,
Miss,
}
#[must_use]
pub fn tab_window(
label_widths: &[u16],
divider_width: u16,
selected: usize,
avail: u16,
) -> TabWindow {
let n = label_widths.len();
if n == 0 {
return TabWindow::default();
}
let selected = selected.min(n - 1);
let fits = |start: usize, end: usize| -> bool {
let labels: u16 = label_widths[start..end].iter().sum();
let dividers = divider_width * (end - start).saturating_sub(1) as u16;
let markers = u16::from(start > 0) * 2 + u16::from(end < n) * 2;
labels + dividers + markers <= avail
};
let full: u16 = label_widths.iter().sum::<u16>() + divider_width * (n - 1) as u16;
if full <= avail {
return TabWindow {
start: 0,
end: n,
clipped_left: false,
clipped_right: false,
};
}
let (mut start, mut end) = (selected, selected + 1);
loop {
let can_right = end < n && fits(start, end + 1);
let can_left = start > 0 && fits(start - 1, end);
if can_right {
end += 1;
} else if can_left {
start -= 1;
} else {
break;
}
}
TabWindow {
start,
end,
clipped_left: start > 0,
clipped_right: end < n,
}
}
#[must_use]
pub fn tab_bar_hit_windowed(
labels: &[String],
window: TabWindow,
left: u16,
divider_width: u16,
click_x: u16,
) -> TabHit {
use unicode_width::UnicodeWidthStr;
if click_x < left {
return TabHit::Miss;
}
let mut cursor = left;
if window.clipped_left {
if click_x < cursor + 2 {
return TabHit::PrevMarker;
}
cursor += 2;
}
let visible = &labels[window.start..window.end];
for (i, label) in visible.iter().enumerate() {
let span = UnicodeWidthStr::width(label.as_str()) as u16;
if click_x >= cursor && click_x < cursor.saturating_add(span) {
return TabHit::Tab(window.start + i);
}
cursor = cursor.saturating_add(span);
if i + 1 != visible.len() {
cursor = cursor.saturating_add(divider_width);
}
}
if window.clipped_right && click_x >= cursor && click_x < cursor + 2 {
return TabHit::NextMarker;
}
TabHit::Miss
}
#[cfg(test)]
mod tab_window_tests {
use super::{TabHit, TabWindow, tab_bar_hit_windowed, tab_window};
const W: &[u16] = &[12, 14, 16, 12, 18, 11, 14, 8, 9, 9];
#[test]
fn tab_window_no_markers_when_fits() {
let w = tab_window(W, 3, 0, 400);
assert_eq!((w.start, w.end), (0, 10));
assert!(!w.clipped_left && !w.clipped_right);
}
#[test]
fn tab_window_keeps_selected_visible() {
for avail in [40u16, 60, 80, 120, 200] {
for selected in [0usize, 4, 9] {
let w = tab_window(W, 3, selected, avail);
assert!(
(w.start..w.end).contains(&selected),
"selected {selected} outside window {w:?} at avail {avail}"
);
let labels: u16 = W[w.start..w.end].iter().sum();
let dividers = 3 * (w.end - w.start).saturating_sub(1) as u16;
let markers = u16::from(w.clipped_left) * 2 + u16::from(w.clipped_right) * 2;
assert!(
labels + dividers + markers <= avail,
"window {w:?} overflows {avail}"
);
}
}
}
#[test]
fn tab_window_marks_clipped_sides() {
let w = tab_window(W, 3, 4, 60);
assert!(w.clipped_left, "tabs before the window need a « marker");
assert!(w.clipped_right, "tabs after the window need a » marker");
}
#[test]
fn tab_bar_hit_windowed_respects_offset() {
let labels: Vec<String> = (0..10).map(|i| format!("[{i}] Tab{i} ")).collect();
let window = TabWindow {
start: 3,
end: 6,
clipped_left: true,
clipped_right: true,
};
assert_eq!(
tab_bar_hit_windowed(&labels, window, 0, 3, 0),
TabHit::PrevMarker
);
assert_eq!(
tab_bar_hit_windowed(&labels, window, 0, 3, 3),
TabHit::Tab(3)
);
}
}