use crate::{
color::BG_CURSOR_LINE,
fuzzy::Fuzzy,
local_registry::{LocalRegistry, PkgInfo},
ui::{render_line, LineState, Scroll, Surround},
};
use ratatui::prelude::{Buffer, Rect};
use term_rustdoc::util::xformat;
#[derive(Default)]
pub(super) struct PkgLists {
local: LocalRegistry,
filter: Vec<LocalPkgsIndex>,
local_all_versions: LocalRegistry,
fuzzy: Option<Fuzzy>,
}
impl PkgLists {
fn new_local(fuzzy: Fuzzy) -> Self {
let [registry, all] = match LocalRegistry::all_pkgs_with_latest_and_all_versions() {
Ok(registry) => registry,
Err(err) => {
error!("{err}");
return PkgLists::default();
}
};
info!(
"Found {} latest pkgs under {}",
registry.len(),
registry.registry_src_path().display()
);
PkgLists {
filter: (0..registry.len()).map(LocalPkgsIndex).collect(),
local: registry,
local_all_versions: all,
fuzzy: Some(fuzzy),
}
}
pub fn get_all_version(&self, name: &str) -> Vec<PkgInfo> {
let all = &self.local_all_versions;
let Ok(found) = all.binary_search_by(|info| info.name().cmp(name)) else {
return Vec::new();
};
let before = all[..found]
.iter()
.rev()
.take_while(|info| info.name() == name)
.count();
let after = all[found..]
.iter()
.take_while(|info| info.name() == name)
.count();
let mut all = all[found.saturating_sub(before)..found.saturating_add(after)].to_owned();
all.sort_unstable_by(|a, b| b.version().cmp(a.version()));
all
}
fn fill_filter(&mut self) {
let filtered = &mut self.filter;
if filtered.is_empty() {
filtered.extend((0..self.local.len()).map(LocalPkgsIndex));
}
}
fn force_all(&mut self) {
self.filter.clear();
self.filter
.extend((0..self.local.len()).map(LocalPkgsIndex));
}
fn update_search(&mut self, pattern: &str) {
struct Ele<'s>(&'s str, LocalPkgsIndex);
impl AsRef<str> for Ele<'_> {
fn as_ref(&self) -> &str {
self.0
}
}
impl From<Ele<'_>> for LocalPkgsIndex {
fn from(value: Ele<'_>) -> Self {
value.1
}
}
if let Some(fuzzy) = &mut self.fuzzy {
fuzzy.parse(pattern);
let iter = self.local.iter().enumerate();
let iter = iter.map(|(idx, pkg)| Ele(pkg.name(), LocalPkgsIndex(idx)));
fuzzy.match_list(iter, &mut self.filter);
self.fill_filter();
}
}
}
#[repr(transparent)]
#[derive(Clone, Copy)]
pub(super) struct LocalPkgsIndex(usize);
impl std::ops::Deref for PkgLists {
type Target = [LocalPkgsIndex];
fn deref(&self) -> &Self::Target {
&self.filter
}
}
impl LineState for LocalPkgsIndex {
type State = usize;
fn state(&self) -> Self::State {
self.0
}
fn is_identical(&self, state: &Self::State) -> bool {
self.0 == *state
}
}
#[derive(Default)]
pub struct Registry {
pub inner: Scroll<PkgLists>,
border: Surround,
}
impl Registry {
pub fn new_local(fuzzy: Fuzzy) -> Self {
Registry {
inner: Scroll {
lines: PkgLists::new_local(fuzzy),
..Default::default()
},
..Default::default()
}
}
pub fn set_area(&mut self, border: Surround) {
self.inner.area = border.inner();
self.border = border;
}
pub fn scroll_text(&mut self) -> &mut Scroll<PkgLists> {
&mut self.inner
}
pub fn render(&self, buf: &mut Buffer, current: bool) {
self.border.render(buf);
let text = &self.inner;
let Some(lines) = text.visible_lines() else {
return;
};
let Rect { x, mut y, .. } = text.area;
let width = text.area.width as usize;
let pkgs = &text.lines.local;
if current && text.get_line_of_current_cursor().is_some() {
let row = text.area.y + text.cursor.y;
for col in x..text.area.width + x {
buf.get_mut(col, row).set_bg(BG_CURSOR_LINE);
}
}
let mut start = text.start + 1;
for line in lines {
let pkg = &pkgs[line.0];
let [(name, style_name), (ver, style_ver)] = pkg.styled_name_ver();
let num = xformat!("{start:02}. ");
let line = [
(&*num, style_name),
(name, style_name),
(" v", style_ver),
(ver, style_ver),
];
render_line(line, buf, x, y, width);
y += 1;
start += 1;
}
let text = xformat!(
" Got {} / Total {} ",
self.inner.total_len(),
self.inner.lines.local.len()
);
self.border.render_only_bottom_right_text(buf, &text);
}
pub fn update_search(&mut self, pattern: &str) {
self.inner.lines.update_search(pattern);
self.inner.start = 0;
self.set_cursor();
}
pub fn clear_and_reset(&mut self) {
self.inner.lines.force_all();
self.inner.start = 0;
self.set_cursor();
}
fn set_cursor(&mut self) {
if !self.inner.check_if_can_return_to_previous_cursor() {
self.inner.cursor.y = 0;
}
}
pub fn get_pkg(&self, y: Option<u16>) -> Option<PkgInfo> {
let pkgs = &self.inner.lines.local;
y.map_or_else(
|| self.inner.get_line_of_current_cursor(),
|y| self.inner.get_line_on_screen(y),
)
.map(|idx| pkgs[idx.0].clone())
}
pub fn get_current_pkg(&self) -> Option<(&str, &str)> {
if let Some(idx) = self.inner.get_line_of_current_cursor().map(|id| id.0) {
if let Some(pkg) = self.inner.lines.local.get(idx) {
return Some((pkg.name(), pkg.ver()));
}
}
None
}
}