mirror of
https://github.com/tareqimbasher/cargo-seek.git
synced 2026-01-09 07:52:41 +08:00
more cleanup
This commit is contained in:
parent
4ae389f29e
commit
649ca8c5e8
12
Cargo.toml
12
Cargo.toml
@ -7,6 +7,12 @@ authors = ["Tareq Imbasher <https://github.com/tareqimbasher"]
|
||||
build = "build.rs"
|
||||
repository = "https://github.com/tareqimbasher/seekr"
|
||||
|
||||
[profile.release]
|
||||
opt-level = "z" # Optimize for size.
|
||||
strip = true # Automatically strip symbols from the binary.
|
||||
lto = true
|
||||
panic = "abort"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
@ -58,9 +64,3 @@ async-trait = "0.1"
|
||||
[build-dependencies]
|
||||
anyhow = "1.0.86"
|
||||
vergen-gix = { version = "1.0.2", features = ["build", "cargo"] }
|
||||
|
||||
[profile.release]
|
||||
opt-level = "z" # Optimize for size.
|
||||
strip = true # Automatically strip symbols from the binary.
|
||||
lto = true
|
||||
panic = "abort"
|
||||
2
rustfmt.toml
Normal file
2
rustfmt.toml
Normal file
@ -0,0 +1,2 @@
|
||||
edition = "2021"
|
||||
#style_edition = "2021"
|
||||
@ -1,10 +1,8 @@
|
||||
use crate::components::{Focusable, StatusDuration, StatusLevel};
|
||||
use crate::search::{Scope, SearchResults, Sort};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::Display;
|
||||
|
||||
use crate::components::home::enums::Focusable;
|
||||
use crate::components::status_bar::{StatusDuration, StatusLevel};
|
||||
use crate::search::{Scope, SearchResults, Sort};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Display, Serialize, Deserialize)]
|
||||
pub enum Action {
|
||||
Tick,
|
||||
|
||||
19
src/app.rs
19
src/app.rs
@ -5,29 +5,22 @@ use std::sync::Arc;
|
||||
use tokio::sync::{mpsc, RwLock};
|
||||
use tracing::{debug, info};
|
||||
|
||||
use crate::action::CargoAction;
|
||||
use crate::action::{Action, CargoAction};
|
||||
use crate::cargo::{add, install, remove, uninstall, CargoEnv};
|
||||
use crate::components::status_bar::StatusLevel;
|
||||
use crate::components::{AppId, Component, FpsCounter, Home, Settings, StatusBar, StatusLevel};
|
||||
use crate::config::Config;
|
||||
use crate::errors::{AppError, AppResult};
|
||||
use crate::{
|
||||
action::Action,
|
||||
components::{
|
||||
app_id::AppId, fps::FpsCounter, home::Home, settings::Settings, status_bar::StatusBar,
|
||||
Component,
|
||||
},
|
||||
config::Config,
|
||||
tui::{Event, Tui},
|
||||
};
|
||||
use crate::tui::{Event, Tui};
|
||||
|
||||
pub struct App {
|
||||
cargo_env: Arc<RwLock<CargoEnv>>,
|
||||
config: Config,
|
||||
tick_rate: f64,
|
||||
frame_rate: f64,
|
||||
mode: Mode,
|
||||
components: Vec<Box<dyn Component>>,
|
||||
should_quit: bool,
|
||||
should_suspend: bool,
|
||||
mode: Mode,
|
||||
last_tick_key_events: Vec<KeyEvent>,
|
||||
action_tx: mpsc::UnboundedSender<Action>,
|
||||
action_rx: mpsc::UnboundedReceiver<Action>,
|
||||
@ -49,9 +42,9 @@ impl App {
|
||||
let cargo_env = Arc::new(RwLock::new(CargoEnv::new(root)));
|
||||
|
||||
let mut components: Vec<Box<dyn Component>> = vec![
|
||||
Box::new(AppId::new()),
|
||||
Box::new(Home::new(Arc::clone(&cargo_env), action_tx.clone())?),
|
||||
Box::new(Settings::new()),
|
||||
Box::new(AppId::new()),
|
||||
Box::new(StatusBar::new(action_tx.clone())),
|
||||
];
|
||||
|
||||
|
||||
@ -1,47 +1,43 @@
|
||||
use std::collections::{HashMap};
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::cargo::{get_installed_binaries, InstalledBinary, Project};
|
||||
use crate::errors::AppResult;
|
||||
|
||||
pub struct CargoEnv {
|
||||
pub root: Option<PathBuf>,
|
||||
pub current_dir: Option<PathBuf>,
|
||||
pub project: Option<Project>,
|
||||
pub installed: Vec<InstalledBinary>,
|
||||
installed_map: HashMap<String, String>,
|
||||
installed_versions: HashMap<String, String>,
|
||||
}
|
||||
|
||||
/// The current cargo environment (installed binaries and current project, if any)
|
||||
impl CargoEnv {
|
||||
pub fn new(root: Option<PathBuf>) -> Self {
|
||||
let project = match root.clone() {
|
||||
Some(p) => Project::from(p),
|
||||
None => None,
|
||||
};
|
||||
|
||||
pub fn new(current_dir: Option<PathBuf>) -> Self {
|
||||
Self {
|
||||
root,
|
||||
project,
|
||||
current_dir,
|
||||
project: None,
|
||||
installed: Vec::new(),
|
||||
installed_map: HashMap::new(),
|
||||
installed_versions: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads the current Cargo environment and updates the internal state.
|
||||
pub fn read(&mut self) -> AppResult<()> {
|
||||
if let Some(root) = &self.root {
|
||||
if self.project.is_none() {
|
||||
self.project = Project::from(root.clone());
|
||||
}
|
||||
}
|
||||
|
||||
self.installed = get_installed_binaries().ok().unwrap_or_default();
|
||||
|
||||
self.installed_map = self.installed
|
||||
self.installed_versions = self
|
||||
.installed
|
||||
.iter()
|
||||
.map(|bin| (bin.name.clone(), bin.version.clone()))
|
||||
.collect();
|
||||
|
||||
if self.project.is_none() {
|
||||
if let Some(current_dir) = &self.current_dir {
|
||||
self.project = Project::from(current_dir);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(project) = self.project.as_mut() {
|
||||
project.read().ok();
|
||||
}
|
||||
@ -49,7 +45,8 @@ impl CargoEnv {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Gets the installed version of the given crate name, if any.
|
||||
pub fn get_installed_version(&self, name: &str) -> Option<String> {
|
||||
self.installed_map.get(name).cloned()
|
||||
self.installed_versions.get(name).cloned()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
mod cargo_env;
|
||||
mod api;
|
||||
mod cargo_env;
|
||||
mod project;
|
||||
|
||||
pub use api::*;
|
||||
pub use cargo_env::CargoEnv;
|
||||
pub use project::*;
|
||||
pub use api::*;
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use std::fs;
|
||||
use std::fs::DirEntry;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::cargo::{get_metadata, Package};
|
||||
use crate::errors::AppResult;
|
||||
use crate::errors::{AppError, AppResult};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
struct DependencyInfo {
|
||||
@ -20,46 +22,30 @@ pub struct Project {
|
||||
}
|
||||
|
||||
impl Project {
|
||||
pub fn from(path: PathBuf) -> Option<Project> {
|
||||
if !path.exists() || !path.is_dir() {
|
||||
pub fn from(path: &Path) -> Option<Project> {
|
||||
if !path.try_exists().ok().unwrap_or_default() || !path.is_dir() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let files = std::fs::read_dir(path);
|
||||
|
||||
if files.is_err() {
|
||||
return None;
|
||||
if let Ok(Some(manifest_file_path)) = find_project_manifest(path) {
|
||||
Some(Project {
|
||||
manifest_file_path,
|
||||
packages: Vec::new(),
|
||||
dependencies: HashMap::new(),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
||||
// Iterate over files and check if we have a match. Iteration was chosen because
|
||||
// checking if specific paths exists is error-prone. Ex: checking if "cargo.toml" exists
|
||||
// on Windows returns true when the file's name is called "Cargo.toml", this causes an
|
||||
// issue in that the cargo executable wants the exact file name.
|
||||
let manifest_file = files
|
||||
.unwrap()
|
||||
.find(|f| {
|
||||
if let Ok(file) = f {
|
||||
let file_name = file.file_name().to_str().unwrap_or_default().to_string();
|
||||
if file_name == "Cargo.toml" || file_name == "cargo.toml" {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
})?
|
||||
.ok();
|
||||
|
||||
manifest_file.as_ref()?;
|
||||
|
||||
let manifest_file_path = manifest_file.unwrap().path();
|
||||
|
||||
Some(Project {
|
||||
manifest_file_path,
|
||||
packages: Vec::new(),
|
||||
dependencies: HashMap::new(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Reads the current project and updates internal state.
|
||||
pub fn read(&mut self) -> AppResult<()> {
|
||||
if !self.manifest_file_path.exists() {
|
||||
return Err(AppError::Unknown(
|
||||
"Manifest file no longer exists".to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
let metadata = get_metadata(&self.manifest_file_path)?;
|
||||
|
||||
let packages = metadata.packages;
|
||||
@ -86,7 +72,42 @@ impl Project {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Gets the local project version of the given crate name, if any.
|
||||
pub fn get_local_version(&self, package_name: &str) -> Option<String> {
|
||||
self.dependencies.get(package_name).map(|dep| dep.version.clone())
|
||||
self.dependencies
|
||||
.get(package_name)
|
||||
.map(|dep| dep.version.clone())
|
||||
}
|
||||
}
|
||||
|
||||
fn find_project_manifest(starting_dir_path: &Path) -> AppResult<Option<PathBuf>> {
|
||||
let mut search_path = Some(starting_dir_path);
|
||||
let mut manifest_file: Option<DirEntry> = None;
|
||||
|
||||
while search_path.is_some() && manifest_file.is_none() {
|
||||
let path = search_path.unwrap();
|
||||
|
||||
let found = fs::read_dir(path)?.find(|f| {
|
||||
if let Ok(file) = f {
|
||||
let file_name = file.file_name().to_string_lossy().to_lowercase();
|
||||
if file_name == "cargo.toml" {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
});
|
||||
|
||||
if let Some(found) = found {
|
||||
manifest_file = Some(found?);
|
||||
break;
|
||||
}
|
||||
|
||||
search_path = path.parent();
|
||||
}
|
||||
|
||||
if let Some(manifest_file) = manifest_file {
|
||||
Ok(Some(manifest_file.path()))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
use std::time::Instant;
|
||||
use async_trait::async_trait;
|
||||
use ratatui::{
|
||||
layout::{Constraint, Layout, Rect},
|
||||
@ -7,6 +6,7 @@ use ratatui::{
|
||||
widgets::Paragraph,
|
||||
Frame,
|
||||
};
|
||||
use std::time::Instant;
|
||||
|
||||
use super::Component;
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ use std::sync::Arc;
|
||||
use std::{fs, io::Write, process::Command};
|
||||
|
||||
use crate::action::{Action, SearchAction};
|
||||
use crate::components::home::enums::Focusable;
|
||||
use crate::components::home::focusable::Focusable;
|
||||
use crate::components::home::Home;
|
||||
use crate::components::status_bar::{StatusDuration, StatusLevel};
|
||||
use crate::errors::AppResult;
|
||||
@ -287,7 +287,9 @@ pub async fn handle_action(
|
||||
.search_results
|
||||
.as_ref()
|
||||
.and_then(|results| results.get_selected())
|
||||
.and_then(|krate| Url::parse(format!("https://crates.io/crates/{}", krate.id).as_str()).ok())
|
||||
.and_then(|krate| {
|
||||
Url::parse(format!("https://crates.io/crates/{}", krate.id).as_str()).ok()
|
||||
})
|
||||
{
|
||||
open::that(url.to_string())?;
|
||||
}
|
||||
@ -297,7 +299,9 @@ pub async fn handle_action(
|
||||
.search_results
|
||||
.as_ref()
|
||||
.and_then(|results| results.get_selected())
|
||||
.and_then(|krate| Url::parse(format!("https://lib.rs/crates/{}", krate.id).as_str()).ok())
|
||||
.and_then(|krate| {
|
||||
Url::parse(format!("https://lib.rs/crates/{}", krate.id).as_str()).ok()
|
||||
})
|
||||
{
|
||||
open::that(url.to_string())?;
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use chrono::Utc;
|
||||
use chrono::Utc;
|
||||
use ratatui::prelude::{Color, Line, Text};
|
||||
use ratatui::widgets::block::{Position, Title};
|
||||
use ratatui::widgets::{Block, Borders, List, ListItem, Padding, Paragraph, Wrap};
|
||||
@ -9,7 +9,7 @@ use ratatui::{
|
||||
};
|
||||
|
||||
use crate::app::Mode;
|
||||
use crate::components::home::enums::Focusable;
|
||||
use crate::components::home::focusable::Focusable;
|
||||
use crate::components::home::Home;
|
||||
use crate::components::ux::{Button, State, GRAY, ORANGE, PURPLE, YELLOW};
|
||||
use crate::components::Component;
|
||||
@ -148,8 +148,11 @@ fn render_results(home: &mut Home, frame: &mut Frame, area: Rect) -> AppResult<(
|
||||
let name = i.name.to_string();
|
||||
let version = i.version.to_string();
|
||||
|
||||
let mut white_space =
|
||||
area.width as i32 - name.len() as i32 - tag.len() as i32 - VERSION_PADDING as i32 - correction;
|
||||
let mut white_space = area.width as i32
|
||||
- name.len() as i32
|
||||
- tag.len() as i32
|
||||
- VERSION_PADDING as i32
|
||||
- correction;
|
||||
if white_space < 1 {
|
||||
white_space = 1;
|
||||
}
|
||||
@ -169,10 +172,7 @@ fn render_results(home: &mut Home, frame: &mut Frame, area: Rect) -> AppResult<(
|
||||
Style::default()
|
||||
};
|
||||
|
||||
ListItem::new(Line::from(vec![
|
||||
tag.bold(),
|
||||
line.into()
|
||||
]).set_style(style))
|
||||
ListItem::new(Line::from(vec![tag.bold(), line.into()]).set_style(style))
|
||||
})
|
||||
.collect();
|
||||
|
||||
@ -268,7 +268,6 @@ fn render_usage(home: &mut Home, frame: &mut Frame, area: Rect) -> AppResult<()>
|
||||
"installed".bold(),
|
||||
]),
|
||||
Line::default(),
|
||||
|
||||
Line::from(vec!["SEARCH".bold()]),
|
||||
Line::from(vec![
|
||||
format!("{:<PAD$}", "Enter:").set_style(prop_style),
|
||||
|
||||
@ -2,8 +2,6 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::iter::Cycle;
|
||||
|
||||
use crate::search::Sort;
|
||||
|
||||
#[derive(Default, PartialEq, Clone, Debug, Eq, Sequence, Serialize, Deserialize)]
|
||||
pub enum Focusable {
|
||||
Usage,
|
||||
@ -32,20 +30,6 @@ impl Focusable {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Sort {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let output = match self {
|
||||
Sort::Relevance => "Relevance",
|
||||
Sort::Name => "Name",
|
||||
Sort::Downloads => "Downloads",
|
||||
Sort::RecentDownloads => "Recent Downloads",
|
||||
Sort::RecentlyUpdated => "Recently Updated",
|
||||
Sort::NewlyAdded => "Newly Added",
|
||||
};
|
||||
write!(f, "{}", output)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_results_or_details_focused(focused: &Focusable) -> bool {
|
||||
*focused == Focusable::Results
|
||||
|| *focused == Focusable::DocsButton
|
||||
@ -1,8 +1,8 @@
|
||||
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
||||
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
||||
use tui_input::backend::crossterm::EventHandler;
|
||||
|
||||
use crate::action::{Action, CargoAction, SearchAction};
|
||||
use crate::components::home::enums::{is_results_or_details_focused, Focusable};
|
||||
use crate::components::home::focusable::{is_results_or_details_focused, Focusable};
|
||||
use crate::components::home::Home;
|
||||
use crate::components::Component;
|
||||
use crate::errors::AppResult;
|
||||
@ -179,8 +179,10 @@ pub fn handle_key(home: &mut Home, key: KeyEvent) -> AppResult<Option<Action>> {
|
||||
|
||||
if home.focused == Focusable::Search {
|
||||
match key.code {
|
||||
KeyCode::Down => if home.search_results.is_some() {
|
||||
return Ok(Some(Action::Focus(Focusable::Results)));
|
||||
KeyCode::Down => {
|
||||
if home.search_results.is_some() {
|
||||
return Ok(Some(Action::Focus(Focusable::Results)));
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// Send to input box
|
||||
@ -241,10 +243,8 @@ pub fn handle_key(home: &mut Home, key: KeyEvent) -> AppResult<Option<Action>> {
|
||||
}
|
||||
}
|
||||
|
||||
if is_results_or_details_focused(&home.focused) {
|
||||
if ctrl && key.code == KeyCode::Char('d') {
|
||||
return Ok(Some(Action::OpenDocs));
|
||||
}
|
||||
if is_results_or_details_focused(&home.focused) && ctrl && key.code == KeyCode::Char('d') {
|
||||
return Ok(Some(Action::OpenDocs));
|
||||
}
|
||||
|
||||
if home.focused == Focusable::Sort {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
mod action_handler;
|
||||
mod action_handler;
|
||||
mod draw;
|
||||
pub mod enums;
|
||||
mod focusable;
|
||||
mod key_handler;
|
||||
|
||||
use super::Component;
|
||||
@ -16,7 +16,6 @@ use tui_input::Input;
|
||||
use crate::cargo::CargoEnv;
|
||||
use crate::components::home::action_handler::handle_action;
|
||||
use crate::components::home::draw::render;
|
||||
use crate::components::home::enums::Focusable;
|
||||
use crate::components::home::key_handler::handle_key;
|
||||
use crate::components::status_bar::StatusLevel;
|
||||
use crate::components::ux::Dropdown;
|
||||
@ -28,6 +27,7 @@ use crate::{
|
||||
app::Mode,
|
||||
config::Config,
|
||||
};
|
||||
pub use focusable::Focusable;
|
||||
|
||||
pub struct Home {
|
||||
cargo_env: Arc<RwLock<CargoEnv>>,
|
||||
@ -267,39 +267,6 @@ mod tests {
|
||||
assert_eq!(home.focused, Focusable::Results);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_focus_next_action() {
|
||||
let (home, _) = execute_update(Action::FocusNext).await;
|
||||
assert_eq!(home.focused, Focusable::Results);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_focus_next_action_when_last_is_focused() {
|
||||
let (mut home, mut tui) = execute_update(Action::Focus(Focusable::DocsButton)).await;
|
||||
|
||||
execute_update_with_home(&mut home, &mut tui, Action::FocusNext).await;
|
||||
|
||||
assert_eq!(home.focused, Focusable::Search);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_focus_previous_action() {
|
||||
let (mut home, mut tui) = execute_update(Action::Focus(Focusable::DocsButton)).await;
|
||||
|
||||
execute_update_with_home(&mut home, &mut tui, Action::FocusPrevious).await;
|
||||
|
||||
assert_eq!(home.focused, Focusable::ReadmeButton);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_focus_previous_action_when_first_is_focused() {
|
||||
let (mut home, mut tui) = execute_update(Action::Focus(Focusable::Search)).await;
|
||||
|
||||
execute_update_with_home(&mut home, &mut tui, Action::FocusPrevious).await;
|
||||
|
||||
assert_eq!(home.focused, Focusable::DocsButton);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_search_clear_action() {
|
||||
let (mut home, _) = get_home_and_tui();
|
||||
|
||||
@ -1,14 +1,20 @@
|
||||
pub mod app_id;
|
||||
pub mod fps;
|
||||
pub mod home;
|
||||
pub mod settings;
|
||||
pub mod status_bar;
|
||||
pub mod ux;
|
||||
mod app_id;
|
||||
mod fps;
|
||||
mod home;
|
||||
mod settings;
|
||||
mod status_bar;
|
||||
mod ux;
|
||||
|
||||
use std::any::Any;
|
||||
use async_trait::async_trait;
|
||||
use crossterm::event::{KeyEvent, MouseEvent};
|
||||
use ratatui::{layout::Rect, Frame};
|
||||
use std::any::Any;
|
||||
|
||||
pub use app_id::*;
|
||||
pub use fps::FpsCounter;
|
||||
pub use home::*;
|
||||
pub use settings::Settings;
|
||||
pub use status_bar::{StatusBar, StatusDuration, StatusLevel};
|
||||
|
||||
use crate::app::Mode;
|
||||
use crate::errors::AppResult;
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
use std::cmp::PartialEq;
|
||||
use async_trait::async_trait;
|
||||
use ratatui::layout::{Alignment, Constraint, Layout, Rect};
|
||||
use ratatui::style::{Styled, Stylize};
|
||||
@ -6,6 +5,7 @@ use ratatui::text::{Line, Text};
|
||||
use ratatui::widgets::Paragraph;
|
||||
use ratatui::Frame;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cmp::PartialEq;
|
||||
use strum::Display;
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
use tokio::sync::oneshot;
|
||||
@ -108,7 +108,8 @@ impl StatusBar {
|
||||
if cancel_rx.try_recv().is_ok() {
|
||||
return;
|
||||
}
|
||||
tx.send(Action::UpdateStatus(StatusLevel::Info, "ready".into())).unwrap();
|
||||
tx.send(Action::UpdateStatus(StatusLevel::Info, "ready".into()))
|
||||
.unwrap();
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -213,8 +214,7 @@ impl Component for StatusBar {
|
||||
}
|
||||
|
||||
frame.render_widget(
|
||||
Paragraph::new(Text::from(Line::from(text)))
|
||||
.alignment(Alignment::Right),
|
||||
Paragraph::new(Text::from(Line::from(text))).alignment(Alignment::Right),
|
||||
right,
|
||||
);
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use async_trait::async_trait;
|
||||
use async_trait::async_trait;
|
||||
use crossterm::event::{KeyCode, KeyEvent};
|
||||
use ratatui::layout::{Alignment, Constraint, Layout, Rect};
|
||||
use ratatui::prelude::Stylize;
|
||||
@ -23,7 +23,11 @@ pub struct Dropdown<T> {
|
||||
}
|
||||
|
||||
impl<T: IntoEnumIterator + Default + Clone> Dropdown<T> {
|
||||
pub fn new(header: String, selected_ix: usize, on_enter: Box<dyn Fn(&T) + Send + Sync>) -> Self {
|
||||
pub fn new(
|
||||
header: String,
|
||||
selected_ix: usize,
|
||||
on_enter: Box<dyn Fn(&T) + Send + Sync>,
|
||||
) -> Self {
|
||||
Dropdown {
|
||||
header,
|
||||
config: Config::default(),
|
||||
|
||||
@ -23,10 +23,10 @@ pub enum AppError {
|
||||
Join(#[from] tokio::task::JoinError),
|
||||
#[error("Error: {0}")]
|
||||
Unknown(String),
|
||||
|
||||
|
||||
// Custom
|
||||
#[error("{0}")]
|
||||
Cargo(String)
|
||||
Cargo(String),
|
||||
}
|
||||
|
||||
/// Catch-all: if an error that implements std::error::Error occurs
|
||||
|
||||
@ -2,22 +2,21 @@ mod action;
|
||||
mod app;
|
||||
mod cargo;
|
||||
mod cli;
|
||||
mod components;
|
||||
mod config;
|
||||
mod errors;
|
||||
mod logging;
|
||||
mod search;
|
||||
mod tui;
|
||||
mod util;
|
||||
mod components;
|
||||
|
||||
use clap::Parser;
|
||||
use cli::Cli;
|
||||
use color_eyre::Result;
|
||||
|
||||
use crate::app::App;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
async fn main() -> color_eyre::Result<()> {
|
||||
errors::init()?;
|
||||
logging::init()?;
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
@ -22,4 +22,3 @@ pub struct Crate {
|
||||
#[serde(default)]
|
||||
pub installed_version: Option<String>,
|
||||
}
|
||||
|
||||
|
||||
@ -28,7 +28,7 @@ impl CrateSearchManager {
|
||||
let client = AsyncClient::with_http_client(
|
||||
Client::builder()
|
||||
.default_headers(headers)
|
||||
.timeout(Duration::from_secs(15))
|
||||
.timeout(Duration::from_secs(10))
|
||||
.build()?,
|
||||
Duration::from_millis(1000),
|
||||
);
|
||||
@ -129,7 +129,6 @@ impl CrateSearchManager {
|
||||
}
|
||||
|
||||
// Back-fill is_local and is_installed for search results that don't have it
|
||||
// todo optimize
|
||||
Self::update_results(&mut search_results, &cargo_env);
|
||||
|
||||
tx.send(Action::Search(SearchAction::Render(search_results)))
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
mod cargo_crate;
|
||||
mod cargo_crate;
|
||||
mod crate_search_manager;
|
||||
mod search_results;
|
||||
mod search_options;
|
||||
mod search_results;
|
||||
|
||||
pub use cargo_crate::*;
|
||||
pub use crate_search_manager::*;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use enum_iterator::Sequence;
|
||||
use enum_iterator::Sequence;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum_macros::{Display, EnumIter};
|
||||
|
||||
@ -24,6 +24,20 @@ pub enum Sort {
|
||||
NewlyAdded,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Sort {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let output = match self {
|
||||
Sort::Relevance => "Relevance",
|
||||
Sort::Name => "Name",
|
||||
Sort::Downloads => "Downloads",
|
||||
Sort::RecentDownloads => "Recent Downloads",
|
||||
Sort::RecentlyUpdated => "Recently Updated",
|
||||
Sort::NewlyAdded => "Newly Added",
|
||||
};
|
||||
write!(f, "{}", output)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct SearchOptions {
|
||||
pub term: Option<String>,
|
||||
@ -31,4 +45,4 @@ pub struct SearchOptions {
|
||||
pub per_page: Option<usize>,
|
||||
pub sort: Sort,
|
||||
pub scope: Scope,
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use ratatui::widgets::ListState;
|
||||
use ratatui::widgets::ListState;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::search::Crate;
|
||||
@ -87,4 +87,4 @@ impl SearchResults {
|
||||
pub fn list_state(&mut self) -> &mut ListState {
|
||||
&mut self.state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user