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]] [[package]]
name = "television" name = "television"
version = "0.1.6" version = "0.1.7"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"better-panic", "better-panic",

View File

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

37
TODO.md
View File

@ -1,58 +1,53 @@
# bugs # bugs
- [x] index out of bounds when resizing the terminal to a very small size - [x] index out of bounds when resizing the terminal to a very small size
- [x] meta previews in cache are not terminal size aware - [x] meta previews in cache are not terminal size aware
# tasks # tasks
- [x] preview navigation - [x] preview navigation
- [x] add a way to open the selected file in the default editor (or maybe that - [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] maybe filter out image types etc. for now
- [x] return selected entry on exit - [x] return selected entry on exit
- [x] piping output to another command - [x] piping output to another command
- [x] piping custom entries from stdin (e.g. `ls | tv`, what bout choosing - [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 - [x] documentation
## improvements ## improvements
- [x] async finder initialization - [x] async finder initialization
- [x] async finder search - [x] async finder search
- [x] use nucleo for env - [x] use nucleo for env
- [ ] better keymaps - [x] better keymaps
- [ ] mutualize placeholder previews in cache (really not a priority) - [ ] mutualize placeholder previews in cache (really not a priority)
- [x] better abstractions for channels / separation / isolation so that others - [x] better abstractions for channels / separation / isolation so that others
can contribute new ones easily can contribute new ones easily
- [ ] channel selection in the UI (separate menu or top panel or something) - [x] channel selection in the UI (separate menu or top panel or something)
- [x] only render highlighted lines that are visible - [x] only render highlighted lines that are visible
- [x] only ever read a portion of the file for the temp preview - [x] only ever read a portion of the file for the temp preview
- [ ] make layout an attribute of the channel? - [x] profile using dyn Traits instead of an enum for channels (might degrade performance by storing on the heap)
- [ ] 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 - [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? - [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) - [x] shrink entry names that are too long (from the middle)
- [ ] make the preview toggleable
## feature ideas ## 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] environment variables
- [x] aliases - [x] aliases
- [ ] shell history - [ ] shell history
- [x] text - [x] text
- [ ] text in documents (pdfs, archives, ...) (rga, adapters) - [ ] text in documents (pdfs, archives, ...) (rga, adapters)
https://github.com/jrmuizel/pdf-extract https://github.com/jrmuizel/pdf-extract
- [x] fd - [x] fd
- [ ] recent directories - [ ] recent directories
- [ ] git (commits, branches, status, diff, ...) - [ ] git (commits, branches, status, diff, ...)
- [ ] makefile commands - [ ] makefile commands
- [ ] remote files (s3, ...) - [ ] remote files (s3, ...)
- [ ] custom actions as part of a channel (mappable) - [ ] custom actions as part of a channel (mappable)
- [ ] from one set of entries to another? (fuzzy-refine) maybe piping - [x] add a way of copying the selected entry name/value to the clipboard
tv with itself? - [ ] have a keybinding to send all current entries to stdout
- [ ] add a way of copying the selected entry name/value to the clipboard - [x] git repositories channel (crawl the filesystem for git repos)
- [ ] have a keybind to send all current entries to stdout ... oorrrrr to another channel??
- [ ] action menu on the bottom: send to channel, copy to clipboard, send to stdout, ... maybe with tab to navigate
between possible actions (defined per channel, not all channels can pipe to all channels)
- [ ] git repositories channel (crawl the filesystem for git repos)

View File

@ -1,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 std::sync::Arc;
use color_eyre::Result; use color_eyre::Result;

View File

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

View File

@ -9,7 +9,6 @@ use std::{
io::{BufRead, Read, Seek}, io::{BufRead, Read, Seek},
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::{atomic::AtomicUsize, Arc}, sync::{atomic::AtomicUsize, Arc},
u32,
}; };
use tracing::{debug, warn}; use tracing::{debug, warn};
@ -85,7 +84,7 @@ impl Channel {
break; break;
} }
if let Some(injected_lines) = 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; lines_in_mem += injected_lines;
} }
@ -183,7 +182,7 @@ impl OnAir for Channel {
.matched_items( .matched_items(
offset offset
..(num_entries + offset) ..(num_entries + offset)
.min(snapshot.matched_item_count()), .min(snapshot.matched_item_count()),
) )
.map(move |item| { .map(move |item| {
snapshot.pattern().column_pattern(0).indices( snapshot.pattern().column_pattern(0).indices(
@ -202,11 +201,11 @@ impl OnAir for Channel {
display_path.clone() + &item.data.line_number.to_string(), display_path.clone() + &item.data.line_number.to_string(),
PreviewType::Files, PreviewType::Files,
) )
.with_display_name(display_path) .with_display_name(display_path)
.with_value(line) .with_value(line)
.with_value_match_ranges(indices.map(|i| (i, i + 1)).collect()) .with_value_match_ranges(indices.map(|i| (i, i + 1)).collect())
.with_icon(FileIcon::from(item.data.path.as_path())) .with_icon(FileIcon::from(item.data.path.as_path()))
.with_line_number(item.data.line_number) .with_line_number(item.data.line_number)
}) })
.collect() .collect()
} }
@ -298,11 +297,9 @@ async fn crawl_for_candidates(
} }
} }
// try to inject the lines of the file // try to inject the lines of the file
if let Some(injected_lines) = try_inject_lines( if let Some(injected_lines) =
injector.clone(), try_inject_lines(&injector, &current_dir, entry.path())
&current_dir, {
entry.path(),
) {
lines_in_mem.fetch_add( lines_in_mem.fetch_add(
injected_lines, injected_lines,
std::sync::atomic::Ordering::Relaxed, std::sync::atomic::Ordering::Relaxed,
@ -316,7 +313,7 @@ async fn crawl_for_candidates(
} }
fn try_inject_lines( fn try_inject_lines(
injector: Injector<CandidateLine>, injector: &Injector<CandidateLine>,
current_dir: &PathBuf, current_dir: &PathBuf,
path: &Path, path: &Path,
) -> Option<usize> { ) -> Option<usize> {
@ -330,7 +327,7 @@ fn try_inject_lines(
if (bytes_read == 0) if (bytes_read == 0)
|| is_not_text(&buffer).unwrap_or(false) || is_not_text(&buffer).unwrap_or(false)
|| proportion_of_printable_ascii_characters(&buffer) || proportion_of_printable_ascii_characters(&buffer)
< PRINTABLE_ASCII_THRESHOLD < PRINTABLE_ASCII_THRESHOLD
{ {
return None; return None;
} }
@ -353,7 +350,7 @@ fn try_inject_lines(
continue; continue;
} }
let candidate = CandidateLine::new( let candidate = CandidateLine::new(
path.strip_prefix(&current_dir) path.strip_prefix(current_dir)
.unwrap_or(path) .unwrap_or(path)
.to_path_buf(), .to_path_buf(),
line, line,

View File

@ -26,6 +26,7 @@ pub struct AppConfig {
pub config_dir: PathBuf, pub config_dir: PathBuf,
} }
#[allow(dead_code)]
#[derive(Clone, Debug, Default, Deserialize)] #[derive(Clone, Debug, Default, Deserialize)]
pub struct Config { pub struct Config {
#[allow(clippy::struct_field_names)] #[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. /// 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> pub fn get_or_insert<F>(&mut self, key: String, f: F) -> Arc<Preview>
where where
F: FnOnce() -> Preview, F: FnOnce() -> Preview,

View File

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

View File

@ -1,11 +1,11 @@
use color_eyre::Result; use color_eyre::Result;
//use image::{ImageReader, Rgb}; //use image::{ImageReader, Rgb};
//use ratatui_image::picker::Picker; //use ratatui_image::picker::Picker;
use parking_lot::Mutex;
use std::fs::File; use std::fs::File;
use std::io::{BufRead, BufReader, Read, Seek}; use std::io::{BufRead, BufReader, Read, Seek};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::Arc; use std::sync::Arc;
use tokio::sync::Mutex;
use syntect::{ use syntect::{
highlighting::{Theme, ThemeSet}, highlighting::{Theme, ThemeSet},
@ -53,7 +53,7 @@ impl FilePreviewer {
let path_buf = PathBuf::from(&entry.name); let path_buf = PathBuf::from(&entry.name);
// do we have a preview in cache for that entry? // 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(); return preview.clone();
} }
debug!("No preview in cache for {:?}", entry.name); debug!("No preview in cache for {:?}", entry.name);
@ -181,7 +181,7 @@ impl FilePreviewer {
"Successfully computed highlights for {:?}", "Successfully computed highlights for {:?}",
entry_c.name entry_c.name
); );
cache.lock().await.insert( cache.lock().insert(
entry_c.name.clone(), entry_c.name.clone(),
Arc::new(Preview::new( Arc::new(Preview::new(
entry_c.name, entry_c.name,
@ -242,7 +242,7 @@ impl FilePreviewer {
} }
async fn cache_preview(&mut self, key: String, preview: Arc<Preview>) { 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 => { RenderingTask::Render => {
let mut television = television.lock().await; let mut television = television.lock().await;
if let Ok(size) = tui.size() { 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 // content for each terminal cell is stored linearly in a
// buffer with a u16 index which means we can't support // buffer with a `u16` index which means we can't support
// terminal areas larger than u16::MAX. // terminal areas larger than `u16::MAX`.
if size.width.checked_mul(size.height).is_some() { if size.width.checked_mul(size.height).is_some() {
tui.terminal.draw(|frame| { tui.terminal.draw(|frame| {
if let Err(err) = television.draw(frame, frame.area()) { if let Err(err) = television.draw(frame, frame.area()) {

View File

@ -86,7 +86,6 @@ impl Television {
UnitChannel::from(&self.channel) UnitChannel::from(&self.channel)
} }
/// FIXME: this needs rework
pub fn change_channel(&mut self, channel: TelevisionChannel) { pub fn change_channel(&mut self, channel: TelevisionChannel) {
self.reset_preview_scroll(); self.reset_preview_scroll();
self.reset_picker_selection(); self.reset_picker_selection();
@ -362,15 +361,6 @@ impl Television {
Ok(None) 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. /// Render the television on the screen.
/// ///
/// # Arguments /// # Arguments

View File

@ -1,4 +1,4 @@
use ratatui::style::{Color, Style}; use ratatui::style::Color;
pub(crate) mod help; pub(crate) mod help;
pub mod input; pub mod input;
@ -11,24 +11,5 @@ pub mod preview;
mod remote_control; mod remote_control;
pub mod results; pub mod results;
pub mod spinner; 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 { pub const BORDER_COLOR: Color = Color::Blue;
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)
// }
}

View File

@ -1,7 +1,7 @@
use crate::channels::OnAir; use crate::channels::OnAir;
use crate::television::Television; use crate::television::Television;
use crate::ui::get_border_style;
use crate::ui::layout::Layout; use crate::ui::layout::Layout;
use crate::ui::BORDER_COLOR;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use ratatui::layout::{ use ratatui::layout::{
Alignment, Constraint, Direction, Layout as RatatuiLayout, Alignment, Constraint, Direction, Layout as RatatuiLayout,
@ -422,7 +422,7 @@ impl Television {
.title_top(Line::from(" Pattern ").alignment(Alignment::Center)) .title_top(Line::from(" Pattern ").alignment(Alignment::Center))
.borders(Borders::ALL) .borders(Borders::ALL)
.border_type(BorderType::Rounded) .border_type(BorderType::Rounded)
.border_style(get_border_style(false)) .border_style(Style::default().fg(BORDER_COLOR))
.style(Style::default()); .style(Style::default());
let input_block_inner = input_block.inner(layout.input); let input_block_inner = input_block.inner(layout.input);
@ -454,7 +454,7 @@ impl Television {
.fg(crate::television::DEFAULT_INPUT_FG) .fg(crate::television::DEFAULT_INPUT_FG)
.bold(), .bold(),
)) ))
.block(arrow_block); .block(arrow_block);
f.render_widget(arrow, inner_input_chunks[0]); f.render_widget(arrow, inner_input_chunks[0]);
let interactive_input_block = Block::default(); let interactive_input_block = Block::default();
@ -497,8 +497,8 @@ impl Television {
.fg(crate::television::DEFAULT_RESULTS_COUNT_FG) .fg(crate::television::DEFAULT_RESULTS_COUNT_FG)
.italic(), .italic(),
)) ))
.block(result_count_block) .block(result_count_block)
.alignment(Alignment::Right); .alignment(Alignment::Right);
f.render_widget(result_count_paragraph, inner_input_chunks[2]); f.render_widget(result_count_paragraph, inner_input_chunks[2]);
// Make the cursor visible and ask tui-rs to put it at the // Make the cursor visible and ask tui-rs to put it at the
@ -507,9 +507,9 @@ impl Television {
// Put cursor past the end of the input text // Put cursor past the end of the input text
inner_input_chunks[1].x inner_input_chunks[1].x
+ u16::try_from( + u16::try_from(
self.results_picker.input.visual_cursor().max(scroll) self.results_picker.input.visual_cursor().max(scroll)
- scroll, - scroll,
)?, )?,
// Move one line down, from the border to the input line // Move one line down, from the border to the input line
inner_input_chunks[1].y, inner_input_chunks[1].y,
)); ));

View File

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

View File

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

View File

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

View File

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

View File

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