new channel git-repos and previews

This commit is contained in:
alexpasmantier 2024-10-19 00:30:43 +02:00
parent d2213af480
commit 590fe14ee5
40 changed files with 397 additions and 204 deletions

7
Cargo.lock generated
View File

@ -2459,6 +2459,7 @@ dependencies = [
"strum", "strum",
"syntect", "syntect",
"television-derive", "television-derive",
"termtree",
"tokio", "tokio",
"toml", "toml",
"tracing", "tracing",
@ -2500,6 +2501,12 @@ dependencies = [
"windows-sys 0.59.0", "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]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.64" version = "1.0.64"

View File

@ -22,9 +22,7 @@ path = "crates/television/main.rs"
name = "tv" name = "tv"
[workspace] [workspace]
members = [ members = ["crates/television_derive"]
"crates/television_derive",
]
[dependencies] [dependencies]
television-derive = { version = "0.1.0", path = "crates/television_derive" } 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" unicode-width = "0.2.0"
human-panic = "2.0.2" human-panic = "2.0.2"
pretty_assertions = "1.4.1" pretty_assertions = "1.4.1"
termtree = "0.5.1"
[build-dependencies] [build-dependencies]
@ -87,13 +86,7 @@ debug = true
[profile.release] [profile.release]
opt-level = 3
debug = "none"
strip = "symbols"
debug-assertions = false
overflow-checks = false
lto = "thin" lto = "thin"
panic = "abort"
[target.'cfg(target_os = "macos")'.dependencies] [target.'cfg(target_os = "macos")'.dependencies]
crossterm = { version = "0.28.1", features = ["serde", "use-dev-tty"] } crossterm = { version = "0.28.1", features = ["serde", "use-dev-tty"] }

View File

@ -55,3 +55,4 @@ tv with itself?
- [ ] have a keybind to send all current entries to stdout ... oorrrrr to another channel?? - [ ] 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 - [ ] 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) between possible actions (defined per channel, not all channels can pipe to all channels)
- [ ] git repositories channel (crawl the filesystem for git repos)

View File

@ -1,7 +1,5 @@
use anyhow::Result; use anyhow::Result;
use vergen_gix::{ use vergen_gix::{BuildBuilder, CargoBuilder, Emitter, RustcBuilder};
BuildBuilder, CargoBuilder, Emitter, GixBuilder, RustcBuilder,
};
fn main() -> Result<()> { fn main() -> Result<()> {
let build = BuildBuilder::default().build_date(true).build()?; let build = BuildBuilder::default().build_date(true).build()?;

View File

@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize};
use strum::Display; use strum::Display;
#[derive(Debug, Clone, PartialEq, Eq, Display, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Display, Serialize, Deserialize)]
pub(crate) enum Action { pub enum Action {
// input actions // input actions
AddInputChar(char), AddInputChar(char),
DeletePrevChar, DeletePrevChar,

View File

@ -67,7 +67,7 @@ use crate::{
render::{render, RenderingTask}, render::{render, RenderingTask},
}; };
pub(crate) struct App { pub struct App {
config: Config, config: Config,
// maybe move these two into config instead of passing them // maybe move these two into config instead of passing them
// via the cli? // via the cli?
@ -87,7 +87,7 @@ pub(crate) struct App {
#[derive( #[derive(
Default, Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default, Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize,
)] )]
pub(crate) enum Mode { pub enum Mode {
#[default] #[default]
Help, Help,
Input, Input,
@ -96,7 +96,7 @@ pub(crate) enum Mode {
} }
impl App { impl App {
pub(crate) fn new( pub fn new(
channel: CliTvChannel, channel: CliTvChannel,
tick_rate: f64, tick_rate: f64,
frame_rate: f64, frame_rate: f64,

View File

@ -4,6 +4,7 @@ use television_derive::CliChannel;
mod alias; mod alias;
mod env; mod env;
mod files; mod files;
mod git_repos;
mod stdin; mod stdin;
mod text; mod text;
@ -87,9 +88,10 @@ pub trait TelevisionChannel: Send {
/// ///
#[allow(dead_code, clippy::module_name_repetitions)] #[allow(dead_code, clippy::module_name_repetitions)]
#[derive(CliChannel)] #[derive(CliChannel)]
pub(crate) enum AvailableChannels { pub enum AvailableChannels {
Env(env::Channel), Env(env::Channel),
Files(files::Channel), Files(files::Channel),
GitRepos(git_repos::Channel),
Text(text::Channel), Text(text::Channel),
Stdin(stdin::Channel), Stdin(stdin::Channel),
Alias(alias::Channel), Alias(alias::Channel),

View File

@ -16,7 +16,7 @@ struct Alias {
value: String, value: String,
} }
pub(crate) struct Channel { pub struct Channel {
matcher: Nucleo<Alias>, matcher: Nucleo<Alias>,
last_pattern: String, last_pattern: String,
file_icon: FileIcon, file_icon: FileIcon,
@ -67,7 +67,7 @@ fn get_raw_aliases(shell: &str) -> Vec<String> {
} }
impl Channel { impl Channel {
pub(crate) fn new() -> Self { pub fn new() -> Self {
let raw_shell = get_current_shell().unwrap_or("bash".to_string()); let raw_shell = get_current_shell().unwrap_or("bash".to_string());
let shell = raw_shell.split('/').last().unwrap(); let shell = raw_shell.split('/').last().unwrap();
debug!("Current shell: {}", shell); debug!("Current shell: {}", shell);

View File

@ -17,7 +17,7 @@ struct EnvVar {
} }
#[allow(clippy::module_name_repetitions)] #[allow(clippy::module_name_repetitions)]
pub(crate) struct Channel { pub struct Channel {
matcher: Nucleo<EnvVar>, matcher: Nucleo<EnvVar>,
last_pattern: String, last_pattern: String,
file_icon: FileIcon, file_icon: FileIcon,
@ -30,7 +30,7 @@ const NUM_THREADS: usize = 1;
const FILE_ICON_STR: &str = "config"; const FILE_ICON_STR: &str = "config";
impl Channel { impl Channel {
pub(crate) fn new() -> Self { pub fn new() -> Self {
let matcher = Nucleo::new( let matcher = Nucleo::new(
Config::DEFAULT, Config::DEFAULT,
Arc::new(|| {}), Arc::new(|| {}),

View File

@ -15,18 +15,18 @@ use crate::{
}; };
use crate::{fuzzy::MATCHER, utils::strings::PRINTABLE_ASCII_THRESHOLD}; use crate::{fuzzy::MATCHER, utils::strings::PRINTABLE_ASCII_THRESHOLD};
pub(crate) struct Channel { pub struct Channel {
matcher: Nucleo<DirEntry>, matcher: Nucleo<DirEntry>,
last_pattern: String, last_pattern: String,
result_count: u32, result_count: u32,
total_count: u32, total_count: u32,
running: bool, 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) // a shallow cache (maybe more like a stack actually? so we just pop result sets)
} }
impl Channel { impl Channel {
pub(crate) fn new() -> Self { pub fn new() -> Self {
let matcher = Nucleo::new( let matcher = Nucleo::new(
Config::DEFAULT.match_paths(), Config::DEFAULT.match_paths(),
Arc::new(|| {}), Arc::new(|| {}),
@ -126,7 +126,8 @@ impl TelevisionChannel for Channel {
#[allow(clippy::unused_async)] #[allow(clippy::unused_async)]
async fn load_files(path: PathBuf, injector: Injector<DirEntry>) { async fn load_files(path: PathBuf, injector: Injector<DirEntry>) {
let current_dir = std::env::current_dir().unwrap(); 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(|| { walker.run(|| {
let injector = injector.clone(); let injector = injector.clone();
@ -134,7 +135,6 @@ async fn load_files(path: PathBuf, injector: Injector<DirEntry>) {
Box::new(move |result| { Box::new(move |result| {
if let Ok(entry) = result { if let Ok(entry) = result {
if entry.file_type().unwrap().is_file() { if entry.file_type().unwrap().is_file() {
// Send the path via the async channel
let file_name = entry.file_name(); let file_name = entry.file_name();
if proportion_of_printable_ascii_characters( if proportion_of_printable_ascii_characters(
file_name.as_bytes(), file_name.as_bytes(),

View File

@ -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<DirEntry>,
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<Entry> {
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<Entry> {
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<DirEntry>,
) {
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
})
});
}

View File

@ -11,7 +11,7 @@ use crate::previewers::PreviewType;
use super::TelevisionChannel; use super::TelevisionChannel;
pub(crate) struct Channel { pub struct Channel {
matcher: Nucleo<String>, matcher: Nucleo<String>,
last_pattern: String, last_pattern: String,
result_count: u32, result_count: u32,
@ -23,7 +23,7 @@ pub(crate) struct Channel {
const NUM_THREADS: usize = 2; const NUM_THREADS: usize = 2;
impl Channel { impl Channel {
pub(crate) fn new() -> Self { pub fn new() -> Self {
let mut lines = Vec::new(); let mut lines = Vec::new();
for line in std::io::stdin().lock().lines().map_while(Result::ok) { for line in std::io::stdin().lock().lines().map_while(Result::ok) {
debug!("Read line: {:?}", line); debug!("Read line: {:?}", line);

View File

@ -15,7 +15,7 @@ use tracing::{debug, info};
use super::TelevisionChannel; use super::TelevisionChannel;
use crate::previewers::PreviewType; use crate::previewers::PreviewType;
use crate::utils::{ 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, strings::preprocess_line,
}; };
use crate::{ use crate::{
@ -41,7 +41,7 @@ impl CandidateLine {
} }
#[allow(clippy::module_name_repetitions)] #[allow(clippy::module_name_repetitions)]
pub(crate) struct Channel { pub struct Channel {
matcher: Nucleo<CandidateLine>, matcher: Nucleo<CandidateLine>,
last_pattern: String, last_pattern: String,
result_count: u32, result_count: u32,
@ -50,7 +50,7 @@ pub(crate) struct Channel {
} }
impl Channel { impl Channel {
pub(crate) fn new() -> Self { pub fn new() -> Self {
let matcher = Nucleo::new(Config::DEFAULT, Arc::new(|| {}), None, 1); let matcher = Nucleo::new(Config::DEFAULT, Arc::new(|| {}), None, 1);
// start loading files in the background // start loading files in the background
tokio::spawn(load_candidates( tokio::spawn(load_candidates(
@ -163,7 +163,8 @@ const MAX_FILE_SIZE: u64 = 4 * 1024 * 1024;
#[allow(clippy::unused_async)] #[allow(clippy::unused_async)]
async fn load_candidates(path: PathBuf, injector: Injector<CandidateLine>) { async fn load_candidates(path: PathBuf, injector: Injector<CandidateLine>) {
let current_dir = std::env::current_dir().unwrap(); 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(|| { walker.run(|| {
let injector = injector.clone(); let injector = injector.clone();
@ -218,7 +219,6 @@ async fn load_candidates(path: PathBuf, injector: Injector<CandidateLine>) {
line, line,
line_number, line_number,
); );
// Send the line via the async channel
let _ = injector.push( let _ = injector.push(
candidate, candidate,
|c, cols| { |c, cols| {

View File

@ -5,7 +5,7 @@ use crate::config::{get_config_dir, get_data_dir};
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(author, version = version(), about)] #[command(author, version = version(), about)]
pub(crate) struct Cli { pub struct Cli {
/// Which channel shall we watch? /// Which channel shall we watch?
#[arg(value_enum, default_value = "files")] #[arg(value_enum, default_value = "files")]
pub channel: CliTvChannel, 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 author = clap::crate_authors!();
// let current_exe_path = PathBuf::from(clap::crate_name!()).display().to_string(); // let current_exe_path = PathBuf::from(clap::crate_name!()).display().to_string();

View File

@ -20,7 +20,7 @@ const CONFIG: &str = include_str!("../../.config/config.toml");
#[allow(dead_code, clippy::module_name_repetitions)] #[allow(dead_code, clippy::module_name_repetitions)]
#[derive(Clone, Debug, Deserialize, Default)] #[derive(Clone, Debug, Deserialize, Default)]
pub(crate) struct AppConfig { pub struct AppConfig {
#[serde(default)] #[serde(default)]
pub data_dir: PathBuf, pub data_dir: PathBuf,
#[serde(default)] #[serde(default)]
@ -28,7 +28,7 @@ pub(crate) struct AppConfig {
} }
#[derive(Clone, Debug, Default, Deserialize)] #[derive(Clone, Debug, Default, Deserialize)]
pub(crate) struct Config { pub struct Config {
#[allow(clippy::struct_field_names)] #[allow(clippy::struct_field_names)]
#[serde(default, flatten)] #[serde(default, flatten)]
pub config: AppConfig, pub config: AppConfig,
@ -52,7 +52,7 @@ lazy_static! {
} }
impl Config { impl Config {
pub(crate) fn new() -> Result<Self, config::ConfigError> { pub fn new() -> Result<Self, config::ConfigError> {
//let default_config: Config = json5::from_str(CONFIG).unwrap(); //let default_config: Config = json5::from_str(CONFIG).unwrap();
let default_config: Config = toml::from_str(CONFIG).unwrap(); let default_config: Config = toml::from_str(CONFIG).unwrap();
let data_dir = get_data_dir(); 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() { let directory = if let Some(s) = DATA_FOLDER.clone() {
s s
} else if let Some(proj_dirs) = project_directory() { } else if let Some(proj_dirs) = project_directory() {
@ -112,7 +112,7 @@ pub(crate) fn get_data_dir() -> PathBuf {
directory directory
} }
pub(crate) fn get_config_dir() -> PathBuf { pub fn get_config_dir() -> PathBuf {
let directory = if let Some(s) = CONFIG_FOLDER.clone() { let directory = if let Some(s) = CONFIG_FOLDER.clone() {
s s
} else if let Some(proj_dirs) = project_directory() { } else if let Some(proj_dirs) = project_directory() {
@ -129,7 +129,7 @@ fn project_directory() -> Option<ProjectDirs> {
} }
#[derive(Clone, Debug, Default, Deref, DerefMut)] #[derive(Clone, Debug, Default, Deref, DerefMut)]
pub(crate) struct KeyBindings(pub HashMap<Mode, HashMap<Key, Action>>); pub struct KeyBindings(pub HashMap<Mode, HashMap<Key, Action>>);
impl<'de> Deserialize<'de> for KeyBindings { impl<'de> Deserialize<'de> for KeyBindings {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
@ -237,7 +237,7 @@ fn parse_key_code_with_modifiers(
} }
#[allow(dead_code)] #[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 char;
let key_code = match key_event.code { let key_code = match key_event.code {
KeyCode::Backspace => "backspace", KeyCode::Backspace => "backspace",
@ -300,7 +300,7 @@ pub(crate) fn key_event_to_string(key_event: &KeyEvent) -> String {
key key
} }
pub(crate) fn parse_key(raw: &str) -> Result<Key, String> { pub fn parse_key(raw: &str) -> Result<Key, String> {
if raw.chars().filter(|c| *c == '>').count() if raw.chars().filter(|c| *c == '>').count()
!= raw.chars().filter(|c| *c == '<').count() != raw.chars().filter(|c| *c == '<').count()
{ {
@ -317,7 +317,7 @@ pub(crate) fn parse_key(raw: &str) -> Result<Key, String> {
Ok(convert_raw_event_to_key(key_event)) 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 // default to 1 thread if we can't determine the number of available threads
let default = NonZeroUsize::MIN; let default = NonZeroUsize::MIN;
// never use more than 32 threads to avoid startup overhead // 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)] #[derive(Clone, Debug, Default, Deref, DerefMut)]
pub(crate) struct Styles(pub HashMap<Mode, HashMap<String, Style>>); pub struct Styles(pub HashMap<Mode, HashMap<String, Style>>);
impl<'de> Deserialize<'de> for Styles { impl<'de> Deserialize<'de> for Styles {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
@ -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) = let (foreground, background) =
line.split_at(line.to_lowercase().find("on ").unwrap_or(line.len())); line.split_at(line.to_lowercase().find("on ").unwrap_or(line.len()));
let foreground = process_color_string(foreground); let foreground = process_color_string(foreground);

View File

@ -3,7 +3,7 @@ use devicons::FileIcon;
use crate::previewers::PreviewType; use crate::previewers::PreviewType;
#[derive(Clone, Debug, Eq, PartialEq, Hash)] #[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub(crate) struct Entry { pub struct Entry {
pub name: String, pub name: String,
display_name: Option<String>, display_name: Option<String>,
pub value: Option<String>, pub value: Option<String>,
@ -15,7 +15,7 @@ pub(crate) struct Entry {
} }
impl Entry { impl Entry {
pub(crate) fn new(name: String, preview_type: PreviewType) -> Self { pub fn new(name: String, preview_type: PreviewType) -> Self {
Self { Self {
name, name,
display_name: None, 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.display_name = Some(display_name);
self 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.value = Some(value);
self self
} }
pub(crate) fn with_name_match_ranges( pub fn with_name_match_ranges(
mut self, mut self,
name_match_ranges: Vec<(u32, u32)>, name_match_ranges: Vec<(u32, u32)>,
) -> Self { ) -> Self {
@ -46,7 +46,7 @@ impl Entry {
self self
} }
pub(crate) fn with_value_match_ranges( pub fn with_value_match_ranges(
mut self, mut self,
value_match_ranges: Vec<(u32, u32)>, value_match_ranges: Vec<(u32, u32)>,
) -> Self { ) -> Self {
@ -54,21 +54,21 @@ impl Entry {
self 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.icon = Some(icon);
self 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.line_number = Some(line_number);
self self
} }
pub(crate) fn display_name(&self) -> &str { pub fn display_name(&self) -> &str {
self.display_name.as_ref().unwrap_or(&self.name) 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(); let mut repr = self.name.clone();
if let Some(line_number) = self.line_number { if let Some(line_number) = self.line_number {
repr.push_str(&format!(":{line_number}")); repr.push_str(&format!(":{line_number}"));

View File

@ -3,7 +3,7 @@ use std::env;
use color_eyre::Result; use color_eyre::Result;
use tracing::error; use tracing::error;
pub(crate) fn init() -> Result<()> { pub fn init() -> Result<()> {
let (panic_hook, eyre_hook) = color_eyre::config::HookBuilder::default() let (panic_hook, eyre_hook) = color_eyre::config::HookBuilder::default()
.panic_section(format!( .panic_section(format!(
"This is a bug. Consider reporting it at {}", "This is a bug. Consider reporting it at {}",

View File

@ -17,7 +17,7 @@ use tokio::sync::mpsc;
use tracing::warn; use tracing::warn;
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub(crate) enum Event<I> { pub enum Event<I> {
Closed, Closed,
Input(I), Input(I),
FocusLost, FocusLost,
@ -29,7 +29,7 @@ pub(crate) enum Event<I> {
#[derive( #[derive(
Debug, Clone, Copy, Serialize, Deserialize, PartialEq, PartialOrd, Eq, Hash, Debug, Clone, Copy, Serialize, Deserialize, PartialEq, PartialOrd, Eq, Hash,
)] )]
pub(crate) enum Key { pub enum Key {
Backspace, Backspace,
Enter, Enter,
Left, Left,
@ -62,7 +62,7 @@ pub(crate) enum Key {
} }
#[allow(clippy::module_name_repetitions)] #[allow(clippy::module_name_repetitions)]
pub(crate) struct EventLoop { pub struct EventLoop {
pub rx: mpsc::UnboundedReceiver<Event<Key>>, pub rx: mpsc::UnboundedReceiver<Event<Key>>,
//tx: mpsc::UnboundedSender<Event<Key>>, //tx: mpsc::UnboundedSender<Event<Key>>,
pub abort_tx: mpsc::UnboundedSender<()>, pub abort_tx: mpsc::UnboundedSender<()>,
@ -99,7 +99,7 @@ async fn poll_event(timeout: Duration) -> bool {
} }
impl EventLoop { 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, rx) = mpsc::unbounded_channel();
let tx_c = tx.clone(); let tx_c = tx.clone();
let tick_interval = 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 { match event.code {
Backspace => match event.modifiers { Backspace => match event.modifiers {
KeyModifiers::CONTROL => Key::CtrlBackspace, KeyModifiers::CONTROL => Key::CtrlBackspace,

View File

@ -1,7 +1,7 @@
use parking_lot::Mutex; use parking_lot::Mutex;
use std::ops::DerefMut; use std::ops::DerefMut;
pub(crate) struct LazyMutex<T> { pub struct LazyMutex<T> {
inner: Mutex<Option<T>>, inner: Mutex<Option<T>>,
init: fn() -> T, init: fn() -> T,
} }
@ -14,7 +14,7 @@ impl<T> LazyMutex<T> {
} }
} }
pub(crate) fn lock(&self) -> impl DerefMut<Target = T> + '_ { pub fn lock(&self) -> impl DerefMut<Target = T> + '_ {
parking_lot::MutexGuard::map(self.inner.lock(), |val| { parking_lot::MutexGuard::map(self.inner.lock(), |val| {
val.get_or_insert_with(self.init) val.get_or_insert_with(self.init)
}) })

View File

@ -8,7 +8,7 @@ lazy_static::lazy_static! {
pub static ref LOG_FILE: String = format!("{}.log", env!("CARGO_PKG_NAME")); 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(); let directory = config::get_data_dir();
std::fs::create_dir_all(directory.clone())?; std::fs::create_dir_all(directory.clone())?;
let log_path = directory.join(LOG_FILE.clone()); let log_path = directory.join(LOG_FILE.clone());

View File

@ -20,7 +20,7 @@ mod fuzzy;
mod logging; mod logging;
mod previewers; mod previewers;
mod render; mod render;
pub mod television; mod television;
mod tui; mod tui;
mod ui; mod ui;
mod utils; mod utils;
@ -55,7 +55,7 @@ async fn main() -> Result<()> {
Ok(()) Ok(())
} }
pub(crate) fn is_readable_stdin() -> bool { pub fn is_readable_stdin() -> bool {
use std::io::IsTerminal; use std::io::IsTerminal;
#[cfg(unix)] #[cfg(unix)]

View File

@ -9,15 +9,15 @@ mod env;
mod files; mod files;
// previewer types // previewer types
pub(crate) use basic::BasicPreviewer; pub use basic::BasicPreviewer;
pub(crate) use directory::DirectoryPreviewer; pub use directory::DirectoryPreviewer;
pub(crate) use env::EnvVarPreviewer; pub use env::EnvVarPreviewer;
pub(crate) use files::FilePreviewer; pub use files::FilePreviewer;
//use ratatui_image::protocol::StatefulProtocol; //use ratatui_image::protocol::StatefulProtocol;
use syntect::highlighting::Style; use syntect::highlighting::Style;
#[derive(Debug, Clone, Eq, PartialEq, Hash, Default)] #[derive(Debug, Clone, Eq, PartialEq, Hash, Default)]
pub(crate) enum PreviewType { pub enum PreviewType {
#[default] #[default]
Basic, Basic,
Directory, Directory,
@ -26,10 +26,10 @@ pub(crate) enum PreviewType {
} }
#[derive(Clone)] #[derive(Clone)]
pub(crate) enum PreviewContent { pub enum PreviewContent {
Empty, Empty,
FileTooLarge, FileTooLarge,
HighlightedText(Vec<Vec<(Style, String)>>), SyntectHighlightedText(Vec<Vec<(Style, String)>>),
//Image(Box<dyn StatefulProtocol>), //Image(Box<dyn StatefulProtocol>),
Loading, Loading,
NotSupported, NotSupported,
@ -47,7 +47,7 @@ pub const FILE_TOO_LARGE_MSG: &str = "File too large";
/// - `title`: The title of the preview. /// - `title`: The title of the preview.
/// - `content`: The content of the preview. /// - `content`: The content of the preview.
#[derive(Clone)] #[derive(Clone)]
pub(crate) struct Preview { pub struct Preview {
pub title: String, pub title: String,
pub content: PreviewContent, pub content: PreviewContent,
} }
@ -62,19 +62,24 @@ impl Default for Preview {
} }
impl Preview { impl Preview {
pub(crate) fn new(title: String, content: PreviewContent) -> Self { pub fn new(title: String, content: PreviewContent) -> Self {
Preview { title, content } Preview { title, content }
} }
pub(crate) fn total_lines(&self) -> u16 { pub fn total_lines(&self) -> u16 {
match &self.content { 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, _ => 0,
} }
} }
} }
pub(crate) struct Previewer { pub struct Previewer {
basic: BasicPreviewer, basic: BasicPreviewer,
directory: DirectoryPreviewer, directory: DirectoryPreviewer,
file: FilePreviewer, file: FilePreviewer,
@ -82,7 +87,7 @@ pub(crate) struct Previewer {
} }
impl Previewer { impl Previewer {
pub(crate) fn new() -> Self { pub fn new() -> Self {
Previewer { Previewer {
basic: BasicPreviewer::new(), basic: BasicPreviewer::new(),
directory: DirectoryPreviewer::new(), directory: DirectoryPreviewer::new(),

View File

@ -3,14 +3,14 @@ use std::sync::Arc;
use crate::entry::Entry; use crate::entry::Entry;
use crate::previewers::{Preview, PreviewContent}; use crate::previewers::{Preview, PreviewContent};
pub(crate) struct BasicPreviewer {} pub struct BasicPreviewer {}
impl BasicPreviewer { impl BasicPreviewer {
pub(crate) fn new() -> Self { pub fn new() -> Self {
BasicPreviewer {} BasicPreviewer {}
} }
pub(crate) fn preview(&self, entry: &Entry) -> Arc<Preview> { pub fn preview(&self, entry: &Entry) -> Arc<Preview> {
Arc::new(Preview { Arc::new(Preview {
title: entry.name.clone(), title: entry.name.clone(),
content: PreviewContent::PlainTextWrapped(entry.name.clone()), content: PreviewContent::PlainTextWrapped(entry.name.clone()),

View File

@ -55,7 +55,7 @@ where
T: Eq + std::hash::Hash + Clone + std::fmt::Debug, T: Eq + std::hash::Hash + Clone + std::fmt::Debug,
{ {
/// Create a new `RingSet` with the given capacity. /// Create a new `RingSet` with the given capacity.
pub(crate) fn with_capacity(capacity: usize) -> Self { pub fn with_capacity(capacity: usize) -> Self {
RingSet { RingSet {
ring_buffer: VecDeque::with_capacity(capacity), ring_buffer: VecDeque::with_capacity(capacity),
known_keys: HashSet::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. /// 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. /// Returns the item that was removed, if any.
/// If the item is already in the buffer, do nothing and return None. /// If the item is already in the buffer, do nothing and return None.
pub(crate) fn push(&mut self, item: T) -> Option<T> { pub fn push(&mut self, item: T) -> Option<T> {
// If the key is already in the buffer, do nothing // If the key is already in the buffer, do nothing
if self.contains(&item) { if self.contains(&item) {
debug!("Key already in ring buffer: {:?}", item); debug!("Key already in ring buffer: {:?}", item);
@ -107,28 +107,28 @@ const DEFAULT_PREVIEW_CACHE_SIZE: usize = 100;
/// A cache for previews. /// A cache for previews.
/// The cache is implemented as an LRU cache with a fixed size. /// The cache is implemented as an LRU cache with a fixed size.
pub(crate) struct PreviewCache { pub struct PreviewCache {
entries: HashMap<String, Arc<Preview>>, entries: HashMap<String, Arc<Preview>>,
ring_set: RingSet<String>, ring_set: RingSet<String>,
} }
impl PreviewCache { impl PreviewCache {
/// Create a new preview cache with the given capacity. /// Create a new preview cache with the given capacity.
pub(crate) fn new(capacity: usize) -> Self { pub fn new(capacity: usize) -> Self {
PreviewCache { PreviewCache {
entries: HashMap::new(), entries: HashMap::new(),
ring_set: RingSet::with_capacity(capacity), ring_set: RingSet::with_capacity(capacity),
} }
} }
pub(crate) fn get(&self, key: &str) -> Option<Arc<Preview>> { pub fn get(&self, key: &str) -> Option<Arc<Preview>> {
self.entries.get(key).cloned() self.entries.get(key).cloned()
} }
/// Insert a new preview into the cache. /// Insert a new preview into the cache.
/// If the cache is full, the oldest entry will be removed. /// If the cache is full, the oldest entry will be removed.
/// If the key is already in the cache, the preview will be updated. /// If the key is already in the cache, the preview will be updated.
pub(crate) fn insert(&mut self, key: String, preview: Arc<Preview>) { pub fn insert(&mut self, key: String, preview: Arc<Preview>) {
debug!("Inserting preview into cache: {}", key); debug!("Inserting preview into cache: {}", key);
self.entries.insert(key.clone(), preview.clone()); self.entries.insert(key.clone(), preview.clone());
if let Some(oldest_key) = self.ring_set.push(key) { 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. /// Get the preview for the given key, or insert a new preview if it doesn't exist.
pub(crate) fn get_or_insert<F>( pub fn get_or_insert<F>(
&mut self, &mut self,
key: String, key: String,
f: F, f: F,

View File

@ -2,51 +2,78 @@ use std::path::Path;
use std::sync::Arc; use std::sync::Arc;
use devicons::FileIcon; use devicons::FileIcon;
use termtree::Tree;
use crate::entry::Entry; use crate::entry::Entry;
use crate::previewers::cache::PreviewCache; use crate::previewers::cache::PreviewCache;
use crate::previewers::{Preview, PreviewContent}; use crate::previewers::{Preview, PreviewContent};
use crate::utils::files::walk_builder;
pub(crate) struct DirectoryPreviewer { pub struct DirectoryPreviewer {
cache: PreviewCache, cache: PreviewCache,
} }
impl DirectoryPreviewer { impl DirectoryPreviewer {
pub(crate) fn new() -> Self { pub fn new() -> Self {
DirectoryPreviewer { DirectoryPreviewer {
cache: PreviewCache::default(), cache: PreviewCache::default(),
} }
} }
pub(crate) fn preview(&mut self, entry: &Entry) -> Arc<Preview> { pub fn preview(&mut self, entry: &Entry) -> Arc<Preview> {
if let Some(preview) = self.cache.get(&entry.name) { if let Some(preview) = self.cache.get(&entry.name) {
return preview; 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()); self.cache.insert(entry.name.clone(), preview.clone());
preview preview
} }
} }
fn build_preview(entry: &Entry) -> Preview { fn build_tree_preview(entry: &Entry) -> Preview {
let dir_path = Path::new(&entry.name); let path = Path::new(&entry.name);
// get the list of files in the directory let tree = tree(path).unwrap();
let mut lines = vec![]; let tree_string = tree.to_string();
if let Ok(entries) = std::fs::read_dir(dir_path) { Preview {
for entry in entries.flatten() { title: entry.name.clone(),
if let Ok(file_name) = entry.file_name().into_string() { content: PreviewContent::PlainText(
lines.push(format!( tree_string
"{} {}", .lines()
FileIcon::from(&file_name), .map(std::borrow::ToOwned::to_owned)
&file_name .collect(),
)); ),
}
} }
} }
Preview { fn label<P: AsRef<Path>>(p: P, strip: &str) -> String {
title: entry.name.clone(), //let path = p.as_ref().file_name().unwrap().to_str().unwrap().to_owned();
content: PreviewContent::PlainText(lines), 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: AsRef<Path>>(p: P) -> std::io::Result<Tree<String>> {
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)
} }

View File

@ -4,18 +4,18 @@ use std::sync::Arc;
use crate::entry; use crate::entry;
use crate::previewers::{Preview, PreviewContent}; use crate::previewers::{Preview, PreviewContent};
pub(crate) struct EnvVarPreviewer { pub struct EnvVarPreviewer {
cache: HashMap<entry::Entry, Arc<Preview>>, cache: HashMap<entry::Entry, Arc<Preview>>,
} }
impl EnvVarPreviewer { impl EnvVarPreviewer {
pub(crate) fn new() -> Self { pub fn new() -> Self {
EnvVarPreviewer { EnvVarPreviewer {
cache: HashMap::new(), cache: HashMap::new(),
} }
} }
pub(crate) fn preview(&mut self, entry: &entry::Entry) -> Arc<Preview> { pub fn preview(&mut self, entry: &entry::Entry) -> Arc<Preview> {
// check if we have that preview in the cache // check if we have that preview in the cache
if let Some(preview) = self.cache.get(entry) { if let Some(preview) = self.cache.get(entry) {
return preview.clone(); return preview.clone();

View File

@ -12,7 +12,6 @@ use syntect::{
highlighting::{Style, Theme, ThemeSet}, highlighting::{Style, Theme, ThemeSet},
parsing::SyntaxSet, parsing::SyntaxSet,
}; };
//use tracing::{debug, info, warn};
use tracing::{debug, warn}; use tracing::{debug, warn};
use crate::entry; use crate::entry;
@ -26,7 +25,7 @@ use crate::utils::strings::{
use super::cache::PreviewCache; use super::cache::PreviewCache;
pub(crate) struct FilePreviewer { pub struct FilePreviewer {
cache: Arc<Mutex<PreviewCache>>, cache: Arc<Mutex<PreviewCache>>,
syntax_set: Arc<SyntaxSet>, syntax_set: Arc<SyntaxSet>,
syntax_theme: Arc<Theme>, syntax_theme: Arc<Theme>,
@ -34,7 +33,7 @@ pub(crate) struct FilePreviewer {
} }
impl FilePreviewer { impl FilePreviewer {
pub(crate) fn new() -> Self { pub fn new() -> Self {
let syntax_set = SyntaxSet::load_defaults_nonewlines(); let syntax_set = SyntaxSet::load_defaults_nonewlines();
let theme_set = ThemeSet::load_defaults(); let theme_set = ThemeSet::load_defaults();
//info!("getting image picker"); //info!("getting image picker");
@ -184,7 +183,9 @@ impl FilePreviewer {
entry_c.name.clone(), entry_c.name.clone(),
Arc::new(Preview::new( Arc::new(Preview::new(
entry_c.name, entry_c.name,
PreviewContent::HighlightedText(highlighted_lines), PreviewContent::SyntectHighlightedText(
highlighted_lines,
),
)), )),
); );
debug!("Inserted highlighted preview into cache"); debug!("Inserted highlighted preview into cache");
@ -292,6 +293,7 @@ fn file_too_large(title: &str) -> Arc<Preview> {
)) ))
} }
#[allow(dead_code)]
fn loading(title: &str) -> Arc<Preview> { fn loading(title: &str) -> Arc<Preview> {
Arc::new(Preview::new(title.to_string(), PreviewContent::Loading)) Arc::new(Preview::new(title.to_string(), PreviewContent::Loading))
} }

View File

@ -15,7 +15,7 @@ use crate::television::Television;
use crate::{action::Action, config::Config, tui::Tui}; use crate::{action::Action, config::Config, tui::Tui};
#[derive(Debug)] #[derive(Debug)]
pub(crate) enum RenderingTask { pub enum RenderingTask {
ClearScreen, ClearScreen,
Render, Render,
Resize(u16, u16), Resize(u16, u16),

View File

@ -42,7 +42,7 @@ enum Pane {
static PANES: [Pane; 3] = [Pane::Input, Pane::Results, Pane::Preview]; static PANES: [Pane; 3] = [Pane::Input, Pane::Results, Pane::Preview];
pub(crate) struct Television { pub struct Television {
action_tx: Option<UnboundedSender<Action>>, action_tx: Option<UnboundedSender<Action>>,
config: Config, config: Config,
channel: Box<dyn TelevisionChannel>, channel: Box<dyn TelevisionChannel>,
@ -54,8 +54,8 @@ pub(crate) struct Television {
picker_view_offset: usize, picker_view_offset: usize,
results_area_height: u32, results_area_height: u32,
previewer: Previewer, previewer: Previewer,
pub(crate) preview_scroll: Option<u16>, pub preview_scroll: Option<u16>,
pub(crate) preview_pane_height: u16, pub preview_pane_height: u16,
current_preview_total_lines: u16, current_preview_total_lines: u16,
/// A cache for meta paragraphs (i.e. previews like "Not Supported", etc.). /// 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 /// preview pane. This is a little extra security to ensure meta previews
/// are rendered correctly even when resizing the terminal while still /// are rendered correctly even when resizing the terminal while still
/// benefiting from a cache mechanism. /// benefiting from a cache mechanism.
pub(crate) meta_paragraph_cache: pub meta_paragraph_cache: HashMap<(String, u16, u16), Paragraph<'static>>,
HashMap<(String, u16, u16), Paragraph<'static>>,
spinner: Spinner, spinner: Spinner,
spinner_state: SpinnerState, spinner_state: SpinnerState,
} }
impl Television { impl Television {
#[must_use] #[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(); let mut tv_channel = cli_channel.to_channel();
tv_channel.find(EMPTY_STRING); tv_channel.find(EMPTY_STRING);
@ -106,13 +105,13 @@ impl Television {
#[must_use] #[must_use]
/// # Panics /// # Panics
/// This method will panic if the index doesn't fit into an u32. /// This method will panic if the index doesn't fit into an u32.
pub(crate) fn get_selected_entry(&self) -> Option<Entry> { pub fn get_selected_entry(&self) -> Option<Entry> {
self.picker_state self.picker_state
.selected() .selected()
.and_then(|i| self.channel.get_result(u32::try_from(i).unwrap())) .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 { if self.channel.result_count() == 0 {
return; 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 { if self.channel.result_count() == 0 {
return; return;
} }
@ -175,7 +174,7 @@ impl Television {
self.preview_scroll = None; 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() { if self.preview_scroll.is_none() {
self.preview_scroll = Some(0); 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 { if let Some(scroll) = self.preview_scroll {
self.preview_scroll = Some(scroll.saturating_sub(offset)); self.preview_scroll = Some(scroll.saturating_sub(offset));
} }
@ -202,13 +201,13 @@ impl Television {
.unwrap() .unwrap()
} }
pub(crate) fn next_pane(&mut self) { pub fn next_pane(&mut self) {
let current_index = self.get_current_pane_index(); let current_index = self.get_current_pane_index();
let next_index = (current_index + 1) % PANES.len(); let next_index = (current_index + 1) % PANES.len();
self.current_pane = PANES[next_index]; 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 current_index = self.get_current_pane_index();
let previous_index = if current_index == 0 { let previous_index = if current_index == 0 {
PANES.len() - 1 PANES.len() - 1
@ -227,7 +226,7 @@ impl Television {
/// ┌───────────────────┐│ │ /// ┌───────────────────┐│ │
/// │ Search x ││ │ /// │ 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 { if self.current_pane == Pane::Input {
self.current_pane = Pane::Results; self.current_pane = Pane::Results;
} }
@ -242,7 +241,7 @@ impl Television {
/// ┌───────────────────┐│ │ /// ┌───────────────────┐│ │
/// │ Search ││ │ /// │ Search ││ │
/// └───────────────────┘└─────────────┘ /// └───────────────────┘└─────────────┘
pub(crate) fn move_to_pane_below(&mut self) { pub fn move_to_pane_below(&mut self) {
if self.current_pane == Pane::Results { if self.current_pane == Pane::Results {
self.current_pane = Pane::Input; self.current_pane = Pane::Input;
} }
@ -257,7 +256,7 @@ impl Television {
/// ┌───────────────────┐│ │ /// ┌───────────────────┐│ │
/// │ Search x ││ │ /// │ Search x ││ │
/// └───────────────────┘└─────────────┘ /// └───────────────────┘└─────────────┘
pub(crate) fn move_to_pane_right(&mut self) { pub fn move_to_pane_right(&mut self) {
match self.current_pane { match self.current_pane {
Pane::Results | Pane::Input => { Pane::Results | Pane::Input => {
self.current_pane = Pane::Preview; self.current_pane = Pane::Preview;
@ -275,14 +274,14 @@ impl Television {
/// ┌───────────────────┐│ │ /// ┌───────────────────┐│ │
/// │ Search ││ │ /// │ Search ││ │
/// └───────────────────┘└─────────────┘ /// └───────────────────┘└─────────────┘
pub(crate) fn move_to_pane_left(&mut self) { pub fn move_to_pane_left(&mut self) {
if self.current_pane == Pane::Preview { if self.current_pane == Pane::Preview {
self.current_pane = Pane::Results; self.current_pane = Pane::Results;
} }
} }
#[must_use] #[must_use]
pub(crate) fn is_input_focused(&self) -> bool { pub fn is_input_focused(&self) -> bool {
Pane::Input == self.current_pane Pane::Input == self.current_pane
} }
} }
@ -302,7 +301,7 @@ impl Television {
/// # Returns /// # Returns
/// ///
/// * `Result<()>` - An Ok result or an error. /// * `Result<()>` - An Ok result or an error.
pub(crate) fn register_action_handler( pub fn register_action_handler(
&mut self, &mut self,
tx: UnboundedSender<Action>, tx: UnboundedSender<Action>,
) -> Result<()> { ) -> Result<()> {
@ -319,10 +318,7 @@ impl Television {
/// # Returns /// # Returns
/// ///
/// * `Result<()>` - An Ok result or an error. /// * `Result<()>` - An Ok result or an error.
pub(crate) fn register_config_handler( pub fn register_config_handler(&mut self, config: Config) -> Result<()> {
&mut self,
config: Config,
) -> Result<()> {
self.config = config; self.config = config;
Ok(()) Ok(())
} }
@ -411,11 +407,7 @@ impl Television {
/// # Returns /// # Returns
/// ///
/// * `Result<()>` - An Ok result or an error. /// * `Result<()>` - An Ok result or an error.
pub(crate) fn draw( pub fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
&mut self,
frame: &mut Frame,
area: Rect,
) -> Result<()> {
let layout = Layout::all_panes_centered(Dimensions::default(), area); let layout = Layout::all_panes_centered(Dimensions::default(), area);
//let layout = //let layout =
//Layout::results_only_centered(Dimensions::new(40, 60), area); //Layout::results_only_centered(Dimensions::new(40, 60), area);

View File

@ -16,7 +16,7 @@ use tokio::task::JoinHandle;
use tracing::debug; use tracing::debug;
#[allow(dead_code)] #[allow(dead_code)]
pub(crate) struct Tui<W> pub struct Tui<W>
where where
W: Write, W: Write,
{ {
@ -30,7 +30,7 @@ impl<W> Tui<W>
where where
W: Write, W: Write,
{ {
pub(crate) fn new(writer: W) -> Result<Self> { pub fn new(writer: W) -> Result<Self> {
Ok(Self { Ok(Self {
task: tokio::spawn(async {}), task: tokio::spawn(async {}),
frame_rate: 60.0, 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.frame_rate = frame_rate;
self self
} }
pub(crate) fn size(&self) -> Result<Size> { pub fn size(&self) -> Result<Size> {
Ok(self.terminal.size()?) Ok(self.terminal.size()?)
} }
pub(crate) fn enter(&mut self) -> Result<()> { pub fn enter(&mut self) -> Result<()> {
enable_raw_mode()?; enable_raw_mode()?;
let mut buffered_stderr = LineWriter::new(stderr()); let mut buffered_stderr = LineWriter::new(stderr());
execute!(buffered_stderr, EnterAlternateScreen)?; execute!(buffered_stderr, EnterAlternateScreen)?;
@ -56,7 +56,7 @@ where
Ok(()) Ok(())
} }
pub(crate) fn exit(&mut self) -> Result<()> { pub fn exit(&mut self) -> Result<()> {
if is_raw_mode_enabled()? { if is_raw_mode_enabled()? {
debug!("Exiting terminal"); debug!("Exiting terminal");
@ -69,14 +69,14 @@ where
Ok(()) Ok(())
} }
pub(crate) fn suspend(&mut self) -> Result<()> { pub fn suspend(&mut self) -> Result<()> {
self.exit()?; self.exit()?;
#[cfg(not(windows))] #[cfg(not(windows))]
signal_hook::low_level::raise(signal_hook::consts::signal::SIGTSTP)?; signal_hook::low_level::raise(signal_hook::consts::signal::SIGTSTP)?;
Ok(()) Ok(())
} }
pub(crate) fn resume(&mut self) -> Result<()> { pub fn resume(&mut self) -> Result<()> {
self.enter()?; self.enter()?;
Ok(()) Ok(())
} }

View File

@ -16,7 +16,7 @@ pub mod spinner;
//const DEFAULT_PREVIEW_GUTTER_FG: Color = Color::Rgb(70, 70, 70); //const DEFAULT_PREVIEW_GUTTER_FG: Color = Color::Rgb(70, 70, 70);
//const DEFAULT_PREVIEW_GUTTER_SELECTED_FG: Color = Color::Rgb(255, 150, 150); //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) Style::default().fg(Color::Blue)
// NOTE: do we want to change the border color based on focus? Are we // NOTE: do we want to change the border color based on focus? Are we

View File

@ -6,7 +6,7 @@ pub mod backend;
/// Different backends can be used to convert events into requests. /// Different backends can be used to convert events into requests.
#[allow(clippy::module_name_repetitions)] #[allow(clippy::module_name_repetitions)]
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)] #[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)]
pub(crate) enum InputRequest { pub enum InputRequest {
SetCursor(usize), SetCursor(usize),
InsertChar(char), InsertChar(char),
GoToPrevChar, GoToPrevChar,
@ -24,7 +24,7 @@ pub(crate) enum InputRequest {
} }
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)] #[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)]
pub(crate) struct StateChanged { pub struct StateChanged {
pub value: bool, pub value: bool,
pub cursor: bool, pub cursor: bool,
} }
@ -45,7 +45,7 @@ pub type InputResponse = Option<StateChanged>;
/// assert_eq!(input.to_string(), "Hello World"); /// assert_eq!(input.to_string(), "Hello World");
/// ``` /// ```
#[derive(Default, Debug, Clone)] #[derive(Default, Debug, Clone)]
pub(crate) struct Input { pub struct Input {
value: String, value: String,
cursor: usize, cursor: usize,
} }
@ -53,14 +53,14 @@ pub(crate) struct Input {
impl Input { impl Input {
/// Initialize a new instance with a given value /// Initialize a new instance with a given value
/// Cursor will be set to the given value's length. /// 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(); let len = value.chars().count();
Self { value, cursor: len } Self { value, cursor: len }
} }
/// Set the value manually. /// Set the value manually.
/// Cursor will be set to the given value's length. /// 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.cursor = value.chars().count();
self.value = value; self.value = value;
self self
@ -68,20 +68,20 @@ impl Input {
/// Set the cursor manually. /// Set the cursor manually.
/// If the input is larger than the value length, it'll be auto adjusted. /// 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.cursor = cursor.min(self.value.chars().count());
self self
} }
// Reset the cursor and value to default // Reset the cursor and value to default
pub(crate) fn reset(&mut self) { pub fn reset(&mut self) {
self.cursor = Default::default(); self.cursor = Default::default();
self.value = String::default(); self.value = String::default();
} }
/// Handle request and emit response. /// Handle request and emit response.
#[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]
pub(crate) fn handle(&mut self, req: InputRequest) -> InputResponse { pub fn handle(&mut self, req: InputRequest) -> InputResponse {
use InputRequest::{ use InputRequest::{
DeleteLine, DeleteNextChar, DeleteNextWord, DeletePrevChar, DeleteLine, DeleteNextChar, DeleteNextWord, DeletePrevChar,
DeletePrevWord, DeleteTillEnd, GoToEnd, GoToNextChar, DeletePrevWord, DeleteTillEnd, GoToEnd, GoToNextChar,
@ -328,17 +328,17 @@ impl Input {
} }
/// Get a reference to the current value. /// Get a reference to the current value.
pub(crate) fn value(&self) -> &str { pub fn value(&self) -> &str {
self.value.as_str() self.value.as_str()
} }
/// Get the currect cursor placement. /// Get the currect cursor placement.
pub(crate) fn cursor(&self) -> usize { pub fn cursor(&self) -> usize {
self.cursor self.cursor
} }
/// Get the current cursor position with account for multispace characters. /// 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 { if self.cursor == 0 {
return 0; return 0;
} }
@ -356,7 +356,7 @@ impl Input {
} }
/// Get the scroll position with account for multispace characters. /// 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 scroll = self.visual_cursor().max(width) - width;
let mut uscroll = 0; let mut uscroll = 0;
let mut chars = self.value().chars(); let mut chars = self.value().chars();

View File

@ -5,7 +5,7 @@ use ratatui::crossterm::event::{
/// Converts crossterm event into input requests. /// Converts crossterm event into input requests.
/// TODO: make these keybindings configurable. /// TODO: make these keybindings configurable.
pub(crate) fn to_input_request(evt: &CrosstermEvent) -> Option<InputRequest> { pub fn to_input_request(evt: &CrosstermEvent) -> Option<InputRequest> {
use InputRequest::*; use InputRequest::*;
use KeyCode::*; use KeyCode::*;
match evt { match evt {

View File

@ -1,13 +1,13 @@
use ratatui::layout; use ratatui::layout;
use ratatui::layout::{Constraint, Direction, Rect}; use ratatui::layout::{Constraint, Direction, Rect};
pub(crate) struct Dimensions { pub struct Dimensions {
pub x: u16, pub x: u16,
pub y: u16, pub y: u16,
} }
impl Dimensions { impl Dimensions {
pub(crate) fn new(x: u16, y: u16) -> Self { pub fn new(x: u16, y: u16) -> Self {
Self { x, y } Self { x, y }
} }
} }
@ -18,7 +18,7 @@ impl Default for Dimensions {
} }
} }
pub(crate) struct Layout { pub struct Layout {
pub results: Rect, pub results: Rect,
pub input: Rect, pub input: Rect,
pub preview_title: Option<Rect>, pub preview_title: Option<Rect>,
@ -26,7 +26,7 @@ pub(crate) struct Layout {
} }
impl Layout { impl Layout {
pub(crate) fn new( pub fn new(
results: Rect, results: Rect,
input: Rect, input: Rect,
preview_title: Option<Rect>, preview_title: Option<Rect>,
@ -42,7 +42,7 @@ impl Layout {
/// TODO: add diagram /// TODO: add diagram
#[allow(dead_code)] #[allow(dead_code)]
pub(crate) fn all_panes_centered( pub fn all_panes_centered(
dimensions: Dimensions, dimensions: Dimensions,
area: Rect, area: Rect,
) -> Self { ) -> Self {
@ -78,7 +78,7 @@ impl Layout {
/// TODO: add diagram /// TODO: add diagram
#[allow(dead_code)] #[allow(dead_code)]
pub(crate) fn results_only_centered( pub fn results_only_centered(
dimensions: Dimensions, dimensions: Dimensions,
area: Rect, area: Rect,
) -> Self { ) -> Self {

View File

@ -20,7 +20,7 @@ impl Television {
const FILL_CHAR_SLANTED: char = ''; const FILL_CHAR_SLANTED: char = '';
const FILL_CHAR_EMPTY: char = ' '; const FILL_CHAR_EMPTY: char = ' ';
pub(crate) fn build_preview_paragraph<'b>( pub fn build_preview_paragraph<'b>(
&'b mut self, &'b mut self,
preview_block: Block<'b>, preview_block: Block<'b>,
inner: Rect, inner: Rect,
@ -76,7 +76,7 @@ impl Television {
.block(preview_block) .block(preview_block)
.wrap(Wrap { trim: true }) .wrap(Wrap { trim: true })
} }
PreviewContent::HighlightedText(highlighted_lines) => { PreviewContent::SyntectHighlightedText(highlighted_lines) => {
compute_paragraph_from_highlighted_lines( compute_paragraph_from_highlighted_lines(
highlighted_lines, highlighted_lines,
target_line.map(|l| l as usize), 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, &mut self,
target_line: Option<u16>, target_line: Option<u16>,
height: u16, 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, &mut self,
inner: Rect, inner: Rect,
message: &str, message: &str,

View File

@ -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_PREVIEW_FG: Color = Color::Rgb(150, 150, 150);
const DEFAULT_RESULT_LINE_NUMBER_FG: Color = Color::Yellow; 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>, results_block: Block<'b>,
entries: &'a [Entry], entries: &'a [Entry],
) -> List<'a> ) -> List<'a>

View File

@ -1,7 +1,7 @@
use std::collections::HashSet; use std::collections::HashSet;
use std::path::Path; use std::path::Path;
use ignore::{types::TypesBuilder, WalkBuilder}; use ignore::{overrides::Override, types::TypesBuilder, WalkBuilder};
use infer::Infer; use infer::Infer;
use lazy_static::lazy_static; 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 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<Override>,
) -> WalkBuilder {
let mut builder = WalkBuilder::new(path); let mut builder = WalkBuilder::new(path);
// ft-based filtering // 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.types(types_builder.build().unwrap());
builder.threads(n_threads); builder.threads(n_threads);
if let Some(ov) = overrides {
builder.overrides(ov);
}
builder builder
} }
pub(crate) fn get_file_size(path: &Path) -> Option<u64> { pub fn get_file_size(path: &Path) -> Option<u64> {
std::fs::metadata(path).ok().map(|m| m.len()) std::fs::metadata(path).ok().map(|m| m.len())
} }
#[derive(Debug)] #[derive(Debug)]
pub(crate) enum FileType { pub enum FileType {
Text, Text,
Image, Image,
Other, Other,
Unknown, Unknown,
} }
pub(crate) fn is_not_text(bytes: &[u8]) -> Option<bool> { pub fn is_not_text(bytes: &[u8]) -> Option<bool> {
let infer = Infer::new(); let infer = Infer::new();
match infer.get(bytes) { match infer.get(bytes) {
Some(t) => { Some(t) => {
@ -56,11 +63,11 @@ pub(crate) fn is_not_text(bytes: &[u8]) -> Option<bool> {
} }
} }
pub(crate) fn is_valid_utf8(bytes: &[u8]) -> bool { pub fn is_valid_utf8(bytes: &[u8]) -> bool {
std::str::from_utf8(bytes).is_ok() 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() path.extension()
.and_then(|ext| ext.to_str()) .and_then(|ext| ext.to_str())
.is_some_and(|ext| KNOWN_TEXT_FILE_EXTENSIONS.contains(ext)) .is_some_and(|ext| KNOWN_TEXT_FILE_EXTENSIONS.contains(ext))

View File

@ -1,4 +1,4 @@
pub(crate) fn sep_name_and_value_indices( pub fn sep_name_and_value_indices(
indices: &mut Vec<u32>, indices: &mut Vec<u32>,
name_len: u32, name_len: u32,
) -> (Vec<u32>, Vec<u32>, bool, bool) { ) -> (Vec<u32>, Vec<u32>, bool, bool) {

View File

@ -1,7 +1,7 @@
use lazy_static::lazy_static; use lazy_static::lazy_static;
use std::fmt::Write; 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; let mut i = start;
while !s.is_char_boundary(i) { while !s.is_char_boundary(i) {
i += 1; i += 1;
@ -9,7 +9,7 @@ pub(crate) fn next_char_boundary(s: &str, start: usize) -> usize {
i 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; let mut i = start;
while !s.is_char_boundary(i) { while !s.is_char_boundary(i) {
i -= 1; i -= 1;
@ -17,7 +17,7 @@ pub(crate) fn prev_char_boundary(s: &str, start: usize) -> usize {
i i
} }
pub(crate) fn slice_at_char_boundaries( pub fn slice_at_char_boundaries(
s: &str, s: &str,
start_byte_index: usize, start_byte_index: usize,
end_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)] ..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; let mut char_index = byte_index;
while !s.is_char_boundary(char_index) { while !s.is_char_boundary(char_index) {
char_index -= 1; char_index -= 1;
@ -65,7 +65,7 @@ const NULL_CHARACTER: char = '\x00';
const UNIT_SEPARATOR_CHARACTER: char = '\u{001F}'; const UNIT_SEPARATOR_CHARACTER: char = '\u{001F}';
const APPLICATION_PROGRAM_COMMAND_CHARACTER: char = '\u{009F}'; 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 output = String::new();
let mut idx = 0; let mut idx = 0;
@ -84,12 +84,10 @@ pub(crate) fn replace_nonprintable(input: &[u8], tab_width: usize) -> String {
output.push_str("\x0A"); output.push_str("\x0A");
} }
// ASCII control characters from 0x00 to 0x1F // ASCII control characters from 0x00 to 0x1F
NULL_CHARACTER..=UNIT_SEPARATOR_CHARACTER => { // + control characters from \u{007F} to \u{009F}
output.push(*NULL_SYMBOL) NULL_CHARACTER..=UNIT_SEPARATOR_CHARACTER
} | DELETE_CHARACTER..=APPLICATION_PROGRAM_COMMAND_CHARACTER => {
// control characters from \u{007F} to \u{009F} output.push(*NULL_SYMBOL);
DELETE_CHARACTER..=APPLICATION_PROGRAM_COMMAND_CHARACTER => {
output.push(*NULL_SYMBOL)
} }
// don't print BOMs // don't print BOMs
BOM_CHARACTER => {} BOM_CHARACTER => {}
@ -115,7 +113,7 @@ pub(crate) fn replace_nonprintable(input: &[u8], tab_width: usize) -> String {
/// based on a sample of its contents. /// based on a sample of its contents.
pub const PRINTABLE_ASCII_THRESHOLD: f32 = 0.7; 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; let mut printable = 0;
for &byte in buffer { for &byte in buffer {
if byte > 32 && byte < 127 { 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; const MAX_LINE_LENGTH: usize = 500;
pub(crate) fn preprocess_line(line: &str) -> String { pub fn preprocess_line(line: &str) -> String {
replace_nonprintable( replace_nonprintable(
{ {
if line.len() > MAX_LINE_LENGTH { 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 { if s.len() <= max_length {
return s.to_string(); return s.to_string();
} }

View File

@ -36,7 +36,7 @@ fn impl_cli_channel(ast: &syn::DeriveInput) -> TokenStream {
use clap::ValueEnum; use clap::ValueEnum;
#[derive(Debug, Clone, ValueEnum, Default, Copy)] #[derive(Debug, Clone, ValueEnum, Default, Copy)]
pub(crate) enum CliTvChannel { pub enum CliTvChannel {
#[default] #[default]
#(#cli_enum_variants),* #(#cli_enum_variants),*
} }
@ -67,7 +67,7 @@ fn impl_cli_channel(ast: &syn::DeriveInput) -> TokenStream {
#cli_enum #cli_enum
impl CliTvChannel { impl CliTvChannel {
pub(crate) fn to_channel(self) -> Box<dyn TelevisionChannel> { pub fn to_channel(self) -> Box<dyn TelevisionChannel> {
match self { match self {
#(#arms),* #(#arms),*
} }