mirror of
https://github.com/alexpasmantier/television.git
synced 2025-07-20 09:00:00 +00:00
new channel git-repos and previews
This commit is contained in:
parent
d2213af480
commit
590fe14ee5
7
Cargo.lock
generated
7
Cargo.lock
generated
@ -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"
|
||||||
|
11
Cargo.toml
11
Cargo.toml
@ -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"] }
|
||||||
|
1
TODO.md
1
TODO.md
@ -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)
|
||||||
|
4
build.rs
4
build.rs
@ -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()?;
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
|
@ -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),
|
||||||
|
@ -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);
|
||||||
|
@ -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(|| {}),
|
||||||
|
@ -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(),
|
||||||
|
161
crates/television/channels/git_repos.rs
Normal file
161
crates/television/channels/git_repos.rs
Normal 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
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
@ -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);
|
||||||
|
@ -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| {
|
||||||
|
@ -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();
|
||||||
|
@ -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);
|
||||||
|
@ -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}"));
|
||||||
|
@ -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 {}",
|
||||||
|
@ -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,
|
||||||
|
@ -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)
|
||||||
})
|
})
|
||||||
|
@ -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());
|
||||||
|
@ -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)]
|
||||||
|
@ -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(),
|
||||||
|
@ -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()),
|
||||||
|
@ -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,
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
@ -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))
|
||||||
}
|
}
|
||||||
|
@ -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),
|
||||||
|
@ -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);
|
||||||
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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();
|
||||||
|
@ -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 {
|
||||||
|
@ -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 {
|
||||||
|
@ -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,
|
||||||
|
@ -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>
|
||||||
|
@ -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))
|
||||||
|
@ -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) {
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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),*
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user