use crate::structs::Crate;
use serde_derive::Deserialize;
use std::{
cmp,
default::Default,
fmt::{self, Display},
iter, str,
};
use termion::{clear, cursor};
const CRATE_ROW_OVERHEAD: u16 = 3 * 3;
#[derive(Deserialize, Clone)]
pub struct Dimension {
pub width: u16,
pub height: u16,
}
impl Dimension {
pub fn loose_heigth(mut self, h: u16) -> Dimension {
self.height -= h;
self
}
}
impl Default for Dimension {
fn default() -> Dimension {
#[cfg(windows)]
fn imp() -> Dimension {
Dimension {
width: 80,
height: 20,
}
}
#[cfg(unix)]
fn imp() -> Dimension {
use termion::terminal_size;
let (mw, mh) = terminal_size().unwrap_or((80, 20));
Dimension {
width: mw,
height: mh,
}
}
imp()
}
}
fn sanitize(input: &str) -> String {
input
.chars()
.map(|c| if c == '\n' { ' ' } else { c })
.collect()
}
#[derive(Deserialize, Default)]
pub struct Meta {
pub total: u32,
pub term: Option<String>,
pub dimension: Option<Dimension>,
}
struct CrateRow<'a>(&'a Crate, &'a (usize, usize, usize, usize));
impl<'a> Display for CrateRow<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let krate = &self.0;
let &(nw, dw, vw, dlw) = self.1;
if self.0.name.is_empty() {
write!(f, "{clear}", clear = clear::AfterCursor)
} else {
write!(
f,
"{name:nw$.nw$} | {desc:dw$.dw$} | {downloads:dlw$.dlw$} | {version:vw$.vw$}",
name = krate.name,
desc = krate
.description
.as_ref()
.map(|d| sanitize(d))
.unwrap_or_else(String::new),
downloads = krate.downloads,
version = krate.max_version,
nw = nw,
dw = dw,
dlw = dlw,
vw = vw
)
}
}
}
pub fn desired_table_widths(items: &[Crate], dim: &Dimension) -> (usize, usize, usize, usize) {
desired_string_widths(items, dim.width - CRATE_ROW_OVERHEAD)
}
fn desired_string_widths(items: &[Crate], max_width: u16) -> (usize, usize, usize, usize) {
let (nw, dw, vw, dlw) =
items
.iter()
.fold((0, 0, 0, 0), |(mut nw, mut dw, mut vw, mut dlw), c| {
if c.name.len() > nw {
nw = c.name.len();
}
let dlen = c
.description
.as_ref()
.map(|s| sanitize(&s))
.unwrap_or_else(String::new)
.len();
if dlen > dw {
dw = dlen;
}
if c.max_version.len() > vw {
vw = c.max_version.len();
}
let dllen = f64::log10(c.downloads as f64) as usize + 1;
if dllen > dlw {
dlw = dllen;
}
(nw, dw, vw, dlw)
});
let w = {
let mut prio_widths = [dw, vw, dlw, nw];
let max_width = max_width as usize;
for (i, &w) in prio_widths.clone().iter().enumerate() {
let total_width: usize = prio_widths[i..].iter().sum();
prio_widths[i] = if total_width > max_width {
w.saturating_sub(total_width - max_width)
} else {
w
};
if total_width < max_width {
prio_widths[i] = w.saturating_add(max_width - total_width);
break;
}
}
prio_widths
};
(w[3], w[0], w[1], w[2])
}
#[derive(Deserialize, Default)]
pub struct SearchResult {
pub crates: Vec<Crate>,
pub meta: Meta,
}
impl SearchResult {
pub fn with_dimension(dim: Dimension) -> SearchResult {
SearchResult {
meta: Meta {
dimension: Some(dim),
..Default::default()
},
..Default::default()
}
}
pub fn from_data(buf: &[u8], dim: Dimension) -> Result<SearchResult, serde_json::Error> {
serde_json::from_slice(buf).map(|mut v: SearchResult| {
v.meta.dimension = Some(dim);
v
})
}
}
impl Display for SearchResult {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let dim = self.meta.dimension.as_ref().expect("dimension to be set");
let max_width = desired_table_widths(&self.crates, &dim);
for krate in self
.crates
.iter()
.cloned()
.chain(iter::repeat(Crate::default()))
.take(dim.height as usize)
{
let krate = format!("{}", CrateRow(&krate, &max_width));
write!(
f,
"{clear}{:.max$}{down}{left}",
krate,
clear = clear::CurrentLine,
down = cursor::Down(1),
left = cursor::Left(cmp::max(krate.len(), dim.width as usize) as u16),
max = dim.width as usize
)?;
}
Ok(())
}
}
#[derive(Clone)]
pub enum Command {
Search(String),
ShowLast,
Open { force: bool, number: usize },
DrawIndices,
Clear,
}
#[derive(Clone, Copy)]
pub enum Mode {
Searching,
Opening,
}
impl Display for Mode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}",
match *self {
Mode::Searching => "search",
Mode::Opening => "open by number",
}
)
}
}
impl Default for Mode {
fn default() -> Self {
Mode::Searching
}
}
#[derive(Default)]
pub struct State {
pub number: String,
pub term: String,
pub mode: Mode,
}
impl State {
pub fn prompt(&self) -> &str {
match self.mode {
Mode::Searching => &self.term,
Mode::Opening => &self.number,
}
}
}
pub struct Indexed<'a>(pub &'a SearchResult);
impl<'a> Display for Indexed<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let dim = self.0.meta.dimension.clone().unwrap_or_default();
let (nw, ..) = desired_table_widths(&self.0.crates, &dim);
let center = (nw + 1) as u16;
write!(
f,
"{hide}{align}",
hide = cursor::Hide,
align = cursor::Right(center)
)?;
for i in (0..self.0.crates.len()).take(dim.height as usize) {
let rendered = format!("|#{:3} #|", i);
write!(
f,
"{}{left}{down}",
rendered,
left = cursor::Left(rendered.len() as u16),
down = cursor::Down(1)
)?
}
Ok(())
}
}