From 590fe14ee520147e2318d4b1f57c97236e95f963 Mon Sep 17 00:00:00 2001 From: alexpasmantier Date: Sat, 19 Oct 2024 00:30:43 +0200 Subject: [PATCH] new channel git-repos and previews --- Cargo.lock | 7 + Cargo.toml | 11 +- TODO.md | 1 + build.rs | 4 +- crates/television/action.rs | 2 +- crates/television/app.rs | 6 +- crates/television/channels.rs | 4 +- crates/television/channels/alias.rs | 4 +- crates/television/channels/env.rs | 4 +- crates/television/channels/files.rs | 10 +- crates/television/channels/git_repos.rs | 161 ++++++++++++++++++++++ crates/television/channels/stdin.rs | 4 +- crates/television/channels/text.rs | 10 +- crates/television/cli.rs | 4 +- crates/television/config.rs | 22 +-- crates/television/entry.rs | 20 +-- crates/television/errors.rs | 2 +- crates/television/event.rs | 10 +- crates/television/fuzzy.rs | 4 +- crates/television/logging.rs | 2 +- crates/television/main.rs | 4 +- crates/television/previewers.rs | 31 +++-- crates/television/previewers/basic.rs | 6 +- crates/television/previewers/cache.rs | 14 +- crates/television/previewers/directory.rs | 69 +++++++--- crates/television/previewers/env.rs | 6 +- crates/television/previewers/files.rs | 10 +- crates/television/render.rs | 2 +- crates/television/television.rs | 48 +++---- crates/television/tui.rs | 16 +-- crates/television/ui.rs | 2 +- crates/television/ui/input.rs | 24 ++-- crates/television/ui/input/backend.rs | 2 +- crates/television/ui/layout.rs | 12 +- crates/television/ui/preview.rs | 8 +- crates/television/ui/results.rs | 2 +- crates/television/utils/files.rs | 21 ++- crates/television/utils/indices.rs | 2 +- crates/television/utils/strings.rs | 26 ++-- crates/television_derive/src/lib.rs | 4 +- 40 files changed, 397 insertions(+), 204 deletions(-) create mode 100644 crates/television/channels/git_repos.rs diff --git a/Cargo.lock b/Cargo.lock index 99b206b..ad917ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2459,6 +2459,7 @@ dependencies = [ "strum", "syntect", "television-derive", + "termtree", "tokio", "toml", "tracing", @@ -2500,6 +2501,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "termtree" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" + [[package]] name = "thiserror" version = "1.0.64" diff --git a/Cargo.toml b/Cargo.toml index f07a4dd..a0997e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,9 +22,7 @@ path = "crates/television/main.rs" name = "tv" [workspace] -members = [ - "crates/television_derive", -] +members = ["crates/television_derive"] [dependencies] television-derive = { version = "0.1.0", path = "crates/television_derive" } @@ -68,6 +66,7 @@ tracing-subscriber = { version = "0.3.18", features = ["env-filter", "serde"] } unicode-width = "0.2.0" human-panic = "2.0.2" pretty_assertions = "1.4.1" +termtree = "0.5.1" [build-dependencies] @@ -87,13 +86,7 @@ debug = true [profile.release] -opt-level = 3 -debug = "none" -strip = "symbols" -debug-assertions = false -overflow-checks = false lto = "thin" -panic = "abort" [target.'cfg(target_os = "macos")'.dependencies] crossterm = { version = "0.28.1", features = ["serde", "use-dev-tty"] } diff --git a/TODO.md b/TODO.md index 10a3412..dc5aee0 100644 --- a/TODO.md +++ b/TODO.md @@ -55,3 +55,4 @@ tv with itself? - [ ] have a keybind to send all current entries to stdout ... oorrrrr to another channel?? - [ ] action menu on the bottom: send to channel, copy to clipboard, send to stdout, ... maybe with tab to navigate between possible actions (defined per channel, not all channels can pipe to all channels) +- [ ] git repositories channel (crawl the filesystem for git repos) diff --git a/build.rs b/build.rs index 6c8d039..5eae933 100644 --- a/build.rs +++ b/build.rs @@ -1,7 +1,5 @@ use anyhow::Result; -use vergen_gix::{ - BuildBuilder, CargoBuilder, Emitter, GixBuilder, RustcBuilder, -}; +use vergen_gix::{BuildBuilder, CargoBuilder, Emitter, RustcBuilder}; fn main() -> Result<()> { let build = BuildBuilder::default().build_date(true).build()?; diff --git a/crates/television/action.rs b/crates/television/action.rs index fce6c41..ded6fe9 100644 --- a/crates/television/action.rs +++ b/crates/television/action.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; use strum::Display; #[derive(Debug, Clone, PartialEq, Eq, Display, Serialize, Deserialize)] -pub(crate) enum Action { +pub enum Action { // input actions AddInputChar(char), DeletePrevChar, diff --git a/crates/television/app.rs b/crates/television/app.rs index 476ec96..cc567c4 100644 --- a/crates/television/app.rs +++ b/crates/television/app.rs @@ -67,7 +67,7 @@ use crate::{ render::{render, RenderingTask}, }; -pub(crate) struct App { +pub struct App { config: Config, // maybe move these two into config instead of passing them // via the cli? @@ -87,7 +87,7 @@ pub(crate) struct App { #[derive( Default, Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, )] -pub(crate) enum Mode { +pub enum Mode { #[default] Help, Input, @@ -96,7 +96,7 @@ pub(crate) enum Mode { } impl App { - pub(crate) fn new( + pub fn new( channel: CliTvChannel, tick_rate: f64, frame_rate: f64, diff --git a/crates/television/channels.rs b/crates/television/channels.rs index a2a8b2b..3b950a0 100644 --- a/crates/television/channels.rs +++ b/crates/television/channels.rs @@ -4,6 +4,7 @@ use television_derive::CliChannel; mod alias; mod env; mod files; +mod git_repos; mod stdin; mod text; @@ -87,9 +88,10 @@ pub trait TelevisionChannel: Send { /// #[allow(dead_code, clippy::module_name_repetitions)] #[derive(CliChannel)] -pub(crate) enum AvailableChannels { +pub enum AvailableChannels { Env(env::Channel), Files(files::Channel), + GitRepos(git_repos::Channel), Text(text::Channel), Stdin(stdin::Channel), Alias(alias::Channel), diff --git a/crates/television/channels/alias.rs b/crates/television/channels/alias.rs index f34bc21..fa63b97 100644 --- a/crates/television/channels/alias.rs +++ b/crates/television/channels/alias.rs @@ -16,7 +16,7 @@ struct Alias { value: String, } -pub(crate) struct Channel { +pub struct Channel { matcher: Nucleo, last_pattern: String, file_icon: FileIcon, @@ -67,7 +67,7 @@ fn get_raw_aliases(shell: &str) -> Vec { } impl Channel { - pub(crate) fn new() -> Self { + pub fn new() -> Self { let raw_shell = get_current_shell().unwrap_or("bash".to_string()); let shell = raw_shell.split('/').last().unwrap(); debug!("Current shell: {}", shell); diff --git a/crates/television/channels/env.rs b/crates/television/channels/env.rs index d590d0d..d78f1bd 100644 --- a/crates/television/channels/env.rs +++ b/crates/television/channels/env.rs @@ -17,7 +17,7 @@ struct EnvVar { } #[allow(clippy::module_name_repetitions)] -pub(crate) struct Channel { +pub struct Channel { matcher: Nucleo, last_pattern: String, file_icon: FileIcon, @@ -30,7 +30,7 @@ const NUM_THREADS: usize = 1; const FILE_ICON_STR: &str = "config"; impl Channel { - pub(crate) fn new() -> Self { + pub fn new() -> Self { let matcher = Nucleo::new( Config::DEFAULT, Arc::new(|| {}), diff --git a/crates/television/channels/files.rs b/crates/television/channels/files.rs index 94d118a..7d1d3f7 100644 --- a/crates/television/channels/files.rs +++ b/crates/television/channels/files.rs @@ -15,18 +15,18 @@ use crate::{ }; use crate::{fuzzy::MATCHER, utils::strings::PRINTABLE_ASCII_THRESHOLD}; -pub(crate) struct Channel { +pub struct Channel { matcher: Nucleo, last_pattern: String, result_count: u32, total_count: u32, running: bool, - // TODO: cache results (to make deleting characters smoother) but like + // PERF: cache results (to make deleting characters smoother) but like // a shallow cache (maybe more like a stack actually? so we just pop result sets) } impl Channel { - pub(crate) fn new() -> Self { + pub fn new() -> Self { let matcher = Nucleo::new( Config::DEFAULT.match_paths(), Arc::new(|| {}), @@ -126,7 +126,8 @@ impl TelevisionChannel for Channel { #[allow(clippy::unused_async)] async fn load_files(path: PathBuf, injector: Injector) { let current_dir = std::env::current_dir().unwrap(); - let walker = walk_builder(&path, *DEFAULT_NUM_THREADS).build_parallel(); + let walker = + walk_builder(&path, *DEFAULT_NUM_THREADS, None).build_parallel(); walker.run(|| { let injector = injector.clone(); @@ -134,7 +135,6 @@ async fn load_files(path: PathBuf, injector: Injector) { Box::new(move |result| { if let Ok(entry) = result { if entry.file_type().unwrap().is_file() { - // Send the path via the async channel let file_name = entry.file_name(); if proportion_of_printable_ascii_characters( file_name.as_bytes(), diff --git a/crates/television/channels/git_repos.rs b/crates/television/channels/git_repos.rs new file mode 100644 index 0000000..d151998 --- /dev/null +++ b/crates/television/channels/git_repos.rs @@ -0,0 +1,161 @@ +use std::sync::Arc; + +use devicons::FileIcon; +use ignore::{overrides::OverrideBuilder, DirEntry}; +use nucleo::{ + pattern::{CaseMatching, Normalization}, + Config, Nucleo, +}; +use tracing::debug; + +use crate::{ + entry::Entry, + fuzzy::MATCHER, + previewers::PreviewType, + utils::files::{walk_builder, DEFAULT_NUM_THREADS}, +}; + +use crate::channels::TelevisionChannel; + +pub struct Channel { + matcher: Nucleo, + last_pattern: String, + result_count: u32, + total_count: u32, + running: bool, +} + +impl Channel { + pub fn new() -> Self { + let matcher = Nucleo::new( + Config::DEFAULT.match_paths(), + Arc::new(|| {}), + None, + 1, + ); + // start loading files in the background + tokio::spawn(crawl_for_repos( + std::env::home_dir().expect("Could not get home directory"), + matcher.injector(), + )); + Channel { + matcher, + last_pattern: String::new(), + result_count: 0, + total_count: 0, + running: false, + } + } + + const MATCHER_TICK_TIMEOUT: u64 = 10; +} + +impl TelevisionChannel for Channel { + fn find(&mut self, pattern: &str) { + if pattern != self.last_pattern { + self.matcher.pattern.reparse( + 0, + pattern, + CaseMatching::Smart, + Normalization::Smart, + pattern.starts_with(&self.last_pattern), + ); + self.last_pattern = pattern.to_string(); + } + } + + fn result_count(&self) -> u32 { + self.result_count + } + + fn total_count(&self) -> u32 { + self.total_count + } + + fn results(&mut self, num_entries: u32, offset: u32) -> Vec { + let status = self.matcher.tick(Self::MATCHER_TICK_TIMEOUT); + let snapshot = self.matcher.snapshot(); + if status.changed { + self.result_count = snapshot.matched_item_count(); + self.total_count = snapshot.item_count(); + } + self.running = status.running; + let mut indices = Vec::new(); + let mut matcher = MATCHER.lock(); + + snapshot + .matched_items( + offset + ..(num_entries + offset) + .min(snapshot.matched_item_count()), + ) + .map(move |item| { + snapshot.pattern().column_pattern(0).indices( + item.matcher_columns[0].slice(..), + &mut matcher, + &mut indices, + ); + indices.sort_unstable(); + indices.dedup(); + let indices = indices.drain(..); + + let path = item.matcher_columns[0].to_string(); + Entry::new(path.clone(), PreviewType::Directory) + .with_name_match_ranges( + indices.map(|i| (i, i + 1)).collect(), + ) + .with_icon(FileIcon::from(&path)) + }) + .collect() + } + + fn get_result(&self, index: u32) -> Option { + let snapshot = self.matcher.snapshot(); + snapshot.get_matched_item(index).map(|item| { + let path = item.matcher_columns[0].to_string(); + Entry::new(path.clone(), PreviewType::Directory) + .with_icon(FileIcon::from(&path)) + }) + } + + fn running(&self) -> bool { + self.running + } +} + +#[allow(clippy::unused_async)] +async fn crawl_for_repos( + starting_point: std::path::PathBuf, + injector: nucleo::Injector, +) { + let mut walker_overrides_builder = OverrideBuilder::new(&starting_point); + walker_overrides_builder.add(".git").unwrap(); + let walker = walk_builder( + &starting_point, + *DEFAULT_NUM_THREADS, + Some(walker_overrides_builder.build().unwrap()), + ) + .build_parallel(); + + walker.run(|| { + let injector = injector.clone(); + Box::new(move |result| { + if let Ok(entry) = result { + if entry.file_type().unwrap().is_dir() + && entry.path().ends_with(".git") + { + debug!("Found git repo: {:?}", entry.path()); + let _ = injector.push(entry, |e, cols| { + cols[0] = e + .path() + .parent() + .unwrap() + .to_string_lossy() + .into(); + }); + } + } + ignore::WalkState::Continue + }) + }); +} diff --git a/crates/television/channels/stdin.rs b/crates/television/channels/stdin.rs index 718331c..d1c7468 100644 --- a/crates/television/channels/stdin.rs +++ b/crates/television/channels/stdin.rs @@ -11,7 +11,7 @@ use crate::previewers::PreviewType; use super::TelevisionChannel; -pub(crate) struct Channel { +pub struct Channel { matcher: Nucleo, last_pattern: String, result_count: u32, @@ -23,7 +23,7 @@ pub(crate) struct Channel { const NUM_THREADS: usize = 2; impl Channel { - pub(crate) fn new() -> Self { + pub fn new() -> Self { let mut lines = Vec::new(); for line in std::io::stdin().lock().lines().map_while(Result::ok) { debug!("Read line: {:?}", line); diff --git a/crates/television/channels/text.rs b/crates/television/channels/text.rs index ae4d6dc..dd78d5f 100644 --- a/crates/television/channels/text.rs +++ b/crates/television/channels/text.rs @@ -15,7 +15,7 @@ use tracing::{debug, info}; use super::TelevisionChannel; use crate::previewers::PreviewType; use crate::utils::{ - files::{is_not_text, is_valid_utf8, walk_builder, DEFAULT_NUM_THREADS}, + files::{is_not_text, walk_builder, DEFAULT_NUM_THREADS}, strings::preprocess_line, }; use crate::{ @@ -41,7 +41,7 @@ impl CandidateLine { } #[allow(clippy::module_name_repetitions)] -pub(crate) struct Channel { +pub struct Channel { matcher: Nucleo, last_pattern: String, result_count: u32, @@ -50,7 +50,7 @@ pub(crate) struct Channel { } impl Channel { - pub(crate) fn new() -> Self { + pub fn new() -> Self { let matcher = Nucleo::new(Config::DEFAULT, Arc::new(|| {}), None, 1); // start loading files in the background tokio::spawn(load_candidates( @@ -163,7 +163,8 @@ const MAX_FILE_SIZE: u64 = 4 * 1024 * 1024; #[allow(clippy::unused_async)] async fn load_candidates(path: PathBuf, injector: Injector) { let current_dir = std::env::current_dir().unwrap(); - let walker = walk_builder(&path, *DEFAULT_NUM_THREADS).build_parallel(); + let walker = + walk_builder(&path, *DEFAULT_NUM_THREADS, None).build_parallel(); walker.run(|| { let injector = injector.clone(); @@ -218,7 +219,6 @@ async fn load_candidates(path: PathBuf, injector: Injector) { line, line_number, ); - // Send the line via the async channel let _ = injector.push( candidate, |c, cols| { diff --git a/crates/television/cli.rs b/crates/television/cli.rs index 3261f82..cb06579 100644 --- a/crates/television/cli.rs +++ b/crates/television/cli.rs @@ -5,7 +5,7 @@ use crate::config::{get_config_dir, get_data_dir}; #[derive(Parser, Debug)] #[command(author, version = version(), about)] -pub(crate) struct Cli { +pub struct Cli { /// Which channel shall we watch? #[arg(value_enum, default_value = "files")] pub channel: CliTvChannel, @@ -30,7 +30,7 @@ const VERSION_MESSAGE: &str = concat!( ")" ); -pub(crate) fn version() -> String { +pub fn version() -> String { let author = clap::crate_authors!(); // let current_exe_path = PathBuf::from(clap::crate_name!()).display().to_string(); diff --git a/crates/television/config.rs b/crates/television/config.rs index b1cd1b3..3d3d2f6 100644 --- a/crates/television/config.rs +++ b/crates/television/config.rs @@ -20,7 +20,7 @@ const CONFIG: &str = include_str!("../../.config/config.toml"); #[allow(dead_code, clippy::module_name_repetitions)] #[derive(Clone, Debug, Deserialize, Default)] -pub(crate) struct AppConfig { +pub struct AppConfig { #[serde(default)] pub data_dir: PathBuf, #[serde(default)] @@ -28,7 +28,7 @@ pub(crate) struct AppConfig { } #[derive(Clone, Debug, Default, Deserialize)] -pub(crate) struct Config { +pub struct Config { #[allow(clippy::struct_field_names)] #[serde(default, flatten)] pub config: AppConfig, @@ -52,7 +52,7 @@ lazy_static! { } impl Config { - pub(crate) fn new() -> Result { + pub fn new() -> Result { //let default_config: Config = json5::from_str(CONFIG).unwrap(); let default_config: Config = toml::from_str(CONFIG).unwrap(); let data_dir = get_data_dir(); @@ -101,7 +101,7 @@ impl Config { } } -pub(crate) fn get_data_dir() -> PathBuf { +pub fn get_data_dir() -> PathBuf { let directory = if let Some(s) = DATA_FOLDER.clone() { s } else if let Some(proj_dirs) = project_directory() { @@ -112,7 +112,7 @@ pub(crate) fn get_data_dir() -> PathBuf { directory } -pub(crate) fn get_config_dir() -> PathBuf { +pub fn get_config_dir() -> PathBuf { let directory = if let Some(s) = CONFIG_FOLDER.clone() { s } else if let Some(proj_dirs) = project_directory() { @@ -129,7 +129,7 @@ fn project_directory() -> Option { } #[derive(Clone, Debug, Default, Deref, DerefMut)] -pub(crate) struct KeyBindings(pub HashMap>); +pub struct KeyBindings(pub HashMap>); impl<'de> Deserialize<'de> for KeyBindings { fn deserialize(deserializer: D) -> Result @@ -237,7 +237,7 @@ fn parse_key_code_with_modifiers( } #[allow(dead_code)] -pub(crate) fn key_event_to_string(key_event: &KeyEvent) -> String { +pub fn key_event_to_string(key_event: &KeyEvent) -> String { let char; let key_code = match key_event.code { KeyCode::Backspace => "backspace", @@ -300,7 +300,7 @@ pub(crate) fn key_event_to_string(key_event: &KeyEvent) -> String { key } -pub(crate) fn parse_key(raw: &str) -> Result { +pub fn parse_key(raw: &str) -> Result { if raw.chars().filter(|c| *c == '>').count() != raw.chars().filter(|c| *c == '<').count() { @@ -317,7 +317,7 @@ pub(crate) fn parse_key(raw: &str) -> Result { Ok(convert_raw_event_to_key(key_event)) } -pub(crate) fn default_num_threads() -> NonZeroUsize { +pub fn default_num_threads() -> NonZeroUsize { // default to 1 thread if we can't determine the number of available threads let default = NonZeroUsize::MIN; // never use more than 32 threads to avoid startup overhead @@ -329,7 +329,7 @@ pub(crate) fn default_num_threads() -> NonZeroUsize { } #[derive(Clone, Debug, Default, Deref, DerefMut)] -pub(crate) struct Styles(pub HashMap>); +pub struct Styles(pub HashMap>); impl<'de> Deserialize<'de> for Styles { fn deserialize(deserializer: D) -> Result @@ -356,7 +356,7 @@ impl<'de> Deserialize<'de> for Styles { } } -pub(crate) fn parse_style(line: &str) -> Style { +pub fn parse_style(line: &str) -> Style { let (foreground, background) = line.split_at(line.to_lowercase().find("on ").unwrap_or(line.len())); let foreground = process_color_string(foreground); diff --git a/crates/television/entry.rs b/crates/television/entry.rs index 1beed0b..93be66e 100644 --- a/crates/television/entry.rs +++ b/crates/television/entry.rs @@ -3,7 +3,7 @@ use devicons::FileIcon; use crate::previewers::PreviewType; #[derive(Clone, Debug, Eq, PartialEq, Hash)] -pub(crate) struct Entry { +pub struct Entry { pub name: String, display_name: Option, pub value: Option, @@ -15,7 +15,7 @@ pub(crate) struct Entry { } impl Entry { - pub(crate) fn new(name: String, preview_type: PreviewType) -> Self { + pub fn new(name: String, preview_type: PreviewType) -> Self { Self { name, display_name: None, @@ -28,17 +28,17 @@ impl Entry { } } - pub(crate) fn with_display_name(mut self, display_name: String) -> Self { + pub fn with_display_name(mut self, display_name: String) -> Self { self.display_name = Some(display_name); self } - pub(crate) fn with_value(mut self, value: String) -> Self { + pub fn with_value(mut self, value: String) -> Self { self.value = Some(value); self } - pub(crate) fn with_name_match_ranges( + pub fn with_name_match_ranges( mut self, name_match_ranges: Vec<(u32, u32)>, ) -> Self { @@ -46,7 +46,7 @@ impl Entry { self } - pub(crate) fn with_value_match_ranges( + pub fn with_value_match_ranges( mut self, value_match_ranges: Vec<(u32, u32)>, ) -> Self { @@ -54,21 +54,21 @@ impl Entry { self } - pub(crate) fn with_icon(mut self, icon: FileIcon) -> Self { + pub fn with_icon(mut self, icon: FileIcon) -> Self { self.icon = Some(icon); self } - pub(crate) fn with_line_number(mut self, line_number: usize) -> Self { + pub fn with_line_number(mut self, line_number: usize) -> Self { self.line_number = Some(line_number); self } - pub(crate) fn display_name(&self) -> &str { + pub fn display_name(&self) -> &str { self.display_name.as_ref().unwrap_or(&self.name) } - pub(crate) fn stdout_repr(&self) -> String { + pub fn stdout_repr(&self) -> String { let mut repr = self.name.clone(); if let Some(line_number) = self.line_number { repr.push_str(&format!(":{line_number}")); diff --git a/crates/television/errors.rs b/crates/television/errors.rs index cba2461..e5259fa 100644 --- a/crates/television/errors.rs +++ b/crates/television/errors.rs @@ -3,7 +3,7 @@ use std::env; use color_eyre::Result; use tracing::error; -pub(crate) fn init() -> Result<()> { +pub fn init() -> Result<()> { let (panic_hook, eyre_hook) = color_eyre::config::HookBuilder::default() .panic_section(format!( "This is a bug. Consider reporting it at {}", diff --git a/crates/television/event.rs b/crates/television/event.rs index b347e27..5fa2010 100644 --- a/crates/television/event.rs +++ b/crates/television/event.rs @@ -17,7 +17,7 @@ use tokio::sync::mpsc; use tracing::warn; #[derive(Debug, Clone, Copy)] -pub(crate) enum Event { +pub enum Event { Closed, Input(I), FocusLost, @@ -29,7 +29,7 @@ pub(crate) enum Event { #[derive( Debug, Clone, Copy, Serialize, Deserialize, PartialEq, PartialOrd, Eq, Hash, )] -pub(crate) enum Key { +pub enum Key { Backspace, Enter, Left, @@ -62,7 +62,7 @@ pub(crate) enum Key { } #[allow(clippy::module_name_repetitions)] -pub(crate) struct EventLoop { +pub struct EventLoop { pub rx: mpsc::UnboundedReceiver>, //tx: mpsc::UnboundedSender>, pub abort_tx: mpsc::UnboundedSender<()>, @@ -99,7 +99,7 @@ async fn poll_event(timeout: Duration) -> bool { } impl EventLoop { - pub(crate) fn new(tick_rate: f64, init: bool) -> Self { + pub fn new(tick_rate: f64, init: bool) -> Self { let (tx, rx) = mpsc::unbounded_channel(); let tx_c = tx.clone(); let tick_interval = @@ -162,7 +162,7 @@ impl EventLoop { } } -pub(crate) fn convert_raw_event_to_key(event: KeyEvent) -> Key { +pub fn convert_raw_event_to_key(event: KeyEvent) -> Key { match event.code { Backspace => match event.modifiers { KeyModifiers::CONTROL => Key::CtrlBackspace, diff --git a/crates/television/fuzzy.rs b/crates/television/fuzzy.rs index 9148bc9..5d83f63 100644 --- a/crates/television/fuzzy.rs +++ b/crates/television/fuzzy.rs @@ -1,7 +1,7 @@ use parking_lot::Mutex; use std::ops::DerefMut; -pub(crate) struct LazyMutex { +pub struct LazyMutex { inner: Mutex>, init: fn() -> T, } @@ -14,7 +14,7 @@ impl LazyMutex { } } - pub(crate) fn lock(&self) -> impl DerefMut + '_ { + pub fn lock(&self) -> impl DerefMut + '_ { parking_lot::MutexGuard::map(self.inner.lock(), |val| { val.get_or_insert_with(self.init) }) diff --git a/crates/television/logging.rs b/crates/television/logging.rs index ec77ea9..907551f 100644 --- a/crates/television/logging.rs +++ b/crates/television/logging.rs @@ -8,7 +8,7 @@ lazy_static::lazy_static! { pub static ref LOG_FILE: String = format!("{}.log", env!("CARGO_PKG_NAME")); } -pub(crate) fn init() -> Result<()> { +pub fn init() -> Result<()> { let directory = config::get_data_dir(); std::fs::create_dir_all(directory.clone())?; let log_path = directory.join(LOG_FILE.clone()); diff --git a/crates/television/main.rs b/crates/television/main.rs index d4496f2..1131fd8 100644 --- a/crates/television/main.rs +++ b/crates/television/main.rs @@ -20,7 +20,7 @@ mod fuzzy; mod logging; mod previewers; mod render; -pub mod television; +mod television; mod tui; mod ui; mod utils; @@ -55,7 +55,7 @@ async fn main() -> Result<()> { Ok(()) } -pub(crate) fn is_readable_stdin() -> bool { +pub fn is_readable_stdin() -> bool { use std::io::IsTerminal; #[cfg(unix)] diff --git a/crates/television/previewers.rs b/crates/television/previewers.rs index d80f379..070abba 100644 --- a/crates/television/previewers.rs +++ b/crates/television/previewers.rs @@ -9,15 +9,15 @@ mod env; mod files; // previewer types -pub(crate) use basic::BasicPreviewer; -pub(crate) use directory::DirectoryPreviewer; -pub(crate) use env::EnvVarPreviewer; -pub(crate) use files::FilePreviewer; +pub use basic::BasicPreviewer; +pub use directory::DirectoryPreviewer; +pub use env::EnvVarPreviewer; +pub use files::FilePreviewer; //use ratatui_image::protocol::StatefulProtocol; use syntect::highlighting::Style; #[derive(Debug, Clone, Eq, PartialEq, Hash, Default)] -pub(crate) enum PreviewType { +pub enum PreviewType { #[default] Basic, Directory, @@ -26,10 +26,10 @@ pub(crate) enum PreviewType { } #[derive(Clone)] -pub(crate) enum PreviewContent { +pub enum PreviewContent { Empty, FileTooLarge, - HighlightedText(Vec>), + SyntectHighlightedText(Vec>), //Image(Box), Loading, NotSupported, @@ -47,7 +47,7 @@ pub const FILE_TOO_LARGE_MSG: &str = "File too large"; /// - `title`: The title of the preview. /// - `content`: The content of the preview. #[derive(Clone)] -pub(crate) struct Preview { +pub struct Preview { pub title: String, pub content: PreviewContent, } @@ -62,19 +62,24 @@ impl Default for Preview { } impl Preview { - pub(crate) fn new(title: String, content: PreviewContent) -> Self { + pub fn new(title: String, content: PreviewContent) -> Self { Preview { title, content } } - pub(crate) fn total_lines(&self) -> u16 { + pub fn total_lines(&self) -> u16 { match &self.content { - PreviewContent::HighlightedText(lines) => lines.len() as u16, + PreviewContent::SyntectHighlightedText(lines) => { + lines.len().try_into().unwrap_or(u16::MAX) + } + PreviewContent::PlainText(lines) => { + lines.len().try_into().unwrap_or(u16::MAX) + } _ => 0, } } } -pub(crate) struct Previewer { +pub struct Previewer { basic: BasicPreviewer, directory: DirectoryPreviewer, file: FilePreviewer, @@ -82,7 +87,7 @@ pub(crate) struct Previewer { } impl Previewer { - pub(crate) fn new() -> Self { + pub fn new() -> Self { Previewer { basic: BasicPreviewer::new(), directory: DirectoryPreviewer::new(), diff --git a/crates/television/previewers/basic.rs b/crates/television/previewers/basic.rs index f2af91b..f02f55b 100644 --- a/crates/television/previewers/basic.rs +++ b/crates/television/previewers/basic.rs @@ -3,14 +3,14 @@ use std::sync::Arc; use crate::entry::Entry; use crate::previewers::{Preview, PreviewContent}; -pub(crate) struct BasicPreviewer {} +pub struct BasicPreviewer {} impl BasicPreviewer { - pub(crate) fn new() -> Self { + pub fn new() -> Self { BasicPreviewer {} } - pub(crate) fn preview(&self, entry: &Entry) -> Arc { + pub fn preview(&self, entry: &Entry) -> Arc { Arc::new(Preview { title: entry.name.clone(), content: PreviewContent::PlainTextWrapped(entry.name.clone()), diff --git a/crates/television/previewers/cache.rs b/crates/television/previewers/cache.rs index ff15ab8..efb9a94 100644 --- a/crates/television/previewers/cache.rs +++ b/crates/television/previewers/cache.rs @@ -55,7 +55,7 @@ where T: Eq + std::hash::Hash + Clone + std::fmt::Debug, { /// Create a new `RingSet` with the given capacity. - pub(crate) fn with_capacity(capacity: usize) -> Self { + pub fn with_capacity(capacity: usize) -> Self { RingSet { ring_buffer: VecDeque::with_capacity(capacity), known_keys: HashSet::with_capacity(capacity), @@ -66,7 +66,7 @@ where /// Push a new item to the back of the buffer, removing the oldest item if the buffer is full. /// Returns the item that was removed, if any. /// If the item is already in the buffer, do nothing and return None. - pub(crate) fn push(&mut self, item: T) -> Option { + pub fn push(&mut self, item: T) -> Option { // If the key is already in the buffer, do nothing if self.contains(&item) { debug!("Key already in ring buffer: {:?}", item); @@ -107,28 +107,28 @@ const DEFAULT_PREVIEW_CACHE_SIZE: usize = 100; /// A cache for previews. /// The cache is implemented as an LRU cache with a fixed size. -pub(crate) struct PreviewCache { +pub struct PreviewCache { entries: HashMap>, ring_set: RingSet, } impl PreviewCache { /// Create a new preview cache with the given capacity. - pub(crate) fn new(capacity: usize) -> Self { + pub fn new(capacity: usize) -> Self { PreviewCache { entries: HashMap::new(), ring_set: RingSet::with_capacity(capacity), } } - pub(crate) fn get(&self, key: &str) -> Option> { + pub fn get(&self, key: &str) -> Option> { self.entries.get(key).cloned() } /// Insert a new preview into the cache. /// If the cache is full, the oldest entry will be removed. /// If the key is already in the cache, the preview will be updated. - pub(crate) fn insert(&mut self, key: String, preview: Arc) { + pub fn insert(&mut self, key: String, preview: Arc) { debug!("Inserting preview into cache: {}", key); self.entries.insert(key.clone(), preview.clone()); if let Some(oldest_key) = self.ring_set.push(key) { @@ -138,7 +138,7 @@ impl PreviewCache { } /// Get the preview for the given key, or insert a new preview if it doesn't exist. - pub(crate) fn get_or_insert( + pub fn get_or_insert( &mut self, key: String, f: F, diff --git a/crates/television/previewers/directory.rs b/crates/television/previewers/directory.rs index 187100a..ff9ce4c 100644 --- a/crates/television/previewers/directory.rs +++ b/crates/television/previewers/directory.rs @@ -2,51 +2,78 @@ use std::path::Path; use std::sync::Arc; use devicons::FileIcon; +use termtree::Tree; use crate::entry::Entry; use crate::previewers::cache::PreviewCache; use crate::previewers::{Preview, PreviewContent}; +use crate::utils::files::walk_builder; -pub(crate) struct DirectoryPreviewer { +pub struct DirectoryPreviewer { cache: PreviewCache, } impl DirectoryPreviewer { - pub(crate) fn new() -> Self { + pub fn new() -> Self { DirectoryPreviewer { cache: PreviewCache::default(), } } - pub(crate) fn preview(&mut self, entry: &Entry) -> Arc { + pub fn preview(&mut self, entry: &Entry) -> Arc { if let Some(preview) = self.cache.get(&entry.name) { return preview; } - let preview = Arc::new(build_preview(entry)); + let preview = Arc::new(build_tree_preview(entry)); self.cache.insert(entry.name.clone(), preview.clone()); preview } } -fn build_preview(entry: &Entry) -> Preview { - let dir_path = Path::new(&entry.name); - // get the list of files in the directory - let mut lines = vec![]; - if let Ok(entries) = std::fs::read_dir(dir_path) { - for entry in entries.flatten() { - if let Ok(file_name) = entry.file_name().into_string() { - lines.push(format!( - "{} {}", - FileIcon::from(&file_name), - &file_name - )); - } - } - } - +fn build_tree_preview(entry: &Entry) -> Preview { + let path = Path::new(&entry.name); + let tree = tree(path).unwrap(); + let tree_string = tree.to_string(); Preview { title: entry.name.clone(), - content: PreviewContent::PlainText(lines), + content: PreviewContent::PlainText( + tree_string + .lines() + .map(std::borrow::ToOwned::to_owned) + .collect(), + ), } } + +fn label>(p: P, strip: &str) -> String { + //let path = p.as_ref().file_name().unwrap().to_str().unwrap().to_owned(); + let path = p.as_ref().strip_prefix(strip).unwrap(); + let icon = FileIcon::from(&path); + format!("{} {}", icon, path.display()) +} + +/// PERF: (urgent) change to use the ignore crate here +fn tree>(p: P) -> std::io::Result> { + let result = std::fs::read_dir(&p)? + .filter_map(std::result::Result::ok) + .fold( + Tree::new(label( + p.as_ref(), + p.as_ref().parent().unwrap().to_str().unwrap(), + )), + |mut root, entry| { + let m = entry.metadata().unwrap(); + if m.is_dir() { + root.push(tree(entry.path()).unwrap()); + } else { + root.push(Tree::new(label( + entry.path(), + p.as_ref().to_str().unwrap(), + ))); + } + root + }, + ); + Ok(result) +} diff --git a/crates/television/previewers/env.rs b/crates/television/previewers/env.rs index 8eb174c..60b64ee 100644 --- a/crates/television/previewers/env.rs +++ b/crates/television/previewers/env.rs @@ -4,18 +4,18 @@ use std::sync::Arc; use crate::entry; use crate::previewers::{Preview, PreviewContent}; -pub(crate) struct EnvVarPreviewer { +pub struct EnvVarPreviewer { cache: HashMap>, } impl EnvVarPreviewer { - pub(crate) fn new() -> Self { + pub fn new() -> Self { EnvVarPreviewer { cache: HashMap::new(), } } - pub(crate) fn preview(&mut self, entry: &entry::Entry) -> Arc { + pub fn preview(&mut self, entry: &entry::Entry) -> Arc { // check if we have that preview in the cache if let Some(preview) = self.cache.get(entry) { return preview.clone(); diff --git a/crates/television/previewers/files.rs b/crates/television/previewers/files.rs index 51b5285..0ebe046 100644 --- a/crates/television/previewers/files.rs +++ b/crates/television/previewers/files.rs @@ -12,7 +12,6 @@ use syntect::{ highlighting::{Style, Theme, ThemeSet}, parsing::SyntaxSet, }; -//use tracing::{debug, info, warn}; use tracing::{debug, warn}; use crate::entry; @@ -26,7 +25,7 @@ use crate::utils::strings::{ use super::cache::PreviewCache; -pub(crate) struct FilePreviewer { +pub struct FilePreviewer { cache: Arc>, syntax_set: Arc, syntax_theme: Arc, @@ -34,7 +33,7 @@ pub(crate) struct FilePreviewer { } impl FilePreviewer { - pub(crate) fn new() -> Self { + pub fn new() -> Self { let syntax_set = SyntaxSet::load_defaults_nonewlines(); let theme_set = ThemeSet::load_defaults(); //info!("getting image picker"); @@ -184,7 +183,9 @@ impl FilePreviewer { entry_c.name.clone(), Arc::new(Preview::new( entry_c.name, - PreviewContent::HighlightedText(highlighted_lines), + PreviewContent::SyntectHighlightedText( + highlighted_lines, + ), )), ); debug!("Inserted highlighted preview into cache"); @@ -292,6 +293,7 @@ fn file_too_large(title: &str) -> Arc { )) } +#[allow(dead_code)] fn loading(title: &str) -> Arc { Arc::new(Preview::new(title.to_string(), PreviewContent::Loading)) } diff --git a/crates/television/render.rs b/crates/television/render.rs index ec6f0d4..c0ed0bf 100644 --- a/crates/television/render.rs +++ b/crates/television/render.rs @@ -15,7 +15,7 @@ use crate::television::Television; use crate::{action::Action, config::Config, tui::Tui}; #[derive(Debug)] -pub(crate) enum RenderingTask { +pub enum RenderingTask { ClearScreen, Render, Resize(u16, u16), diff --git a/crates/television/television.rs b/crates/television/television.rs index 057c7b7..caae561 100644 --- a/crates/television/television.rs +++ b/crates/television/television.rs @@ -42,7 +42,7 @@ enum Pane { static PANES: [Pane; 3] = [Pane::Input, Pane::Results, Pane::Preview]; -pub(crate) struct Television { +pub struct Television { action_tx: Option>, config: Config, channel: Box, @@ -54,8 +54,8 @@ pub(crate) struct Television { picker_view_offset: usize, results_area_height: u32, previewer: Previewer, - pub(crate) preview_scroll: Option, - pub(crate) preview_pane_height: u16, + pub preview_scroll: Option, + pub preview_pane_height: u16, current_preview_total_lines: u16, /// A cache for meta paragraphs (i.e. previews like "Not Supported", etc.). /// @@ -63,15 +63,14 @@ pub(crate) struct Television { /// preview pane. This is a little extra security to ensure meta previews /// are rendered correctly even when resizing the terminal while still /// benefiting from a cache mechanism. - pub(crate) meta_paragraph_cache: - HashMap<(String, u16, u16), Paragraph<'static>>, + pub meta_paragraph_cache: HashMap<(String, u16, u16), Paragraph<'static>>, spinner: Spinner, spinner_state: SpinnerState, } impl Television { #[must_use] - pub(crate) fn new(cli_channel: CliTvChannel) -> Self { + pub fn new(cli_channel: CliTvChannel) -> Self { let mut tv_channel = cli_channel.to_channel(); tv_channel.find(EMPTY_STRING); @@ -106,13 +105,13 @@ impl Television { #[must_use] /// # Panics /// This method will panic if the index doesn't fit into an u32. - pub(crate) fn get_selected_entry(&self) -> Option { + pub fn get_selected_entry(&self) -> Option { self.picker_state .selected() .and_then(|i| self.channel.get_result(u32::try_from(i).unwrap())) } - pub(crate) fn select_prev_entry(&mut self) { + pub fn select_prev_entry(&mut self) { if self.channel.result_count() == 0 { return; } @@ -142,7 +141,7 @@ impl Television { } } - pub(crate) fn select_next_entry(&mut self) { + pub fn select_next_entry(&mut self) { if self.channel.result_count() == 0 { return; } @@ -175,7 +174,7 @@ impl Television { self.preview_scroll = None; } - pub(crate) fn scroll_preview_down(&mut self, offset: u16) { + pub fn scroll_preview_down(&mut self, offset: u16) { if self.preview_scroll.is_none() { self.preview_scroll = Some(0); } @@ -189,7 +188,7 @@ impl Television { } } - pub(crate) fn scroll_preview_up(&mut self, offset: u16) { + pub fn scroll_preview_up(&mut self, offset: u16) { if let Some(scroll) = self.preview_scroll { self.preview_scroll = Some(scroll.saturating_sub(offset)); } @@ -202,13 +201,13 @@ impl Television { .unwrap() } - pub(crate) fn next_pane(&mut self) { + pub fn next_pane(&mut self) { let current_index = self.get_current_pane_index(); let next_index = (current_index + 1) % PANES.len(); self.current_pane = PANES[next_index]; } - pub(crate) fn previous_pane(&mut self) { + pub fn previous_pane(&mut self) { let current_index = self.get_current_pane_index(); let previous_index = if current_index == 0 { PANES.len() - 1 @@ -227,7 +226,7 @@ impl Television { /// ┌───────────────────┐│ │ /// │ Search x ││ │ /// └───────────────────┘└─────────────┘ - pub(crate) fn move_to_pane_on_top(&mut self) { + pub fn move_to_pane_on_top(&mut self) { if self.current_pane == Pane::Input { self.current_pane = Pane::Results; } @@ -242,7 +241,7 @@ impl Television { /// ┌───────────────────┐│ │ /// │ Search ││ │ /// └───────────────────┘└─────────────┘ - pub(crate) fn move_to_pane_below(&mut self) { + pub fn move_to_pane_below(&mut self) { if self.current_pane == Pane::Results { self.current_pane = Pane::Input; } @@ -257,7 +256,7 @@ impl Television { /// ┌───────────────────┐│ │ /// │ Search x ││ │ /// └───────────────────┘└─────────────┘ - pub(crate) fn move_to_pane_right(&mut self) { + pub fn move_to_pane_right(&mut self) { match self.current_pane { Pane::Results | Pane::Input => { self.current_pane = Pane::Preview; @@ -275,14 +274,14 @@ impl Television { /// ┌───────────────────┐│ │ /// │ Search ││ │ /// └───────────────────┘└─────────────┘ - pub(crate) fn move_to_pane_left(&mut self) { + pub fn move_to_pane_left(&mut self) { if self.current_pane == Pane::Preview { self.current_pane = Pane::Results; } } #[must_use] - pub(crate) fn is_input_focused(&self) -> bool { + pub fn is_input_focused(&self) -> bool { Pane::Input == self.current_pane } } @@ -302,7 +301,7 @@ impl Television { /// # Returns /// /// * `Result<()>` - An Ok result or an error. - pub(crate) fn register_action_handler( + pub fn register_action_handler( &mut self, tx: UnboundedSender, ) -> Result<()> { @@ -319,10 +318,7 @@ impl Television { /// # Returns /// /// * `Result<()>` - An Ok result or an error. - pub(crate) fn register_config_handler( - &mut self, - config: Config, - ) -> Result<()> { + pub fn register_config_handler(&mut self, config: Config) -> Result<()> { self.config = config; Ok(()) } @@ -411,11 +407,7 @@ impl Television { /// # Returns /// /// * `Result<()>` - An Ok result or an error. - pub(crate) fn draw( - &mut self, - frame: &mut Frame, - area: Rect, - ) -> Result<()> { + pub fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> { let layout = Layout::all_panes_centered(Dimensions::default(), area); //let layout = //Layout::results_only_centered(Dimensions::new(40, 60), area); diff --git a/crates/television/tui.rs b/crates/television/tui.rs index 792512b..ef5cd46 100644 --- a/crates/television/tui.rs +++ b/crates/television/tui.rs @@ -16,7 +16,7 @@ use tokio::task::JoinHandle; use tracing::debug; #[allow(dead_code)] -pub(crate) struct Tui +pub struct Tui where W: Write, { @@ -30,7 +30,7 @@ impl Tui where W: Write, { - pub(crate) fn new(writer: W) -> Result { + pub fn new(writer: W) -> Result { Ok(Self { task: tokio::spawn(async {}), frame_rate: 60.0, @@ -38,16 +38,16 @@ where }) } - pub(crate) fn frame_rate(mut self, frame_rate: f64) -> Self { + pub fn frame_rate(mut self, frame_rate: f64) -> Self { self.frame_rate = frame_rate; self } - pub(crate) fn size(&self) -> Result { + pub fn size(&self) -> Result { Ok(self.terminal.size()?) } - pub(crate) fn enter(&mut self) -> Result<()> { + pub fn enter(&mut self) -> Result<()> { enable_raw_mode()?; let mut buffered_stderr = LineWriter::new(stderr()); execute!(buffered_stderr, EnterAlternateScreen)?; @@ -56,7 +56,7 @@ where Ok(()) } - pub(crate) fn exit(&mut self) -> Result<()> { + pub fn exit(&mut self) -> Result<()> { if is_raw_mode_enabled()? { debug!("Exiting terminal"); @@ -69,14 +69,14 @@ where Ok(()) } - pub(crate) fn suspend(&mut self) -> Result<()> { + pub fn suspend(&mut self) -> Result<()> { self.exit()?; #[cfg(not(windows))] signal_hook::low_level::raise(signal_hook::consts::signal::SIGTSTP)?; Ok(()) } - pub(crate) fn resume(&mut self) -> Result<()> { + pub fn resume(&mut self) -> Result<()> { self.enter()?; Ok(()) } diff --git a/crates/television/ui.rs b/crates/television/ui.rs index 81633a6..22ea135 100644 --- a/crates/television/ui.rs +++ b/crates/television/ui.rs @@ -16,7 +16,7 @@ pub mod spinner; //const DEFAULT_PREVIEW_GUTTER_FG: Color = Color::Rgb(70, 70, 70); //const DEFAULT_PREVIEW_GUTTER_SELECTED_FG: Color = Color::Rgb(255, 150, 150); -pub(crate) fn get_border_style(focused: bool) -> Style { +pub fn get_border_style(focused: bool) -> Style { Style::default().fg(Color::Blue) // NOTE: do we want to change the border color based on focus? Are we diff --git a/crates/television/ui/input.rs b/crates/television/ui/input.rs index e780aba..a4322a2 100644 --- a/crates/television/ui/input.rs +++ b/crates/television/ui/input.rs @@ -6,7 +6,7 @@ pub mod backend; /// Different backends can be used to convert events into requests. #[allow(clippy::module_name_repetitions)] #[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)] -pub(crate) enum InputRequest { +pub enum InputRequest { SetCursor(usize), InsertChar(char), GoToPrevChar, @@ -24,7 +24,7 @@ pub(crate) enum InputRequest { } #[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)] -pub(crate) struct StateChanged { +pub struct StateChanged { pub value: bool, pub cursor: bool, } @@ -45,7 +45,7 @@ pub type InputResponse = Option; /// assert_eq!(input.to_string(), "Hello World"); /// ``` #[derive(Default, Debug, Clone)] -pub(crate) struct Input { +pub struct Input { value: String, cursor: usize, } @@ -53,14 +53,14 @@ pub(crate) struct Input { impl Input { /// Initialize a new instance with a given value /// Cursor will be set to the given value's length. - pub(crate) fn new(value: String) -> Self { + pub fn new(value: String) -> Self { let len = value.chars().count(); Self { value, cursor: len } } /// Set the value manually. /// Cursor will be set to the given value's length. - pub(crate) fn with_value(mut self, value: String) -> Self { + pub fn with_value(mut self, value: String) -> Self { self.cursor = value.chars().count(); self.value = value; self @@ -68,20 +68,20 @@ impl Input { /// Set the cursor manually. /// If the input is larger than the value length, it'll be auto adjusted. - pub(crate) fn with_cursor(mut self, cursor: usize) -> Self { + pub fn with_cursor(mut self, cursor: usize) -> Self { self.cursor = cursor.min(self.value.chars().count()); self } // Reset the cursor and value to default - pub(crate) fn reset(&mut self) { + pub fn reset(&mut self) { self.cursor = Default::default(); self.value = String::default(); } /// Handle request and emit response. #[allow(clippy::too_many_lines)] - pub(crate) fn handle(&mut self, req: InputRequest) -> InputResponse { + pub fn handle(&mut self, req: InputRequest) -> InputResponse { use InputRequest::{ DeleteLine, DeleteNextChar, DeleteNextWord, DeletePrevChar, DeletePrevWord, DeleteTillEnd, GoToEnd, GoToNextChar, @@ -328,17 +328,17 @@ impl Input { } /// Get a reference to the current value. - pub(crate) fn value(&self) -> &str { + pub fn value(&self) -> &str { self.value.as_str() } /// Get the currect cursor placement. - pub(crate) fn cursor(&self) -> usize { + pub fn cursor(&self) -> usize { self.cursor } /// Get the current cursor position with account for multispace characters. - pub(crate) fn visual_cursor(&self) -> usize { + pub fn visual_cursor(&self) -> usize { if self.cursor == 0 { return 0; } @@ -356,7 +356,7 @@ impl Input { } /// Get the scroll position with account for multispace characters. - pub(crate) fn visual_scroll(&self, width: usize) -> usize { + pub fn visual_scroll(&self, width: usize) -> usize { let scroll = self.visual_cursor().max(width) - width; let mut uscroll = 0; let mut chars = self.value().chars(); diff --git a/crates/television/ui/input/backend.rs b/crates/television/ui/input/backend.rs index 1e66101..c17bf62 100644 --- a/crates/television/ui/input/backend.rs +++ b/crates/television/ui/input/backend.rs @@ -5,7 +5,7 @@ use ratatui::crossterm::event::{ /// Converts crossterm event into input requests. /// TODO: make these keybindings configurable. -pub(crate) fn to_input_request(evt: &CrosstermEvent) -> Option { +pub fn to_input_request(evt: &CrosstermEvent) -> Option { use InputRequest::*; use KeyCode::*; match evt { diff --git a/crates/television/ui/layout.rs b/crates/television/ui/layout.rs index ccc74ff..4b8bf11 100644 --- a/crates/television/ui/layout.rs +++ b/crates/television/ui/layout.rs @@ -1,13 +1,13 @@ use ratatui::layout; use ratatui::layout::{Constraint, Direction, Rect}; -pub(crate) struct Dimensions { +pub struct Dimensions { pub x: u16, pub y: u16, } impl Dimensions { - pub(crate) fn new(x: u16, y: u16) -> Self { + pub fn new(x: u16, y: u16) -> Self { Self { x, y } } } @@ -18,7 +18,7 @@ impl Default for Dimensions { } } -pub(crate) struct Layout { +pub struct Layout { pub results: Rect, pub input: Rect, pub preview_title: Option, @@ -26,7 +26,7 @@ pub(crate) struct Layout { } impl Layout { - pub(crate) fn new( + pub fn new( results: Rect, input: Rect, preview_title: Option, @@ -42,7 +42,7 @@ impl Layout { /// TODO: add diagram #[allow(dead_code)] - pub(crate) fn all_panes_centered( + pub fn all_panes_centered( dimensions: Dimensions, area: Rect, ) -> Self { @@ -78,7 +78,7 @@ impl Layout { /// TODO: add diagram #[allow(dead_code)] - pub(crate) fn results_only_centered( + pub fn results_only_centered( dimensions: Dimensions, area: Rect, ) -> Self { diff --git a/crates/television/ui/preview.rs b/crates/television/ui/preview.rs index 9a8b0ab..c92b635 100644 --- a/crates/television/ui/preview.rs +++ b/crates/television/ui/preview.rs @@ -20,7 +20,7 @@ impl Television { const FILL_CHAR_SLANTED: char = '╱'; const FILL_CHAR_EMPTY: char = ' '; - pub(crate) fn build_preview_paragraph<'b>( + pub fn build_preview_paragraph<'b>( &'b mut self, preview_block: Block<'b>, inner: Rect, @@ -76,7 +76,7 @@ impl Television { .block(preview_block) .wrap(Wrap { trim: true }) } - PreviewContent::HighlightedText(highlighted_lines) => { + PreviewContent::SyntectHighlightedText(highlighted_lines) => { compute_paragraph_from_highlighted_lines( highlighted_lines, target_line.map(|l| l as usize), @@ -119,7 +119,7 @@ impl Television { } } - pub(crate) fn maybe_init_preview_scroll( + pub fn maybe_init_preview_scroll( &mut self, target_line: Option, height: u16, @@ -130,7 +130,7 @@ impl Television { } } - pub(crate) fn build_meta_preview_paragraph<'a>( + pub fn build_meta_preview_paragraph<'a>( &mut self, inner: Rect, message: &str, diff --git a/crates/television/ui/results.rs b/crates/television/ui/results.rs index fbba065..73496ce 100644 --- a/crates/television/ui/results.rs +++ b/crates/television/ui/results.rs @@ -9,7 +9,7 @@ const DEFAULT_RESULT_NAME_FG: Color = Color::Blue; const DEFAULT_RESULT_PREVIEW_FG: Color = Color::Rgb(150, 150, 150); const DEFAULT_RESULT_LINE_NUMBER_FG: Color = Color::Yellow; -pub(crate) fn build_results_list<'a, 'b>( +pub fn build_results_list<'a, 'b>( results_block: Block<'b>, entries: &'a [Entry], ) -> List<'a> diff --git a/crates/television/utils/files.rs b/crates/television/utils/files.rs index 01299b9..a99c398 100644 --- a/crates/television/utils/files.rs +++ b/crates/television/utils/files.rs @@ -1,7 +1,7 @@ use std::collections::HashSet; use std::path::Path; -use ignore::{types::TypesBuilder, WalkBuilder}; +use ignore::{overrides::Override, types::TypesBuilder, WalkBuilder}; use infer::Infer; use lazy_static::lazy_static; @@ -11,7 +11,11 @@ lazy_static::lazy_static! { pub static ref DEFAULT_NUM_THREADS: usize = default_num_threads().into(); } -pub(crate) fn walk_builder(path: &Path, n_threads: usize) -> WalkBuilder { +pub fn walk_builder( + path: &Path, + n_threads: usize, + overrides: Option, +) -> WalkBuilder { let mut builder = WalkBuilder::new(path); // ft-based filtering @@ -20,22 +24,25 @@ pub(crate) fn walk_builder(path: &Path, n_threads: usize) -> WalkBuilder { builder.types(types_builder.build().unwrap()); builder.threads(n_threads); + if let Some(ov) = overrides { + builder.overrides(ov); + } builder } -pub(crate) fn get_file_size(path: &Path) -> Option { +pub fn get_file_size(path: &Path) -> Option { std::fs::metadata(path).ok().map(|m| m.len()) } #[derive(Debug)] -pub(crate) enum FileType { +pub enum FileType { Text, Image, Other, Unknown, } -pub(crate) fn is_not_text(bytes: &[u8]) -> Option { +pub fn is_not_text(bytes: &[u8]) -> Option { let infer = Infer::new(); match infer.get(bytes) { Some(t) => { @@ -56,11 +63,11 @@ pub(crate) fn is_not_text(bytes: &[u8]) -> Option { } } -pub(crate) fn is_valid_utf8(bytes: &[u8]) -> bool { +pub fn is_valid_utf8(bytes: &[u8]) -> bool { std::str::from_utf8(bytes).is_ok() } -pub(crate) fn is_known_text_extension(path: &Path) -> bool { +pub fn is_known_text_extension(path: &Path) -> bool { path.extension() .and_then(|ext| ext.to_str()) .is_some_and(|ext| KNOWN_TEXT_FILE_EXTENSIONS.contains(ext)) diff --git a/crates/television/utils/indices.rs b/crates/television/utils/indices.rs index 250c38f..318277b 100644 --- a/crates/television/utils/indices.rs +++ b/crates/television/utils/indices.rs @@ -1,4 +1,4 @@ -pub(crate) fn sep_name_and_value_indices( +pub fn sep_name_and_value_indices( indices: &mut Vec, name_len: u32, ) -> (Vec, Vec, bool, bool) { diff --git a/crates/television/utils/strings.rs b/crates/television/utils/strings.rs index 26ade0c..09fe804 100644 --- a/crates/television/utils/strings.rs +++ b/crates/television/utils/strings.rs @@ -1,7 +1,7 @@ use lazy_static::lazy_static; use std::fmt::Write; -pub(crate) fn next_char_boundary(s: &str, start: usize) -> usize { +pub fn next_char_boundary(s: &str, start: usize) -> usize { let mut i = start; while !s.is_char_boundary(i) { i += 1; @@ -9,7 +9,7 @@ pub(crate) fn next_char_boundary(s: &str, start: usize) -> usize { i } -pub(crate) fn prev_char_boundary(s: &str, start: usize) -> usize { +pub fn prev_char_boundary(s: &str, start: usize) -> usize { let mut i = start; while !s.is_char_boundary(i) { i -= 1; @@ -17,7 +17,7 @@ pub(crate) fn prev_char_boundary(s: &str, start: usize) -> usize { i } -pub(crate) fn slice_at_char_boundaries( +pub fn slice_at_char_boundaries( s: &str, start_byte_index: usize, end_byte_index: usize, @@ -26,7 +26,7 @@ pub(crate) fn slice_at_char_boundaries( ..next_char_boundary(s, end_byte_index)] } -pub(crate) fn slice_up_to_char_boundary(s: &str, byte_index: usize) -> &str { +pub fn slice_up_to_char_boundary(s: &str, byte_index: usize) -> &str { let mut char_index = byte_index; while !s.is_char_boundary(char_index) { char_index -= 1; @@ -65,7 +65,7 @@ const NULL_CHARACTER: char = '\x00'; const UNIT_SEPARATOR_CHARACTER: char = '\u{001F}'; const APPLICATION_PROGRAM_COMMAND_CHARACTER: char = '\u{009F}'; -pub(crate) fn replace_nonprintable(input: &[u8], tab_width: usize) -> String { +pub fn replace_nonprintable(input: &[u8], tab_width: usize) -> String { let mut output = String::new(); let mut idx = 0; @@ -84,12 +84,10 @@ pub(crate) fn replace_nonprintable(input: &[u8], tab_width: usize) -> String { output.push_str("␊\x0A"); } // ASCII control characters from 0x00 to 0x1F - NULL_CHARACTER..=UNIT_SEPARATOR_CHARACTER => { - output.push(*NULL_SYMBOL) - } - // control characters from \u{007F} to \u{009F} - DELETE_CHARACTER..=APPLICATION_PROGRAM_COMMAND_CHARACTER => { - output.push(*NULL_SYMBOL) + // + control characters from \u{007F} to \u{009F} + NULL_CHARACTER..=UNIT_SEPARATOR_CHARACTER + | DELETE_CHARACTER..=APPLICATION_PROGRAM_COMMAND_CHARACTER => { + output.push(*NULL_SYMBOL); } // don't print BOMs BOM_CHARACTER => {} @@ -115,7 +113,7 @@ pub(crate) fn replace_nonprintable(input: &[u8], tab_width: usize) -> String { /// based on a sample of its contents. pub const PRINTABLE_ASCII_THRESHOLD: f32 = 0.7; -pub(crate) fn proportion_of_printable_ascii_characters(buffer: &[u8]) -> f32 { +pub fn proportion_of_printable_ascii_characters(buffer: &[u8]) -> f32 { let mut printable = 0; for &byte in buffer { if byte > 32 && byte < 127 { @@ -127,7 +125,7 @@ pub(crate) fn proportion_of_printable_ascii_characters(buffer: &[u8]) -> f32 { const MAX_LINE_LENGTH: usize = 500; -pub(crate) fn preprocess_line(line: &str) -> String { +pub fn preprocess_line(line: &str) -> String { replace_nonprintable( { if line.len() > MAX_LINE_LENGTH { @@ -142,7 +140,7 @@ pub(crate) fn preprocess_line(line: &str) -> String { ) } -pub(crate) fn shrink_with_ellipsis(s: &str, max_length: usize) -> String { +pub fn shrink_with_ellipsis(s: &str, max_length: usize) -> String { if s.len() <= max_length { return s.to_string(); } diff --git a/crates/television_derive/src/lib.rs b/crates/television_derive/src/lib.rs index b618fb7..b2bdd5e 100644 --- a/crates/television_derive/src/lib.rs +++ b/crates/television_derive/src/lib.rs @@ -36,7 +36,7 @@ fn impl_cli_channel(ast: &syn::DeriveInput) -> TokenStream { use clap::ValueEnum; #[derive(Debug, Clone, ValueEnum, Default, Copy)] - pub(crate) enum CliTvChannel { + pub enum CliTvChannel { #[default] #(#cli_enum_variants),* } @@ -67,7 +67,7 @@ fn impl_cli_channel(ast: &syn::DeriveInput) -> TokenStream { #cli_enum impl CliTvChannel { - pub(crate) fn to_channel(self) -> Box { + pub fn to_channel(self) -> Box { match self { #(#arms),* }