mirror of
https://github.com/alexpasmantier/television.git
synced 2025-06-07 12:05:34 +00:00
fixing various issues
This commit is contained in:
parent
d0d453fe97
commit
e17cd18d42
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -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",
|
||||||
|
@ -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
37
TODO.md
@ -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)
|
|
||||||
|
@ -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;
|
||||||
|
@ -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| {
|
||||||
|
@ -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(), ¤t_dir, &path)
|
try_inject_lines(&injector, ¤t_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, ¤t_dir, entry.path())
|
||||||
¤t_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(¤t_dir)
|
path.strip_prefix(current_dir)
|
||||||
.unwrap_or(path)
|
.unwrap_or(path)
|
||||||
.to_path_buf(),
|
.to_path_buf(),
|
||||||
line,
|
line,
|
||||||
|
@ -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)]
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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()) {
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
@ -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,
|
||||||
));
|
));
|
||||||
|
@ -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));
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
@ -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));
|
||||||
|
|
||||||
|
@ -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())
|
||||||
|
@ -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("Àì", "Àì␀");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user