fixing various issues

This commit is contained in:
alexpasmantier 2024-10-30 00:12:27 +01:00
parent d0d453fe97
commit e17cd18d42
20 changed files with 97 additions and 186 deletions

2
Cargo.lock generated
View File

@ -2673,7 +2673,7 @@ dependencies = [
[[package]]
name = "television"
version = "0.1.6"
version = "0.1.7"
dependencies = [
"anyhow",
"better-panic",

View File

@ -1,6 +1,6 @@
[package]
name = "television"
version = "0.1.6"
version = "0.1.7"
edition = "2021"
description = "The revolution will be televised."
license = "MIT"

37
TODO.md
View File

@ -1,58 +1,53 @@
# bugs
- [x] index out of bounds when resizing the terminal to a very small size
- [x] meta previews in cache are not terminal size aware
# tasks
- [x] preview navigation
- [x] add a way to open the selected file in the default editor (or maybe that
should be achieved using pipes?) --> use xargs for that
should be achieved using pipes?) --> use xargs for that
- [x] maybe filter out image types etc. for now
- [x] return selected entry on exit
- [x] piping output to another command
- [x] piping custom entries from stdin (e.g. `ls | tv`, what bout choosing
previewers in that case? Some AUTO mode?)
previewers in that case? Some AUTO mode?)
- [x] documentation
## improvements
- [x] async finder initialization
- [x] async finder search
- [x] use nucleo for env
- [ ] better keymaps
- [x] better keymaps
- [ ] mutualize placeholder previews in cache (really not a priority)
- [x] better abstractions for channels / separation / isolation so that others
can contribute new ones easily
- [ ] channel selection in the UI (separate menu or top panel or something)
can contribute new ones easily
- [x] channel selection in the UI (separate menu or top panel or something)
- [x] only render highlighted lines that are visible
- [x] only ever read a portion of the file for the temp preview
- [ ] make layout an attribute of the channel?
- [ ] profile using dyn Traits instead of an enum for channels (might degrade performance by storing on the heap)
- [x] profile using dyn Traits instead of an enum for channels (might degrade performance by storing on the heap)
- [x] I feel like the finder abstraction is a superfluous layer, maybe just use
the channel directly?
the channel directly?
- [x] support for images is implemented but do we really want that in the core?
it's quite heavy
it's quite heavy
- [x] shrink entry names that are too long (from the middle)
- [ ] make the preview toggleable
## feature ideas
- [ ] some sort of iterative fuzzy file explorer (preview contents of folders
on the right, enter to go in etc.) maybe with mixed previews of files and
folders
- [x] environment variables
- [x] aliases
- [ ] shell history
- [x] text
- [ ] text in documents (pdfs, archives, ...) (rga, adapters)
https://github.com/jrmuizel/pdf-extract
https://github.com/jrmuizel/pdf-extract
- [x] fd
- [ ] recent directories
- [ ] git (commits, branches, status, diff, ...)
- [ ] makefile commands
- [ ] remote files (s3, ...)
- [ ] custom actions as part of a channel (mappable)
- [ ] from one set of entries to another? (fuzzy-refine) maybe piping
tv with itself?
- [ ] add a way of copying the selected entry name/value to the clipboard
- [ ] 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)
- [x] add a way of copying the selected entry name/value to the clipboard
- [ ] have a keybinding to send all current entries to stdout
- [x] git repositories channel (crawl the filesystem for git repos)

View File

@ -1,55 +1,3 @@
/**
The general idea
rendering thread event thread main thread
receive event
send on `event_rx` receive `event_rx`
map to action
send on `action_tx`
receive `action_rx`
receive `render_rx` dispatch action
render components update components
*/
use std::sync::Arc;
use color_eyre::Result;

View File

@ -1,4 +1,5 @@
use devicons::FileIcon;
use directories::BaseDirs;
use ignore::overrides::OverrideBuilder;
use nucleo::{
pattern::{CaseMatching, Normalization},
@ -36,8 +37,9 @@ impl Channel {
None,
1,
);
let base_dirs = BaseDirs::new().unwrap();
let crawl_handle = tokio::spawn(crawl_for_repos(
std::env::home_dir().expect("Could not get home directory"),
base_dirs.home_dir().to_path_buf(),
matcher.injector(),
));
Channel {
@ -142,7 +144,9 @@ impl OnAir for Channel {
fn get_ignored_paths() -> Vec<PathBuf> {
let mut ignored_paths = Vec::new();
if let Some(home) = std::env::home_dir() {
if let Some(base_dirs) = BaseDirs::new() {
let home = base_dirs.home_dir();
#[cfg(target_os = "macos")]
{
ignored_paths.push(home.join("Library"));
@ -207,7 +211,7 @@ async fn crawl_for_repos(
// if the entry is a .git directory, add its parent to the list of git repos
if entry.path().ends_with(".git") {
let parent_path = preprocess_line(
&*entry.path().parent().unwrap().to_string_lossy(),
&entry.path().parent().unwrap().to_string_lossy(),
);
debug!("Found git repo: {:?}", parent_path);
let _ = injector.push(parent_path, |e, cols| {

View File

@ -9,7 +9,6 @@ use std::{
io::{BufRead, Read, Seek},
path::{Path, PathBuf},
sync::{atomic::AtomicUsize, Arc},
u32,
};
use tracing::{debug, warn};
@ -85,7 +84,7 @@ impl Channel {
break;
}
if let Some(injected_lines) =
try_inject_lines(injector.clone(), &current_dir, &path)
try_inject_lines(&injector, &current_dir, &path)
{
lines_in_mem += injected_lines;
}
@ -298,11 +297,9 @@ async fn crawl_for_candidates(
}
}
// try to inject the lines of the file
if let Some(injected_lines) = try_inject_lines(
injector.clone(),
&current_dir,
entry.path(),
) {
if let Some(injected_lines) =
try_inject_lines(&injector, &current_dir, entry.path())
{
lines_in_mem.fetch_add(
injected_lines,
std::sync::atomic::Ordering::Relaxed,
@ -316,7 +313,7 @@ async fn crawl_for_candidates(
}
fn try_inject_lines(
injector: Injector<CandidateLine>,
injector: &Injector<CandidateLine>,
current_dir: &PathBuf,
path: &Path,
) -> Option<usize> {
@ -353,7 +350,7 @@ fn try_inject_lines(
continue;
}
let candidate = CandidateLine::new(
path.strip_prefix(&current_dir)
path.strip_prefix(current_dir)
.unwrap_or(path)
.to_path_buf(),
line,

View File

@ -26,6 +26,7 @@ pub struct AppConfig {
pub config_dir: PathBuf,
}
#[allow(dead_code)]
#[derive(Clone, Debug, Default, Deserialize)]
pub struct Config {
#[allow(clippy::struct_field_names)]

View File

@ -138,6 +138,7 @@ impl PreviewCache {
}
/// Get the preview for the given key, or insert a new preview if it doesn't exist.
#[allow(dead_code)]
pub fn get_or_insert<F>(&mut self, key: String, f: F) -> Arc<Preview>
where
F: FnOnce() -> Preview,

View File

@ -2,8 +2,8 @@ use std::path::Path;
use std::sync::Arc;
use devicons::FileIcon;
use parking_lot::Mutex;
use termtree::Tree;
use tokio::sync::Mutex;
use crate::entry::Entry;
@ -23,22 +23,18 @@ impl DirectoryPreviewer {
}
pub async fn preview(&mut self, entry: &Entry) -> Arc<Preview> {
if let Some(preview) = self.cache.lock().await.get(&entry.name) {
if let Some(preview) = self.cache.lock().get(&entry.name) {
return preview;
}
let preview = meta::loading(&entry.name);
self.cache
.lock()
.await
.insert(entry.name.clone(), preview.clone());
let entry_c = entry.clone();
let cache = self.cache.clone();
tokio::spawn(async move {
let preview = Arc::new(build_tree_preview(&entry_c));
cache
.lock()
.await
.insert(entry_c.name.clone(), preview.clone());
cache.lock().insert(entry_c.name.clone(), preview.clone());
});
preview
}

View File

@ -1,11 +1,11 @@
use color_eyre::Result;
//use image::{ImageReader, Rgb};
//use ratatui_image::picker::Picker;
use parking_lot::Mutex;
use std::fs::File;
use std::io::{BufRead, BufReader, Read, Seek};
use std::path::{Path, PathBuf};
use std::sync::Arc;
use tokio::sync::Mutex;
use syntect::{
highlighting::{Theme, ThemeSet},
@ -53,7 +53,7 @@ impl FilePreviewer {
let path_buf = PathBuf::from(&entry.name);
// do we have a preview in cache for that entry?
if let Some(preview) = self.cache.lock().await.get(&entry.name) {
if let Some(preview) = self.cache.lock().get(&entry.name) {
return preview.clone();
}
debug!("No preview in cache for {:?}", entry.name);
@ -181,7 +181,7 @@ impl FilePreviewer {
"Successfully computed highlights for {:?}",
entry_c.name
);
cache.lock().await.insert(
cache.lock().insert(
entry_c.name.clone(),
Arc::new(Preview::new(
entry_c.name,
@ -242,7 +242,7 @@ impl FilePreviewer {
}
async fn cache_preview(&mut self, key: String, preview: Arc<Preview>) {
self.cache.lock().await.insert(key, preview);
self.cache.lock().insert(key, preview);
}
}

View File

@ -84,10 +84,10 @@ pub async fn render(
RenderingTask::Render => {
let mut television = television.lock().await;
if let Ok(size) = tui.size() {
// Ratatui uses u16s to encode terminal dimensions and its
// Ratatui uses `u16`s to encode terminal dimensions and its
// content for each terminal cell is stored linearly in a
// buffer with a u16 index which means we can't support
// terminal areas larger than u16::MAX.
// buffer with a `u16` index which means we can't support
// terminal areas larger than `u16::MAX`.
if size.width.checked_mul(size.height).is_some() {
tui.terminal.draw(|frame| {
if let Err(err) = television.draw(frame, frame.area()) {

View File

@ -86,7 +86,6 @@ impl Television {
UnitChannel::from(&self.channel)
}
/// FIXME: this needs rework
pub fn change_channel(&mut self, channel: TelevisionChannel) {
self.reset_preview_scroll();
self.reset_picker_selection();
@ -362,15 +361,6 @@ impl Television {
Ok(None)
}
fn reset_screen(&mut self) {
self.reset_preview_scroll();
self.reset_picker_selection();
self.reset_picker_input();
self.current_pattern = EMPTY_STRING.to_string();
self.channel.find(EMPTY_STRING);
self.remote_control.find(EMPTY_STRING);
}
/// Render the television on the screen.
///
/// # Arguments

View File

@ -1,4 +1,4 @@
use ratatui::style::{Color, Style};
use ratatui::style::Color;
pub(crate) mod help;
pub mod input;
@ -11,24 +11,5 @@ pub mod preview;
mod remote_control;
pub mod results;
pub mod spinner;
// input
//const DEFAULT_INPUT_FG: Color = Color::Rgb(200, 200, 200);
//const DEFAULT_RESULTS_COUNT_FG: Color = Color::Rgb(150, 150, 150);
// preview
//const DEFAULT_PREVIEW_TITLE_FG: Color = Color::Blue;
//const DEFAULT_SELECTED_PREVIEW_BG: Color = Color::Rgb(50, 50, 50);
//const DEFAULT_PREVIEW_CONTENT_FG: Color = Color::Rgb(150, 150, 180);
//const DEFAULT_PREVIEW_GUTTER_FG: Color = Color::Rgb(70, 70, 70);
//const DEFAULT_PREVIEW_GUTTER_SELECTED_FG: Color = Color::Rgb(255, 150, 150);
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
// keeping the focus feature at all?
// if focused {
// Style::default().fg(Color::Green)
// } else {
// Style::default().fg(Color::Blue)
// }
}
pub const BORDER_COLOR: Color = Color::Blue;

View File

@ -1,7 +1,7 @@
use crate::channels::OnAir;
use crate::television::Television;
use crate::ui::get_border_style;
use crate::ui::layout::Layout;
use crate::ui::BORDER_COLOR;
use color_eyre::eyre::Result;
use ratatui::layout::{
Alignment, Constraint, Direction, Layout as RatatuiLayout,
@ -422,7 +422,7 @@ impl Television {
.title_top(Line::from(" Pattern ").alignment(Alignment::Center))
.borders(Borders::ALL)
.border_type(BorderType::Rounded)
.border_style(get_border_style(false))
.border_style(Style::default().fg(BORDER_COLOR))
.style(Style::default());
let input_block_inner = input_block.inner(layout.input);

View File

@ -3,7 +3,7 @@ use crate::previewers::{
Preview, PreviewContent, FILE_TOO_LARGE_MSG, PREVIEW_NOT_SUPPORTED_MSG,
};
use crate::television::Television;
use crate::ui::get_border_style;
use crate::ui::BORDER_COLOR;
use crate::ui::layout::Layout;
use crate::utils::strings::{shrink_with_ellipsis, EMPTY_STRING};
use color_eyre::eyre::Result;
@ -55,7 +55,7 @@ impl Television {
Block::default()
.borders(Borders::ALL)
.border_type(BorderType::Rounded)
.border_style(get_border_style(false)),
.border_style(Style::default().fg(BORDER_COLOR)),
)
.alignment(Alignment::Left);
f.render_widget(preview_title, layout.preview_title);
@ -73,7 +73,7 @@ impl Television {
.title_top(Line::from(" Preview ").alignment(Alignment::Center))
.borders(Borders::ALL)
.border_type(BorderType::Rounded)
.border_style(get_border_style(false))
.border_style(Style::default().fg(BORDER_COLOR))
.style(Style::default())
.padding(Padding::right(1));

View File

@ -1,6 +1,6 @@
use crate::channels::OnAir;
use crate::television::Television;
use crate::ui::get_border_style;
use crate::ui::BORDER_COLOR;
use crate::ui::logo::build_remote_logo_paragraph;
use crate::ui::mode::mode_color;
use crate::ui::results::{build_results_list, ResultsListColors};
@ -41,7 +41,7 @@ impl Television {
let rc_block = Block::default()
.borders(Borders::ALL)
.border_type(BorderType::Rounded)
.border_style(get_border_style(false))
.border_style(Style::default().fg(BORDER_COLOR))
.style(Style::default())
.padding(Padding::right(1));
@ -81,7 +81,7 @@ impl Television {
)
.borders(Borders::ALL)
.border_type(BorderType::Rounded)
.border_style(get_border_style(false))
.border_style(Style::default().fg(BORDER_COLOR))
.style(Style::default());
let input_block_inner = input_block.inner(*area);

View File

@ -1,12 +1,12 @@
use crate::channels::OnAir;
use crate::entry::Entry;
use crate::television::Television;
use crate::ui::get_border_style;
use crate::ui::layout::Layout;
use crate::ui::BORDER_COLOR;
use crate::utils::strings::{next_char_boundary, slice_at_char_boundaries};
use color_eyre::eyre::Result;
use ratatui::layout::Alignment;
use ratatui::prelude::{Color, Line, Span, Style, Stylize};
use ratatui::prelude::{Color, Line, Span, Style};
use ratatui::widgets::{
Block, BorderType, Borders, List, ListDirection, Padding,
};
@ -37,6 +37,7 @@ impl Default for ResultsListColors {
}
}
#[allow(dead_code)]
impl ResultsListColors {
pub fn result_name_fg(mut self, color: Color) -> Self {
self.result_name_fg = color;
@ -179,7 +180,7 @@ impl Television {
.title_top(Line::from(" Results ").alignment(Alignment::Center))
.borders(Borders::ALL)
.border_type(BorderType::Rounded)
.border_style(get_border_style(false))
.border_style(Style::default().fg(BORDER_COLOR))
.style(Style::default())
.padding(Padding::right(1));

View File

@ -71,10 +71,6 @@ pub fn is_not_text(bytes: &[u8]) -> Option<bool> {
}
}
pub fn is_valid_utf8(bytes: &[u8]) -> bool {
std::str::from_utf8(bytes).is_ok()
}
pub fn is_known_text_extension(path: &Path) -> bool {
path.extension()
.and_then(|ext| ext.to_str())

View File

@ -68,7 +68,7 @@ const NULL_CHARACTER: char = '\x00';
const UNIT_SEPARATOR_CHARACTER: char = '\u{001F}';
const APPLICATION_PROGRAM_COMMAND_CHARACTER: char = '\u{009F}';
pub fn replace_nonprintable(input: &[u8], tab_width: usize) -> String {
pub fn replace_non_printable(input: &[u8], tab_width: usize) -> String {
let mut output = String::new();
let mut idx = 0;
@ -130,7 +130,7 @@ pub fn proportion_of_printable_ascii_characters(buffer: &[u8]) -> f32 {
const MAX_LINE_LENGTH: usize = 300;
pub fn preprocess_line(line: &str) -> String {
replace_nonprintable(
replace_non_printable(
{
if line.len() > MAX_LINE_LENGTH {
slice_up_to_char_boundary(line, MAX_LINE_LENGTH)
@ -160,48 +160,48 @@ pub fn shrink_with_ellipsis(s: &str, max_length: usize) -> String {
mod tests {
use super::*;
fn test_replace_nonprintable(input: &str, expected: &str) {
let actual = replace_nonprintable(input.as_bytes(), 2);
fn test_replace_non_printable(input: &str, expected: &str) {
let actual = replace_non_printable(input.as_bytes(), 2);
assert_eq!(actual, expected);
}
#[test]
fn test_replace_nonprintable_ascii() {
test_replace_nonprintable("Hello, World!", "Hello, World!");
fn test_replace_non_printable_ascii() {
test_replace_non_printable("Hello, World!", "Hello, World!");
}
#[test]
fn test_replace_nonprintable_tab() {
test_replace_nonprintable("Hello\tWorld!", "Hello World!");
test_replace_nonprintable(
fn test_replace_non_printable_tab() {
test_replace_non_printable("Hello\tWorld!", "Hello World!");
test_replace_non_printable(
" -- AND
", " -- AND",
)
}
#[test]
fn test_replace_nonprintable_line_feed() {
test_replace_nonprintable("Hello\nWorld!", "HelloWorld!");
fn test_replace_non_printable_line_feed() {
test_replace_non_printable("Hello\nWorld!", "HelloWorld!");
}
#[test]
fn test_replace_nonprintable_null() {
test_replace_nonprintable("Hello\x00World!", "Hello␀World!");
test_replace_nonprintable("Hello World!\0", "Hello World!␀");
fn test_replace_non_printable_null() {
test_replace_non_printable("Hello\x00World!", "Hello␀World!");
test_replace_non_printable("Hello World!\0", "Hello World!␀");
}
#[test]
fn test_replace_nonprintable_delete() {
test_replace_nonprintable("Hello\x7FWorld!", "Hello␀World!");
fn test_replace_non_printable_delete() {
test_replace_non_printable("Hello\x7FWorld!", "Hello␀World!");
}
#[test]
fn test_replace_nonprintable_bom() {
test_replace_nonprintable("Hello\u{FEFF}World!", "HelloWorld!");
fn test_replace_non_printable_bom() {
test_replace_non_printable("Hello\u{FEFF}World!", "HelloWorld!");
}
#[test]
fn test_replace_nonprintable_start_txt() {
test_replace_nonprintable("Àì", "Àì␀");
fn test_replace_non_printable_start_txt() {
test_replace_non_printable("Àì", "Àì␀");
}
}

View File

@ -34,6 +34,7 @@ pub fn compute_highlights_for_path(
Ok(highlighted_lines)
}
#[allow(dead_code)]
pub fn compute_highlights_for_line<'a>(
line: &'a str,
syntax_set: &SyntaxSet,