#![allow(stable_features)] #![feature(os_str_display)] #![feature(str_as_str)] #![feature(let_chains)] use std::sync::{Arc, RwLock}; use std::path::{Path, PathBuf}; use std::env::{current_dir, set_current_dir}; use std::thread::{sleep, spawn, JoinHandle}; use std::time::Duration; use tengri::{input::*, output::*, tui::*}; use crate::crossterm::event::{Event, KeyEvent, KeyCode, KeyModifiers, KeyEventState, KeyEventKind}; use clap::{arg, command, value_parser}; use walkdir::WalkDir; use xxhash_rust::xxh3::xxh3_64; mod keys; mod view; use self::view::*; mod model; pub(crate) use self::model::*; mod constants; pub(crate) use self::constants::*; pub(crate) type Usually = std::result::Result>; pub(crate) type Perhaps = Usually>; fn cli () -> clap::Command { command!() .arg(arg!([path] "Path to root directory") .value_parser(value_parser!(PathBuf))) .arg(arg!(-j --threads "Number of indexing threads") .required(false) .value_parser(value_parser!(usize))) .arg(arg!(-c --check "Only index content, don't open editor") .required(false) .value_parser(value_parser!(bool))) } fn main () -> Usually<()> { let args = cli().get_matches(); let path = if let Some(path) = args.get_one::("path") { path.into() } else { current_dir()? }; set_current_dir(&path)?; let threads = args.get_one::("threads").map(|x|*x).unwrap_or(4); let results = collect(&path, threads)?; if let Ok(results) = Arc::try_unwrap(results) { let mut results = results.into_inner()?; results.sort(); let state = Arc::new(RwLock::new(Taggart::new(&path, results)?)); if let Some(true) = args.get_one::("check") { return Ok(()) } Tui::new()?.run(&state) } else { panic!("read did not finish") } } fn collect (root: &impl AsRef, thread_count: usize) -> Usually>>> { let results = Arc::new(RwLock::new(vec![])); let entries = Arc::new(RwLock::new(WalkDir::new(&root).into_iter() .filter_entry(|e|!e.file_name().to_str().map(|s|s.starts_with(".")).unwrap_or(false)))); let mut threads: Vec> = vec![]; for thread_id in (0..thread_count).map(|x|x+1) { threads.push({ let root = root.as_ref().to_path_buf().clone(); let results = results.clone(); let entries = entries.clone(); spawn(move || loop { let (path, depth) = { let entry = entries.write().unwrap().next(); if let Some(entry) = entry { let entry = entry.expect("failed to walk entry"); let path = entry.path().to_path_buf(); let depth = entry.depth(); (path, depth) } else { break } }; if depth > 0 { let short_path = path.strip_prefix(root.as_path()) .expect("failed to strip prefix"); println!("(thread {thread_id}) {}", short_path.display()); if let Ok(Some(entry)) = Entry::new(&root, &path, depth) { results.write().unwrap().push(entry); } } }) }); } let timer = Duration::from_millis(100); let _ = spawn(move || loop { if threads.iter().all(|x|x.is_finished()) { break } else { sleep(timer) } }).join(); Ok(results) }