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",
"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"

View File

@ -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"] }

View File

@ -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)

View File

@ -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()?;

View File

@ -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,

View File

@ -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,

View File

@ -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),

View File

@ -16,7 +16,7 @@ struct Alias {
value: String,
}
pub(crate) struct Channel {
pub struct Channel {
matcher: Nucleo<Alias>,
last_pattern: String,
file_icon: FileIcon,
@ -67,7 +67,7 @@ fn get_raw_aliases(shell: &str) -> Vec<String> {
}
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);

View File

@ -17,7 +17,7 @@ struct EnvVar {
}
#[allow(clippy::module_name_repetitions)]
pub(crate) struct Channel {
pub struct Channel {
matcher: Nucleo<EnvVar>,
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(|| {}),

View File

@ -15,18 +15,18 @@ use crate::{
};
use crate::{fuzzy::MATCHER, utils::strings::PRINTABLE_ASCII_THRESHOLD};
pub(crate) struct Channel {
pub struct Channel {
matcher: Nucleo<DirEntry>,
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<DirEntry>) {
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<DirEntry>) {
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(),

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;
pub(crate) struct Channel {
pub struct Channel {
matcher: Nucleo<String>,
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);

View File

@ -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<CandidateLine>,
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<CandidateLine>) {
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<CandidateLine>) {
line,
line_number,
);
// Send the line via the async channel
let _ = injector.push(
candidate,
|c, cols| {

View File

@ -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();

View File

@ -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<Self, config::ConfigError> {
pub fn new() -> Result<Self, config::ConfigError> {
//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<ProjectDirs> {
}
#[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 {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
@ -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<Key, String> {
pub fn parse_key(raw: &str) -> Result<Key, String> {
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<Key, String> {
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<Mode, HashMap<String, Style>>);
pub struct Styles(pub HashMap<Mode, HashMap<String, Style>>);
impl<'de> Deserialize<'de> for Styles {
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) =
line.split_at(line.to_lowercase().find("on ").unwrap_or(line.len()));
let foreground = process_color_string(foreground);

View File

@ -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<String>,
pub value: Option<String>,
@ -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}"));

View File

@ -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 {}",

View File

@ -17,7 +17,7 @@ use tokio::sync::mpsc;
use tracing::warn;
#[derive(Debug, Clone, Copy)]
pub(crate) enum Event<I> {
pub enum Event<I> {
Closed,
Input(I),
FocusLost,
@ -29,7 +29,7 @@ pub(crate) enum Event<I> {
#[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<Event<Key>>,
//tx: mpsc::UnboundedSender<Event<Key>>,
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,

View File

@ -1,7 +1,7 @@
use parking_lot::Mutex;
use std::ops::DerefMut;
pub(crate) struct LazyMutex<T> {
pub struct LazyMutex<T> {
inner: Mutex<Option<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| {
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(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());

View File

@ -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)]

View File

@ -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<Vec<(Style, String)>>),
SyntectHighlightedText(Vec<Vec<(Style, String)>>),
//Image(Box<dyn StatefulProtocol>),
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(),

View File

@ -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<Preview> {
pub fn preview(&self, entry: &Entry) -> Arc<Preview> {
Arc::new(Preview {
title: 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,
{
/// 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<T> {
pub fn push(&mut self, item: T) -> Option<T> {
// 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<String, Arc<Preview>>,
ring_set: RingSet<String>,
}
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<Arc<Preview>> {
pub fn get(&self, key: &str) -> Option<Arc<Preview>> {
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<Preview>) {
pub fn insert(&mut self, key: String, preview: Arc<Preview>) {
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<F>(
pub fn get_or_insert<F>(
&mut self,
key: String,
f: F,

View File

@ -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<Preview> {
pub fn preview(&mut self, entry: &Entry) -> Arc<Preview> {
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(
tree_string
.lines()
.map(std::borrow::ToOwned::to_owned)
.collect(),
),
}
}
Preview {
title: entry.name.clone(),
content: PreviewContent::PlainText(lines),
fn label<P: AsRef<Path>>(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: 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::previewers::{Preview, PreviewContent};
pub(crate) struct EnvVarPreviewer {
pub struct EnvVarPreviewer {
cache: HashMap<entry::Entry, Arc<Preview>>,
}
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<Preview> {
pub fn preview(&mut self, entry: &entry::Entry) -> Arc<Preview> {
// check if we have that preview in the cache
if let Some(preview) = self.cache.get(entry) {
return preview.clone();

View File

@ -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<Mutex<PreviewCache>>,
syntax_set: Arc<SyntaxSet>,
syntax_theme: Arc<Theme>,
@ -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<Preview> {
))
}
#[allow(dead_code)]
fn loading(title: &str) -> Arc<Preview> {
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};
#[derive(Debug)]
pub(crate) enum RenderingTask {
pub enum RenderingTask {
ClearScreen,
Render,
Resize(u16, u16),

View File

@ -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<UnboundedSender<Action>>,
config: Config,
channel: Box<dyn TelevisionChannel>,
@ -54,8 +54,8 @@ pub(crate) struct Television {
picker_view_offset: usize,
results_area_height: u32,
previewer: Previewer,
pub(crate) preview_scroll: Option<u16>,
pub(crate) preview_pane_height: u16,
pub preview_scroll: Option<u16>,
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<Entry> {
pub fn get_selected_entry(&self) -> Option<Entry> {
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<Action>,
) -> 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);

View File

@ -16,7 +16,7 @@ use tokio::task::JoinHandle;
use tracing::debug;
#[allow(dead_code)]
pub(crate) struct Tui<W>
pub struct Tui<W>
where
W: Write,
{
@ -30,7 +30,7 @@ impl<W> Tui<W>
where
W: Write,
{
pub(crate) fn new(writer: W) -> Result<Self> {
pub fn new(writer: W) -> Result<Self> {
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<Size> {
pub fn size(&self) -> Result<Size> {
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(())
}

View File

@ -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

View File

@ -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<StateChanged>;
/// 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();

View File

@ -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<InputRequest> {
pub fn to_input_request(evt: &CrosstermEvent) -> Option<InputRequest> {
use InputRequest::*;
use KeyCode::*;
match evt {

View File

@ -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<Rect>,
@ -26,7 +26,7 @@ pub(crate) struct Layout {
}
impl Layout {
pub(crate) fn new(
pub fn new(
results: Rect,
input: Rect,
preview_title: Option<Rect>,
@ -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 {

View File

@ -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<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,
inner: Rect,
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_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>

View File

@ -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<Override>,
) -> 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<u64> {
pub fn get_file_size(path: &Path) -> Option<u64> {
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<bool> {
pub fn is_not_text(bytes: &[u8]) -> Option<bool> {
let infer = Infer::new();
match infer.get(bytes) {
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()
}
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))

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>,
name_len: u32,
) -> (Vec<u32>, Vec<u32>, bool, bool) {

View File

@ -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();
}

View File

@ -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<dyn TelevisionChannel> {
pub fn to_channel(self) -> Box<dyn TelevisionChannel> {
match self {
#(#arms),*
}