mirror of
https://github.com/alexpasmantier/television.git
synced 2025-06-07 03:55:23 +00:00
refactor(ui): communicate ui state to tv using channels
This commit is contained in:
parent
e2a0fb2047
commit
d8703ce0e6
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -1696,7 +1696,6 @@ dependencies = [
|
||||
"gag",
|
||||
"human-panic",
|
||||
"ignore",
|
||||
"lazy_static",
|
||||
"nom",
|
||||
"nucleo",
|
||||
"parking_lot",
|
||||
|
@ -34,7 +34,6 @@ anyhow = "1.0"
|
||||
base64 = "0.22.1"
|
||||
directories = "6.0"
|
||||
devicons = "0.6"
|
||||
lazy_static = "1.5"
|
||||
tokio = { version = "1.43", features = ["full"] }
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
|
@ -8,6 +8,7 @@ use crate::channels::entry::Entry;
|
||||
use crate::channels::TelevisionChannel;
|
||||
use crate::config::{parse_key, Config};
|
||||
use crate::keymap::Keymap;
|
||||
use crate::render::UiState;
|
||||
use crate::television::{Mode, Television};
|
||||
use crate::{
|
||||
action::Action,
|
||||
@ -37,6 +38,9 @@ pub struct App {
|
||||
event_abort_tx: mpsc::UnboundedSender<()>,
|
||||
/// A sender channel for rendering tasks.
|
||||
render_tx: mpsc::UnboundedSender<RenderingTask>,
|
||||
/// A channel that listens to UI updates.
|
||||
ui_state_rx: mpsc::UnboundedReceiver<UiState>,
|
||||
ui_state_tx: mpsc::UnboundedSender<UiState>,
|
||||
}
|
||||
|
||||
/// The outcome of an action.
|
||||
@ -104,6 +108,7 @@ impl App {
|
||||
.collect(),
|
||||
)?;
|
||||
debug!("{:?}", keymap);
|
||||
let (ui_state_tx, ui_state_rx) = mpsc::unbounded_channel();
|
||||
let television =
|
||||
Television::new(action_tx.clone(), channel, config, input);
|
||||
|
||||
@ -118,6 +123,8 @@ impl App {
|
||||
event_rx,
|
||||
event_abort_tx,
|
||||
render_tx,
|
||||
ui_state_rx,
|
||||
ui_state_tx,
|
||||
})
|
||||
}
|
||||
|
||||
@ -145,9 +152,10 @@ impl App {
|
||||
debug!("Starting rendering loop");
|
||||
let (render_tx, render_rx) = mpsc::unbounded_channel();
|
||||
self.render_tx = render_tx.clone();
|
||||
let ui_state_tx = self.ui_state_tx.clone();
|
||||
let action_tx_r = self.action_tx.clone();
|
||||
let rendering_task = tokio::spawn(async move {
|
||||
render(render_rx, action_tx_r, is_output_tty).await
|
||||
render(render_rx, action_tx_r, ui_state_tx, is_output_tty).await
|
||||
});
|
||||
self.action_tx.send(Action::Render)?;
|
||||
|
||||
@ -298,9 +306,14 @@ impl App {
|
||||
self.render_tx.send(RenderingTask::Resize(w, h))?;
|
||||
}
|
||||
Action::Render => {
|
||||
// forward to the rendering task
|
||||
self.render_tx.send(RenderingTask::Render(
|
||||
self.television.dump_context(),
|
||||
Box::new(self.television.dump_context()),
|
||||
))?;
|
||||
// update the television UI state with the previous frame
|
||||
if let Ok(ui_state) = self.ui_state_rx.try_recv() {
|
||||
self.television.update_ui_state(ui_state);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ use std::io::{BufRead, BufReader};
|
||||
use std::process::Stdio;
|
||||
|
||||
use anyhow::Result;
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use rustc_hash::{FxBuildHasher, FxHashSet};
|
||||
use tracing::debug;
|
||||
@ -64,13 +63,10 @@ impl From<CableChannelPrototype> for Channel {
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref BUILTIN_PREVIEW_RE: Regex = Regex::new(r"^:(\w+):$").unwrap();
|
||||
}
|
||||
|
||||
fn parse_preview_kind(command: &PreviewCommand) -> Result<PreviewKind> {
|
||||
debug!("Parsing preview kind for command: {:?}", command);
|
||||
if let Some(captures) = BUILTIN_PREVIEW_RE.captures(&command.command) {
|
||||
let re = Regex::new(r"^\:(\w+)\:$").unwrap();
|
||||
if let Some(captures) = re.captures(&command.command) {
|
||||
let preview_type = PreviewType::try_from(&captures[1])?;
|
||||
Ok(PreviewKind::Builtin(preview_type))
|
||||
} else {
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::channels::entry::{Entry, PreviewCommand, PreviewType};
|
||||
use crate::channels::{OnAir, TelevisionChannel};
|
||||
use crate::matcher::{config::Config, injector::Injector, Matcher};
|
||||
use crate::utils::files::{walk_builder, DEFAULT_NUM_THREADS};
|
||||
use crate::utils::files::{get_default_num_threads, walk_builder};
|
||||
use devicons::FileIcon;
|
||||
use rustc_hash::{FxBuildHasher, FxHashSet};
|
||||
use std::collections::HashSet;
|
||||
@ -151,7 +151,7 @@ async fn load_dirs(paths: Vec<PathBuf>, injector: Injector<String>) {
|
||||
}
|
||||
let current_dir = std::env::current_dir().unwrap();
|
||||
let mut builder =
|
||||
walk_builder(&paths[0], *DEFAULT_NUM_THREADS, None, None);
|
||||
walk_builder(&paths[0], get_default_num_threads(), None, None);
|
||||
paths[1..].iter().for_each(|path| {
|
||||
builder.add(path);
|
||||
});
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::channels::entry::{Entry, PreviewType};
|
||||
use crate::channels::{OnAir, TelevisionChannel};
|
||||
use crate::matcher::{config::Config, injector::Injector, Matcher};
|
||||
use crate::utils::files::{walk_builder, DEFAULT_NUM_THREADS};
|
||||
use crate::utils::files::{get_default_num_threads, walk_builder};
|
||||
use devicons::FileIcon;
|
||||
use rustc_hash::{FxBuildHasher, FxHashSet};
|
||||
use std::collections::HashSet;
|
||||
@ -157,7 +157,7 @@ async fn load_files(paths: Vec<PathBuf>, injector: Injector<String>) {
|
||||
}
|
||||
let current_dir = std::env::current_dir().unwrap();
|
||||
let mut builder =
|
||||
walk_builder(&paths[0], *DEFAULT_NUM_THREADS, None, None);
|
||||
walk_builder(&paths[0], get_default_num_threads(), None, None);
|
||||
paths[1..].iter().for_each(|path| {
|
||||
builder.add(path);
|
||||
});
|
||||
|
@ -1,7 +1,6 @@
|
||||
use devicons::FileIcon;
|
||||
use directories::BaseDirs;
|
||||
use ignore::overrides::OverrideBuilder;
|
||||
use lazy_static::lazy_static;
|
||||
use rustc_hash::{FxBuildHasher, FxHashSet};
|
||||
use std::collections::HashSet;
|
||||
use std::path::PathBuf;
|
||||
@ -11,13 +10,14 @@ use tracing::debug;
|
||||
use crate::channels::entry::{Entry, PreviewCommand, PreviewType};
|
||||
use crate::channels::OnAir;
|
||||
use crate::matcher::{config::Config, injector::Injector, Matcher};
|
||||
use crate::utils::files::{walk_builder, DEFAULT_NUM_THREADS};
|
||||
use crate::utils::files::{get_default_num_threads, walk_builder};
|
||||
|
||||
pub struct Channel {
|
||||
matcher: Matcher<String>,
|
||||
icon: FileIcon,
|
||||
crawl_handle: JoinHandle<()>,
|
||||
selected_entries: FxHashSet<Entry>,
|
||||
preview_command: PreviewCommand,
|
||||
}
|
||||
|
||||
impl Channel {
|
||||
@ -28,11 +28,20 @@ impl Channel {
|
||||
base_dirs.home_dir().to_path_buf(),
|
||||
matcher.injector(),
|
||||
));
|
||||
|
||||
let preview_command = PreviewCommand {
|
||||
command: String::from(
|
||||
"cd {} && git log -n 200 --pretty=medium --all --graph --color",
|
||||
),
|
||||
delimiter: ":".to_string(),
|
||||
};
|
||||
|
||||
Channel {
|
||||
matcher,
|
||||
icon: FileIcon::from("git"),
|
||||
crawl_handle,
|
||||
selected_entries: HashSet::with_hasher(FxBuildHasher),
|
||||
preview_command,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -43,15 +52,6 @@ impl Default for Channel {
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref PREVIEW_COMMAND: PreviewCommand = PreviewCommand {
|
||||
command: String::from(
|
||||
"cd {} && git log -n 200 --pretty=medium --all --graph --color",
|
||||
),
|
||||
delimiter: ":".to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
impl OnAir for Channel {
|
||||
fn find(&mut self, pattern: &str) {
|
||||
self.matcher.find(pattern);
|
||||
@ -64,9 +64,12 @@ impl OnAir for Channel {
|
||||
.into_iter()
|
||||
.map(|item| {
|
||||
let path = item.matched_string;
|
||||
Entry::new(path, PreviewType::Command(PREVIEW_COMMAND.clone()))
|
||||
.with_name_match_ranges(&item.match_indices)
|
||||
.with_icon(self.icon)
|
||||
Entry::new(
|
||||
path,
|
||||
PreviewType::Command(self.preview_command.clone()),
|
||||
)
|
||||
.with_name_match_ranges(&item.match_indices)
|
||||
.with_icon(self.icon)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
@ -74,8 +77,11 @@ impl OnAir for Channel {
|
||||
fn get_result(&self, index: u32) -> Option<Entry> {
|
||||
self.matcher.get_result(index).map(|item| {
|
||||
let path = item.matched_string;
|
||||
Entry::new(path, PreviewType::Command(PREVIEW_COMMAND.clone()))
|
||||
.with_icon(self.icon)
|
||||
Entry::new(
|
||||
path,
|
||||
PreviewType::Command(self.preview_command.clone()),
|
||||
)
|
||||
.with_icon(self.icon)
|
||||
})
|
||||
}
|
||||
|
||||
@ -162,7 +168,7 @@ async fn crawl_for_repos(starting_point: PathBuf, injector: Injector<String>) {
|
||||
walker_overrides_builder.add(".git").unwrap();
|
||||
let walker = walk_builder(
|
||||
&starting_point,
|
||||
*DEFAULT_NUM_THREADS,
|
||||
get_default_num_threads(),
|
||||
Some(walker_overrides_builder.build().unwrap()),
|
||||
Some(get_ignored_paths()),
|
||||
)
|
||||
|
@ -1,7 +1,7 @@
|
||||
use super::{OnAir, TelevisionChannel};
|
||||
use crate::channels::entry::{Entry, PreviewType};
|
||||
use crate::matcher::{config::Config, injector::Injector, Matcher};
|
||||
use crate::utils::files::{walk_builder, DEFAULT_NUM_THREADS};
|
||||
use crate::utils::files::{get_default_num_threads, walk_builder};
|
||||
use crate::utils::strings::{
|
||||
proportion_of_printable_ascii_characters, PRINTABLE_ASCII_THRESHOLD,
|
||||
};
|
||||
@ -277,7 +277,7 @@ async fn crawl_for_candidates(
|
||||
}
|
||||
let current_dir = std::env::current_dir().unwrap();
|
||||
let mut walker =
|
||||
walk_builder(&directories[0], *DEFAULT_NUM_THREADS, None, None);
|
||||
walk_builder(&directories[0], get_default_num_threads(), None, None);
|
||||
directories[1..].iter().for_each(|path| {
|
||||
walker.add(path);
|
||||
});
|
||||
|
@ -9,7 +9,6 @@ use anyhow::{Context, Result};
|
||||
use directories::ProjectDirs;
|
||||
use keybindings::merge_keybindings;
|
||||
pub use keybindings::{parse_key, Binding, KeyBindings};
|
||||
use lazy_static::lazy_static;
|
||||
use previewers::PreviewersConfig;
|
||||
use serde::Deserialize;
|
||||
use shell_integration::ShellIntegrationConfig;
|
||||
@ -70,23 +69,7 @@ pub struct Config {
|
||||
pub shell_integration: ShellIntegrationConfig,
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
pub static ref PROJECT_NAME: String = String::from("television");
|
||||
pub static ref PROJECT_NAME_UPPER: String = PROJECT_NAME.to_uppercase();
|
||||
pub static ref DATA_FOLDER: Option<PathBuf> =
|
||||
// if `TELEVISION_DATA` is set, use that as the data directory
|
||||
env::var_os(format!("{}_DATA", PROJECT_NAME_UPPER.clone())).map(PathBuf::from).or_else(|| {
|
||||
// otherwise, use the XDG data directory
|
||||
env::var_os("XDG_DATA_HOME").map(PathBuf::from).map(|p| p.join(PROJECT_NAME.as_str())).filter(|p| p.is_absolute())
|
||||
});
|
||||
pub static ref CONFIG_FOLDER: Option<PathBuf> =
|
||||
// if `TELEVISION_CONFIG` is set, use that as the television config directory
|
||||
env::var_os(format!("{}_CONFIG", PROJECT_NAME_UPPER.clone())).map(PathBuf::from).or_else(|| {
|
||||
// otherwise, use the XDG config directory + 'television'
|
||||
env::var_os("XDG_CONFIG_HOME").map(PathBuf::from).map(|p| p.join(PROJECT_NAME.as_str())).filter(|p| p.is_absolute())
|
||||
});
|
||||
}
|
||||
|
||||
const PROJECT_NAME: &str = "television";
|
||||
const CONFIG_FILE_NAME: &str = "config.toml";
|
||||
|
||||
pub struct ConfigEnv {
|
||||
@ -184,7 +167,19 @@ impl Config {
|
||||
}
|
||||
|
||||
pub fn get_data_dir() -> PathBuf {
|
||||
let directory = if let Some(s) = DATA_FOLDER.clone() {
|
||||
// if `TELEVISION_DATA` is set, use that as the data directory
|
||||
let data_folder =
|
||||
env::var_os(format!("{}_DATA", PROJECT_NAME.to_uppercase()))
|
||||
.map(PathBuf::from)
|
||||
.or_else(|| {
|
||||
// otherwise, use the XDG data directory
|
||||
env::var_os("XDG_DATA_HOME")
|
||||
.map(PathBuf::from)
|
||||
.map(|p| p.join(PROJECT_NAME))
|
||||
.filter(|p| p.is_absolute())
|
||||
});
|
||||
|
||||
let directory = if let Some(s) = data_folder {
|
||||
debug!("Using data directory: {:?}", s);
|
||||
s
|
||||
} else if let Some(proj_dirs) = project_directory() {
|
||||
@ -197,7 +192,18 @@ pub fn get_data_dir() -> PathBuf {
|
||||
}
|
||||
|
||||
pub fn get_config_dir() -> PathBuf {
|
||||
let directory = if let Some(s) = CONFIG_FOLDER.clone() {
|
||||
// if `TELEVISION_CONFIG` is set, use that as the television config directory
|
||||
let config_dir =
|
||||
env::var_os(format!("{}_CONFIG", PROJECT_NAME.to_uppercase()))
|
||||
.map(PathBuf::from)
|
||||
.or_else(|| {
|
||||
// otherwise, use the XDG config directory + 'television'
|
||||
env::var_os("XDG_CONFIG_HOME")
|
||||
.map(PathBuf::from)
|
||||
.map(|p| p.join(PROJECT_NAME))
|
||||
.filter(|p| p.is_absolute())
|
||||
});
|
||||
let directory = if let Some(s) = config_dir {
|
||||
debug!("Config directory: {:?}", s);
|
||||
s
|
||||
} else if cfg!(unix) {
|
||||
|
@ -128,10 +128,10 @@ impl Theme {
|
||||
pub fn from_builtin(
|
||||
name: &str,
|
||||
) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
let theme_content: &str = builtin::BUILTIN_THEMES.get(name).map_or(
|
||||
builtin::BUILTIN_THEMES.get(DEFAULT_THEME).unwrap(),
|
||||
|t| *t,
|
||||
);
|
||||
let builtin_themes = builtin::builtin_themes();
|
||||
let theme_content: &str = builtin_themes
|
||||
.get(name)
|
||||
.map_or(builtin_themes.get(DEFAULT_THEME).unwrap(), |t| *t);
|
||||
let theme = toml::from_str(theme_content)?;
|
||||
Ok(theme)
|
||||
}
|
||||
|
@ -1,43 +1,39 @@
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref BUILTIN_THEMES: FxHashMap<&'static str, &'static str> = {
|
||||
let mut m = FxHashMap::default();
|
||||
m.insert("default", include_str!("../../../themes/default.toml"));
|
||||
m.insert(
|
||||
"television",
|
||||
include_str!("../../../themes/television.toml"),
|
||||
);
|
||||
m.insert(
|
||||
"gruvbox-dark",
|
||||
include_str!("../../../themes/gruvbox-dark.toml"),
|
||||
);
|
||||
m.insert(
|
||||
"gruvbox-light",
|
||||
include_str!("../../../themes/gruvbox-light.toml"),
|
||||
);
|
||||
m.insert(
|
||||
"catppuccin",
|
||||
include_str!("../../../themes/catppuccin.toml"),
|
||||
);
|
||||
m.insert("nord-dark", include_str!("../../../themes/nord-dark.toml"));
|
||||
m.insert(
|
||||
"solarized-dark",
|
||||
include_str!("../../../themes/solarized-dark.toml"),
|
||||
);
|
||||
m.insert(
|
||||
"solarized-light",
|
||||
include_str!("../../../themes/solarized-light.toml"),
|
||||
);
|
||||
m.insert("dracula", include_str!("../../../themes/dracula.toml"));
|
||||
m.insert("monokai", include_str!("../../../themes/monokai.toml"));
|
||||
m.insert("onedark", include_str!("../../../themes/onedark.toml"));
|
||||
m.insert(
|
||||
"tokyonight",
|
||||
include_str!("../../../themes/tokyonight.toml"),
|
||||
);
|
||||
m
|
||||
};
|
||||
pub fn builtin_themes() -> FxHashMap<&'static str, &'static str> {
|
||||
let mut m = FxHashMap::default();
|
||||
m.insert("default", include_str!("../../../themes/default.toml"));
|
||||
m.insert(
|
||||
"television",
|
||||
include_str!("../../../themes/television.toml"),
|
||||
);
|
||||
m.insert(
|
||||
"gruvbox-dark",
|
||||
include_str!("../../../themes/gruvbox-dark.toml"),
|
||||
);
|
||||
m.insert(
|
||||
"gruvbox-light",
|
||||
include_str!("../../../themes/gruvbox-light.toml"),
|
||||
);
|
||||
m.insert(
|
||||
"catppuccin",
|
||||
include_str!("../../../themes/catppuccin.toml"),
|
||||
);
|
||||
m.insert("nord-dark", include_str!("../../../themes/nord-dark.toml"));
|
||||
m.insert(
|
||||
"solarized-dark",
|
||||
include_str!("../../../themes/solarized-dark.toml"),
|
||||
);
|
||||
m.insert(
|
||||
"solarized-light",
|
||||
include_str!("../../../themes/solarized-light.toml"),
|
||||
);
|
||||
m.insert("dracula", include_str!("../../../themes/dracula.toml"));
|
||||
m.insert("monokai", include_str!("../../../themes/monokai.toml"));
|
||||
m.insert("onedark", include_str!("../../../themes/onedark.toml"));
|
||||
m.insert(
|
||||
"tokyonight",
|
||||
include_str!("../../../themes/tokyonight.toml"),
|
||||
);
|
||||
m
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ use std::{hash::Hash, time::Instant};
|
||||
use anyhow::Result;
|
||||
use ratatui::{layout::Rect, Frame};
|
||||
use rustc_hash::FxHashSet;
|
||||
use tokio::sync::mpsc::Sender;
|
||||
|
||||
use crate::{
|
||||
action::Action,
|
||||
@ -18,7 +17,7 @@ use crate::{
|
||||
remote_control::draw_remote_control, results::draw_results_list,
|
||||
spinner::Spinner,
|
||||
},
|
||||
television::{Message, Mode},
|
||||
television::Mode,
|
||||
utils::metadata::AppMetadata,
|
||||
};
|
||||
|
||||
@ -61,7 +60,6 @@ impl Hash for ChannelState {
|
||||
pub struct TvState {
|
||||
pub mode: Mode,
|
||||
pub selected_entry: Option<Entry>,
|
||||
pub results_area_height: u16,
|
||||
pub results_picker: Picker,
|
||||
pub rc_picker: Picker,
|
||||
pub channel_state: ChannelState,
|
||||
@ -74,7 +72,6 @@ impl TvState {
|
||||
pub fn new(
|
||||
mode: Mode,
|
||||
selected_entry: Option<Entry>,
|
||||
results_area_height: u16,
|
||||
results_picker: Picker,
|
||||
rc_picker: Picker,
|
||||
channel_state: ChannelState,
|
||||
@ -84,7 +81,6 @@ impl TvState {
|
||||
Self {
|
||||
mode,
|
||||
selected_entry,
|
||||
results_area_height,
|
||||
results_picker,
|
||||
rc_picker,
|
||||
channel_state,
|
||||
@ -100,8 +96,8 @@ pub struct Ctx {
|
||||
pub config: Config,
|
||||
pub colorscheme: Colorscheme,
|
||||
pub app_metadata: AppMetadata,
|
||||
pub tv_tx_handle: Sender<Message>,
|
||||
pub instant: Instant,
|
||||
pub layout: Layout,
|
||||
}
|
||||
|
||||
impl Ctx {
|
||||
@ -110,16 +106,16 @@ impl Ctx {
|
||||
config: Config,
|
||||
colorscheme: Colorscheme,
|
||||
app_metadata: AppMetadata,
|
||||
tv_tx_handle: Sender<Message>,
|
||||
instant: Instant,
|
||||
layout: Layout,
|
||||
) -> Self {
|
||||
Self {
|
||||
tv_state,
|
||||
config,
|
||||
colorscheme,
|
||||
app_metadata,
|
||||
tv_tx_handle,
|
||||
instant,
|
||||
layout,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -156,7 +152,7 @@ impl Ord for Ctx {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw(ctx: &Ctx, f: &mut Frame<'_>, area: Rect) -> Result<()> {
|
||||
pub fn draw(ctx: &Ctx, f: &mut Frame<'_>, area: Rect) -> Result<Layout> {
|
||||
let selected_entry = ctx
|
||||
.tv_state
|
||||
.selected_entry
|
||||
@ -185,14 +181,6 @@ pub fn draw(ctx: &Ctx, f: &mut Frame<'_>, area: Rect) -> Result<()> {
|
||||
&ctx.colorscheme,
|
||||
);
|
||||
|
||||
if layout.results.height.saturating_sub(2)
|
||||
!= ctx.tv_state.results_area_height
|
||||
{
|
||||
ctx.tv_tx_handle.try_send(Message::ResultListHeightChanged(
|
||||
layout.results.height.saturating_sub(2),
|
||||
))?;
|
||||
}
|
||||
|
||||
// results list
|
||||
draw_results_list(
|
||||
f,
|
||||
@ -259,5 +247,5 @@ pub fn draw(ctx: &Ctx, f: &mut Frame<'_>, area: Rect) -> Result<()> {
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(layout)
|
||||
}
|
||||
|
@ -3,14 +3,10 @@ use tracing_subscriber::{fmt, prelude::*, EnvFilter};
|
||||
|
||||
use crate::config::get_data_dir;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref LOG_FILE: String = format!("{}.log", env!("CARGO_PKG_NAME"));
|
||||
}
|
||||
|
||||
pub fn init() -> Result<()> {
|
||||
let directory = get_data_dir();
|
||||
std::fs::create_dir_all(directory.clone())?;
|
||||
let log_path = directory.join(LOG_FILE.clone());
|
||||
let log_path = directory.join(format!("{}.log", env!("CARGO_PKG_NAME")));
|
||||
let log_file = std::fs::File::create(log_path)?;
|
||||
let file_subscriber = fmt::layer()
|
||||
.with_file(true)
|
||||
|
@ -2,7 +2,6 @@ use crate::channels::entry::{Entry, PreviewCommand};
|
||||
use crate::preview::cache::PreviewCache;
|
||||
use crate::preview::{Preview, PreviewContent};
|
||||
use crate::utils::command::shell_command;
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::Mutex;
|
||||
use regex::Regex;
|
||||
use rustc_hash::FxHashSet;
|
||||
@ -11,12 +10,19 @@ use std::sync::Arc;
|
||||
use tracing::debug;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug)]
|
||||
pub struct CommandPreviewer {
|
||||
cache: Arc<Mutex<PreviewCache>>,
|
||||
config: CommandPreviewerConfig,
|
||||
concurrent_preview_tasks: Arc<AtomicU8>,
|
||||
in_flight_previews: Arc<Mutex<FxHashSet<String>>>,
|
||||
command_re: Regex,
|
||||
}
|
||||
|
||||
impl Default for CommandPreviewer {
|
||||
fn default() -> Self {
|
||||
CommandPreviewer::new(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
@ -53,6 +59,7 @@ impl CommandPreviewer {
|
||||
config,
|
||||
concurrent_preview_tasks: Arc::new(AtomicU8::new(0)),
|
||||
in_flight_previews: Arc::new(Mutex::new(FxHashSet::default())),
|
||||
command_re: Regex::new(r"\{(\d+)\}").unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -96,6 +103,7 @@ impl CommandPreviewer {
|
||||
let concurrent_tasks = self.concurrent_preview_tasks.clone();
|
||||
let command = command.clone();
|
||||
let in_flight_previews = self.in_flight_previews.clone();
|
||||
let command_re = self.command_re.clone();
|
||||
tokio::spawn(async move {
|
||||
try_preview(
|
||||
&command,
|
||||
@ -103,6 +111,7 @@ impl CommandPreviewer {
|
||||
&cache,
|
||||
&concurrent_tasks,
|
||||
&in_flight_previews,
|
||||
&command_re,
|
||||
);
|
||||
});
|
||||
} else {
|
||||
@ -114,11 +123,6 @@ impl CommandPreviewer {
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref COMMAND_PLACEHOLDER_REGEX: Regex =
|
||||
Regex::new(r"\{(\d+)\}").unwrap();
|
||||
}
|
||||
|
||||
/// Format the command with the entry name and provided placeholders
|
||||
///
|
||||
/// # Example
|
||||
@ -131,11 +135,15 @@ lazy_static! {
|
||||
/// delimiter: ":".to_string(),
|
||||
/// };
|
||||
/// let entry = Entry::new("a:given:entry:to:preview".to_string(), PreviewType::Command(command.clone()));
|
||||
/// let formatted_command = format_command(&command, &entry);
|
||||
/// let formatted_command = format_command(&command, &entry, ®ex::Regex::new(r"\{(\d+)\}").unwrap());
|
||||
///
|
||||
/// assert_eq!(formatted_command, "something 'a:given:entry:to:preview' 'entry' 'a'");
|
||||
/// ```
|
||||
pub fn format_command(command: &PreviewCommand, entry: &Entry) -> String {
|
||||
pub fn format_command(
|
||||
command: &PreviewCommand,
|
||||
entry: &Entry,
|
||||
command_re: &Regex,
|
||||
) -> String {
|
||||
let parts = entry.name.split(&command.delimiter).collect::<Vec<&str>>();
|
||||
debug!("Parts: {:?}", parts);
|
||||
|
||||
@ -143,7 +151,7 @@ pub fn format_command(command: &PreviewCommand, entry: &Entry) -> String {
|
||||
.command
|
||||
.replace("{}", format!("'{}'", entry.name).as_str());
|
||||
|
||||
formatted_command = COMMAND_PLACEHOLDER_REGEX
|
||||
formatted_command = command_re
|
||||
.replace_all(&formatted_command, |caps: ®ex::Captures| {
|
||||
let index =
|
||||
caps.get(1).unwrap().as_str().parse::<usize>().unwrap();
|
||||
@ -160,9 +168,10 @@ pub fn try_preview(
|
||||
cache: &Arc<Mutex<PreviewCache>>,
|
||||
concurrent_tasks: &Arc<AtomicU8>,
|
||||
in_flight_previews: &Arc<Mutex<FxHashSet<String>>>,
|
||||
command_re: &Regex,
|
||||
) {
|
||||
debug!("Computing preview for {:?}", entry.name);
|
||||
let command = format_command(command, entry);
|
||||
let command = format_command(command, entry, command_re);
|
||||
debug!("Formatted preview command: {:?}", command);
|
||||
|
||||
let child = shell_command()
|
||||
@ -212,7 +221,11 @@ mod tests {
|
||||
"an:entry:to:preview".to_string(),
|
||||
PreviewType::Command(command.clone()),
|
||||
);
|
||||
let formatted_command = format_command(&command, &entry);
|
||||
let formatted_command = format_command(
|
||||
&command,
|
||||
&entry,
|
||||
&Regex::new(r"\{(\d+)\}").unwrap(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
formatted_command,
|
||||
@ -230,7 +243,11 @@ mod tests {
|
||||
"an:entry:to:preview".to_string(),
|
||||
PreviewType::Command(command.clone()),
|
||||
);
|
||||
let formatted_command = format_command(&command, &entry);
|
||||
let formatted_command = format_command(
|
||||
&command,
|
||||
&entry,
|
||||
&Regex::new(r"\{(\d+)\}").unwrap(),
|
||||
);
|
||||
|
||||
assert_eq!(formatted_command, "something");
|
||||
}
|
||||
@ -245,7 +262,11 @@ mod tests {
|
||||
"an:entry:to:preview".to_string(),
|
||||
PreviewType::Command(command.clone()),
|
||||
);
|
||||
let formatted_command = format_command(&command, &entry);
|
||||
let formatted_command = format_command(
|
||||
&command,
|
||||
&entry,
|
||||
&Regex::new(r"\{(\d+)\}").unwrap(),
|
||||
);
|
||||
|
||||
assert_eq!(formatted_command, "something 'an:entry:to:preview'");
|
||||
}
|
||||
@ -260,7 +281,11 @@ mod tests {
|
||||
"an:entry:to:preview".to_string(),
|
||||
PreviewType::Command(command.clone()),
|
||||
);
|
||||
let formatted_command = format_command(&command, &entry);
|
||||
let formatted_command = format_command(
|
||||
&command,
|
||||
&entry,
|
||||
&Regex::new(r"\{(\d+)\}").unwrap(),
|
||||
);
|
||||
|
||||
assert_eq!(formatted_command, "something 'an' -t 'to'");
|
||||
}
|
||||
|
@ -8,12 +8,13 @@ use tracing::{debug, warn};
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
use crate::draw::Ctx;
|
||||
use crate::screen::layout::Layout;
|
||||
use crate::{action::Action, draw::draw, tui::Tui};
|
||||
|
||||
#[derive(Debug, PartialEq, PartialOrd, Ord, Eq, Clone)]
|
||||
pub enum RenderingTask {
|
||||
ClearScreen,
|
||||
Render(Ctx),
|
||||
Render(Box<Ctx>),
|
||||
Resize(u16, u16),
|
||||
Resume,
|
||||
Suspend,
|
||||
@ -35,9 +36,21 @@ impl IoStream {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct UiState {
|
||||
pub layout: Layout,
|
||||
}
|
||||
|
||||
impl UiState {
|
||||
pub fn new(layout: Layout) -> Self {
|
||||
Self { layout }
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn render(
|
||||
mut render_rx: mpsc::UnboundedReceiver<RenderingTask>,
|
||||
action_tx: mpsc::UnboundedSender<Action>,
|
||||
ui_state_tx: mpsc::UnboundedSender<UiState>,
|
||||
is_output_tty: bool,
|
||||
) -> Result<()> {
|
||||
let stream = if is_output_tty {
|
||||
@ -73,13 +86,19 @@ pub async fn render(
|
||||
if size.width.checked_mul(size.height).is_some() {
|
||||
queue!(stderr(), BeginSynchronizedUpdate).ok();
|
||||
tui.terminal.draw(|frame| {
|
||||
if let Err(err) =
|
||||
draw(&context, frame, frame.area())
|
||||
{
|
||||
warn!("Failed to draw: {:?}", err);
|
||||
let _ = action_tx.send(Action::Error(
|
||||
format!("Failed to draw: {err:?}"),
|
||||
));
|
||||
match draw(&context, frame, frame.area()) {
|
||||
Ok(layout) => {
|
||||
if layout != context.layout {
|
||||
let _ = ui_state_tx
|
||||
.send(UiState::new(layout));
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
warn!("Failed to draw: {:?}", err);
|
||||
let _ = action_tx.send(Action::Error(
|
||||
format!("Failed to draw: {err:?}"),
|
||||
));
|
||||
}
|
||||
}
|
||||
})?;
|
||||
execute!(stderr(), EndSynchronizedUpdate).ok();
|
||||
|
@ -29,7 +29,7 @@ impl Default for Dimensions {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct HelpBarLayout {
|
||||
pub left: Rect,
|
||||
pub middle: Rect,
|
||||
@ -82,6 +82,7 @@ impl Display for PreviewTitlePosition {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Layout {
|
||||
pub help_bar: Option<HelpBarLayout>,
|
||||
pub results: Rect,
|
||||
@ -90,6 +91,17 @@ pub struct Layout {
|
||||
pub remote_control: Option<Rect>,
|
||||
}
|
||||
|
||||
impl Default for Layout {
|
||||
/// Having a default layout with a non-zero height for the results area
|
||||
/// is important for the initial rendering of the application. For the first
|
||||
/// frame, this avoids not rendering any results at all since the picker's contents
|
||||
/// depend on the height of the results area which is not known until the first
|
||||
/// frame is rendered.
|
||||
fn default() -> Self {
|
||||
Self::new(None, Rect::new(0, 0, 0, 100), Rect::default(), None, None)
|
||||
}
|
||||
}
|
||||
|
||||
impl Layout {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
|
@ -10,6 +10,7 @@ use crate::draw::{ChannelState, Ctx, TvState};
|
||||
use crate::input::convert_action_to_input_request;
|
||||
use crate::picker::Picker;
|
||||
use crate::preview::{PreviewState, Previewer};
|
||||
use crate::render::UiState;
|
||||
use crate::screen::colors::Colorscheme;
|
||||
use crate::screen::layout::InputPosition;
|
||||
use crate::screen::spinner::{Spinner, SpinnerState};
|
||||
@ -20,7 +21,7 @@ use anyhow::Result;
|
||||
use rustc_hash::{FxBuildHasher, FxHashSet};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashSet;
|
||||
use tokio::sync::mpsc::{Receiver, Sender, UnboundedSender};
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
|
||||
#[derive(PartialEq, Copy, Clone, Hash, Eq, Debug, Serialize, Deserialize)]
|
||||
pub enum Mode {
|
||||
@ -38,7 +39,6 @@ pub struct Television {
|
||||
pub current_pattern: String,
|
||||
pub results_picker: Picker,
|
||||
pub rc_picker: Picker,
|
||||
results_area_height: u16,
|
||||
pub previewer: Previewer,
|
||||
pub preview_state: PreviewState,
|
||||
pub spinner: Spinner,
|
||||
@ -46,16 +46,7 @@ pub struct Television {
|
||||
pub app_metadata: AppMetadata,
|
||||
pub colorscheme: Colorscheme,
|
||||
pub ticks: u64,
|
||||
// these are really here as a means to communicate between the render thread
|
||||
// and the main thread to update `Television`'s state without needing to pass
|
||||
// a mutable reference to `draw`
|
||||
pub inner_rx: Receiver<Message>,
|
||||
pub inner_tx: Sender<Message>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum Message {
|
||||
ResultListHeightChanged(u16),
|
||||
pub ui_state: UiState,
|
||||
}
|
||||
|
||||
impl Television {
|
||||
@ -87,8 +78,6 @@ impl Television {
|
||||
|
||||
channel.find(&input.unwrap_or(EMPTY_STRING.to_string()));
|
||||
let spinner = Spinner::default();
|
||||
// capacity is quite arbitrary here, we can adjust it later
|
||||
let (inner_tx, inner_rx) = tokio::sync::mpsc::channel(10);
|
||||
|
||||
Self {
|
||||
action_tx,
|
||||
@ -101,7 +90,6 @@ impl Television {
|
||||
current_pattern: EMPTY_STRING.to_string(),
|
||||
results_picker,
|
||||
rc_picker: Picker::default(),
|
||||
results_area_height: 0,
|
||||
previewer,
|
||||
preview_state: PreviewState::default(),
|
||||
spinner,
|
||||
@ -109,11 +97,14 @@ impl Television {
|
||||
app_metadata,
|
||||
colorscheme,
|
||||
ticks: 0,
|
||||
inner_rx,
|
||||
inner_tx,
|
||||
ui_state: UiState::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_ui_state(&mut self, ui_state: UiState) {
|
||||
self.ui_state = ui_state;
|
||||
}
|
||||
|
||||
pub fn init_remote_control(&mut self) {
|
||||
let cable_channels = load_cable_channels().unwrap_or_default();
|
||||
let builtin_channels = load_builtin_channels(Some(
|
||||
@ -134,7 +125,6 @@ impl Television {
|
||||
let tv_state = TvState::new(
|
||||
self.mode,
|
||||
self.get_selected_entry(Some(Mode::Channel)),
|
||||
self.results_area_height,
|
||||
self.results_picker.clone(),
|
||||
self.rc_picker.clone(),
|
||||
channel_state,
|
||||
@ -147,9 +137,9 @@ impl Television {
|
||||
self.config.clone(),
|
||||
self.colorscheme.clone(),
|
||||
self.app_metadata.clone(),
|
||||
self.inner_tx.clone(),
|
||||
// now timestamp
|
||||
std::time::Instant::now(),
|
||||
self.ui_state.layout,
|
||||
)
|
||||
}
|
||||
|
||||
@ -229,7 +219,7 @@ impl Television {
|
||||
picker.select_prev(
|
||||
step,
|
||||
result_count as usize,
|
||||
self.results_area_height as usize,
|
||||
self.ui_state.layout.results.height.saturating_sub(2) as usize,
|
||||
);
|
||||
}
|
||||
|
||||
@ -248,7 +238,7 @@ impl Television {
|
||||
picker.select_next(
|
||||
step,
|
||||
result_count as usize,
|
||||
self.results_area_height as usize,
|
||||
self.ui_state.layout.results.height.saturating_sub(2) as usize,
|
||||
);
|
||||
}
|
||||
|
||||
@ -315,6 +305,7 @@ impl Television {
|
||||
{
|
||||
// preview content
|
||||
if let Some(preview) = self.previewer.preview(selected_entry) {
|
||||
// only update if the preview content has changed
|
||||
if self.preview_state.preview.title != preview.title {
|
||||
self.preview_state.update(
|
||||
preview,
|
||||
@ -323,7 +314,13 @@ impl Television {
|
||||
.line_number
|
||||
.unwrap_or(0)
|
||||
.saturating_sub(
|
||||
(self.results_area_height / 2).into(),
|
||||
(self
|
||||
.ui_state
|
||||
.layout
|
||||
.preview_window
|
||||
.map_or(0, |w| w.height)
|
||||
/ 2)
|
||||
.into(),
|
||||
)
|
||||
.try_into()?,
|
||||
selected_entry
|
||||
@ -348,7 +345,7 @@ impl Television {
|
||||
}
|
||||
|
||||
self.results_picker.entries = self.channel.results(
|
||||
self.results_area_height.into(),
|
||||
self.ui_state.layout.results.height.into(),
|
||||
u32::try_from(self.results_picker.offset()).unwrap(),
|
||||
);
|
||||
self.results_picker.total_items = self.channel.result_count();
|
||||
@ -364,7 +361,7 @@ impl Television {
|
||||
|
||||
self.rc_picker.entries = self.remote_control.results(
|
||||
// this'll be more than the actual rc height but it's fine
|
||||
self.results_area_height.into(),
|
||||
self.ui_state.layout.results.height.into(),
|
||||
u32::try_from(self.rc_picker.offset()).unwrap(),
|
||||
);
|
||||
self.rc_picker.total_items = self.remote_control.total_count();
|
||||
@ -513,11 +510,25 @@ impl Television {
|
||||
}
|
||||
Action::SelectNextPage => {
|
||||
self.preview_state.reset();
|
||||
self.select_next_entry(self.results_area_height.into());
|
||||
self.select_next_entry(
|
||||
self.ui_state
|
||||
.layout
|
||||
.results
|
||||
.height
|
||||
.saturating_sub(2)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
Action::SelectPrevPage => {
|
||||
self.preview_state.reset();
|
||||
self.select_prev_entry(self.results_area_height.into());
|
||||
self.select_prev_entry(
|
||||
self.ui_state
|
||||
.layout
|
||||
.results
|
||||
.height
|
||||
.saturating_sub(2)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
Action::ScrollPreviewDown => self.preview_state.scroll_down(1),
|
||||
Action::ScrollPreviewUp => self.preview_state.scroll_up(1),
|
||||
@ -559,13 +570,6 @@ impl Television {
|
||||
///
|
||||
/// This function may return an Action that'll be processed by the parent `App`.
|
||||
pub fn update(&mut self, action: &Action) -> Result<Option<Action>> {
|
||||
if let Ok(Message::ResultListHeightChanged(height)) =
|
||||
self.inner_rx.try_recv()
|
||||
{
|
||||
self.results_area_height = height;
|
||||
self.action_tx.send(Action::Render)?;
|
||||
}
|
||||
|
||||
self.handle_action(action)?;
|
||||
|
||||
self.update_results_picker_state();
|
||||
|
@ -6,9 +6,9 @@ use std::io::BufReader;
|
||||
use std::io::Read;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use ignore::{overrides::Override, types::TypesBuilder, WalkBuilder};
|
||||
use lazy_static::lazy_static;
|
||||
use tracing::{debug, warn};
|
||||
|
||||
use crate::utils::strings::{
|
||||
@ -61,8 +61,10 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref DEFAULT_NUM_THREADS: usize = default_num_threads().into();
|
||||
pub static DEFAULT_NUM_THREADS: OnceLock<usize> = OnceLock::new();
|
||||
|
||||
pub fn get_default_num_threads() -> usize {
|
||||
*DEFAULT_NUM_THREADS.get_or_init(default_num_threads)
|
||||
}
|
||||
|
||||
pub fn walk_builder(
|
||||
@ -143,340 +145,345 @@ where
|
||||
path.as_ref()
|
||||
.extension()
|
||||
.and_then(|ext| ext.to_str())
|
||||
.is_some_and(|ext| KNOWN_TEXT_FILE_EXTENSIONS.contains(ext))
|
||||
.is_some_and(|ext| get_known_text_file_extensions().contains(ext))
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref KNOWN_TEXT_FILE_EXTENSIONS: FxHashSet<&'static str> = [
|
||||
"ada",
|
||||
"adb",
|
||||
"ads",
|
||||
"applescript",
|
||||
"as",
|
||||
"asc",
|
||||
"ascii",
|
||||
"ascx",
|
||||
"asm",
|
||||
"asmx",
|
||||
"asp",
|
||||
"aspx",
|
||||
"atom",
|
||||
"au3",
|
||||
"awk",
|
||||
"bas",
|
||||
"bash",
|
||||
"bashrc",
|
||||
"bat",
|
||||
"bbcolors",
|
||||
"bcp",
|
||||
"bdsgroup",
|
||||
"bdsproj",
|
||||
"bib",
|
||||
"bowerrc",
|
||||
"c",
|
||||
"cbl",
|
||||
"cc",
|
||||
"cfc",
|
||||
"cfg",
|
||||
"cfm",
|
||||
"cfml",
|
||||
"cgi",
|
||||
"cjs",
|
||||
"clj",
|
||||
"cljs",
|
||||
"cls",
|
||||
"cmake",
|
||||
"cmd",
|
||||
"cnf",
|
||||
"cob",
|
||||
"code-snippets",
|
||||
"coffee",
|
||||
"coffeekup",
|
||||
"conf",
|
||||
"cp",
|
||||
"cpp",
|
||||
"cpt",
|
||||
"cpy",
|
||||
"crt",
|
||||
"cs",
|
||||
"csh",
|
||||
"cson",
|
||||
"csproj",
|
||||
"csr",
|
||||
"css",
|
||||
"csslintrc",
|
||||
"csv",
|
||||
"ctl",
|
||||
"curlrc",
|
||||
"cxx",
|
||||
"d",
|
||||
"dart",
|
||||
"dfm",
|
||||
"diff",
|
||||
"dof",
|
||||
"dpk",
|
||||
"dpr",
|
||||
"dproj",
|
||||
"dtd",
|
||||
"eco",
|
||||
"editorconfig",
|
||||
"ejs",
|
||||
"el",
|
||||
"elm",
|
||||
"emacs",
|
||||
"eml",
|
||||
"ent",
|
||||
"erb",
|
||||
"erl",
|
||||
"eslintignore",
|
||||
"eslintrc",
|
||||
"ex",
|
||||
"exs",
|
||||
"f",
|
||||
"f03",
|
||||
"f77",
|
||||
"f90",
|
||||
"f95",
|
||||
"fish",
|
||||
"for",
|
||||
"fpp",
|
||||
"frm",
|
||||
"fs",
|
||||
"fsproj",
|
||||
"fsx",
|
||||
"ftn",
|
||||
"gemrc",
|
||||
"gemspec",
|
||||
"gitattributes",
|
||||
"gitconfig",
|
||||
"gitignore",
|
||||
"gitkeep",
|
||||
"gitmodules",
|
||||
"go",
|
||||
"gpp",
|
||||
"gradle",
|
||||
"graphql",
|
||||
"groovy",
|
||||
"groupproj",
|
||||
"grunit",
|
||||
"gtmpl",
|
||||
"gvimrc",
|
||||
"h",
|
||||
"haml",
|
||||
"hbs",
|
||||
"hgignore",
|
||||
"hh",
|
||||
"hpp",
|
||||
"hrl",
|
||||
"hs",
|
||||
"hta",
|
||||
"htaccess",
|
||||
"htc",
|
||||
"htm",
|
||||
"html",
|
||||
"htpasswd",
|
||||
"hxx",
|
||||
"iced",
|
||||
"iml",
|
||||
"inc",
|
||||
"inf",
|
||||
"info",
|
||||
"ini",
|
||||
"ino",
|
||||
"int",
|
||||
"irbrc",
|
||||
"itcl",
|
||||
"itermcolors",
|
||||
"itk",
|
||||
"jade",
|
||||
"java",
|
||||
"jhtm",
|
||||
"jhtml",
|
||||
"js",
|
||||
"jscsrc",
|
||||
"jshintignore",
|
||||
"jshintrc",
|
||||
"json",
|
||||
"json5",
|
||||
"jsonld",
|
||||
"jsp",
|
||||
"jspx",
|
||||
"jsx",
|
||||
"ksh",
|
||||
"less",
|
||||
"lhs",
|
||||
"lisp",
|
||||
"log",
|
||||
"ls",
|
||||
"lsp",
|
||||
"lua",
|
||||
"m",
|
||||
"m4",
|
||||
"mak",
|
||||
"map",
|
||||
"markdown",
|
||||
"master",
|
||||
"md",
|
||||
"mdown",
|
||||
"mdwn",
|
||||
"mdx",
|
||||
"metadata",
|
||||
"mht",
|
||||
"mhtml",
|
||||
"mjs",
|
||||
"mk",
|
||||
"mkd",
|
||||
"mkdn",
|
||||
"mkdown",
|
||||
"ml",
|
||||
"mli",
|
||||
"mm",
|
||||
"mxml",
|
||||
"nfm",
|
||||
"nfo",
|
||||
"noon",
|
||||
"npmignore",
|
||||
"npmrc",
|
||||
"nuspec",
|
||||
"nvmrc",
|
||||
"ops",
|
||||
"pas",
|
||||
"pasm",
|
||||
"patch",
|
||||
"pbxproj",
|
||||
"pch",
|
||||
"pem",
|
||||
"pg",
|
||||
"php",
|
||||
"php3",
|
||||
"php4",
|
||||
"php5",
|
||||
"phpt",
|
||||
"phtml",
|
||||
"pir",
|
||||
"pl",
|
||||
"pm",
|
||||
"pmc",
|
||||
"pod",
|
||||
"pot",
|
||||
"prettierrc",
|
||||
"properties",
|
||||
"props",
|
||||
"pt",
|
||||
"pug",
|
||||
"purs",
|
||||
"py",
|
||||
"pyx",
|
||||
"r",
|
||||
"rake",
|
||||
"rb",
|
||||
"rbw",
|
||||
"rc",
|
||||
"rdoc",
|
||||
"rdoc_options",
|
||||
"resx",
|
||||
"rexx",
|
||||
"rhtml",
|
||||
"rjs",
|
||||
"rlib",
|
||||
"ron",
|
||||
"rs",
|
||||
"rss",
|
||||
"rst",
|
||||
"rtf",
|
||||
"rvmrc",
|
||||
"rxml",
|
||||
"s",
|
||||
"sass",
|
||||
"scala",
|
||||
"scm",
|
||||
"scss",
|
||||
"seestyle",
|
||||
"sh",
|
||||
"shtml",
|
||||
"sln",
|
||||
"sls",
|
||||
"spec",
|
||||
"sql",
|
||||
"sqlite",
|
||||
"sqlproj",
|
||||
"srt",
|
||||
"ss",
|
||||
"sss",
|
||||
"st",
|
||||
"strings",
|
||||
"sty",
|
||||
"styl",
|
||||
"stylus",
|
||||
"sub",
|
||||
"sublime-build",
|
||||
"sublime-commands",
|
||||
"sublime-completions",
|
||||
"sublime-keymap",
|
||||
"sublime-macro",
|
||||
"sublime-menu",
|
||||
"sublime-project",
|
||||
"sublime-settings",
|
||||
"sublime-workspace",
|
||||
"sv",
|
||||
"svc",
|
||||
"svg",
|
||||
"swift",
|
||||
"t",
|
||||
"tcl",
|
||||
"tcsh",
|
||||
"terminal",
|
||||
"tex",
|
||||
"text",
|
||||
"textile",
|
||||
"tg",
|
||||
"tk",
|
||||
"tmLanguage",
|
||||
"tmpl",
|
||||
"tmTheme",
|
||||
"toml",
|
||||
"tpl",
|
||||
"ts",
|
||||
"tsv",
|
||||
"tsx",
|
||||
"tt",
|
||||
"tt2",
|
||||
"ttml",
|
||||
"twig",
|
||||
"txt",
|
||||
"v",
|
||||
"vb",
|
||||
"vbproj",
|
||||
"vbs",
|
||||
"vcproj",
|
||||
"vcxproj",
|
||||
"vh",
|
||||
"vhd",
|
||||
"vhdl",
|
||||
"vim",
|
||||
"viminfo",
|
||||
"vimrc",
|
||||
"vm",
|
||||
"vue",
|
||||
"webapp",
|
||||
"webmanifest",
|
||||
"wsc",
|
||||
"x-php",
|
||||
"xaml",
|
||||
"xht",
|
||||
"xhtml",
|
||||
"xml",
|
||||
"xs",
|
||||
"xsd",
|
||||
"xsl",
|
||||
"xslt",
|
||||
"y",
|
||||
"yaml",
|
||||
"yml",
|
||||
"zsh",
|
||||
"zshrc",
|
||||
]
|
||||
.iter()
|
||||
.copied()
|
||||
.collect();
|
||||
pub static KNOWN_TEXT_FILE_EXTENSIONS: OnceLock<FxHashSet<&'static str>> =
|
||||
OnceLock::new();
|
||||
|
||||
pub fn get_known_text_file_extensions() -> &'static FxHashSet<&'static str> {
|
||||
KNOWN_TEXT_FILE_EXTENSIONS.get_or_init(|| {
|
||||
[
|
||||
"ada",
|
||||
"adb",
|
||||
"ads",
|
||||
"applescript",
|
||||
"as",
|
||||
"asc",
|
||||
"ascii",
|
||||
"ascx",
|
||||
"asm",
|
||||
"asmx",
|
||||
"asp",
|
||||
"aspx",
|
||||
"atom",
|
||||
"au3",
|
||||
"awk",
|
||||
"bas",
|
||||
"bash",
|
||||
"bashrc",
|
||||
"bat",
|
||||
"bbcolors",
|
||||
"bcp",
|
||||
"bdsgroup",
|
||||
"bdsproj",
|
||||
"bib",
|
||||
"bowerrc",
|
||||
"c",
|
||||
"cbl",
|
||||
"cc",
|
||||
"cfc",
|
||||
"cfg",
|
||||
"cfm",
|
||||
"cfml",
|
||||
"cgi",
|
||||
"cjs",
|
||||
"clj",
|
||||
"cljs",
|
||||
"cls",
|
||||
"cmake",
|
||||
"cmd",
|
||||
"cnf",
|
||||
"cob",
|
||||
"code-snippets",
|
||||
"coffee",
|
||||
"coffeekup",
|
||||
"conf",
|
||||
"cp",
|
||||
"cpp",
|
||||
"cpt",
|
||||
"cpy",
|
||||
"crt",
|
||||
"cs",
|
||||
"csh",
|
||||
"cson",
|
||||
"csproj",
|
||||
"csr",
|
||||
"css",
|
||||
"csslintrc",
|
||||
"csv",
|
||||
"ctl",
|
||||
"curlrc",
|
||||
"cxx",
|
||||
"d",
|
||||
"dart",
|
||||
"dfm",
|
||||
"diff",
|
||||
"dof",
|
||||
"dpk",
|
||||
"dpr",
|
||||
"dproj",
|
||||
"dtd",
|
||||
"eco",
|
||||
"editorconfig",
|
||||
"ejs",
|
||||
"el",
|
||||
"elm",
|
||||
"emacs",
|
||||
"eml",
|
||||
"ent",
|
||||
"erb",
|
||||
"erl",
|
||||
"eslintignore",
|
||||
"eslintrc",
|
||||
"ex",
|
||||
"exs",
|
||||
"f",
|
||||
"f03",
|
||||
"f77",
|
||||
"f90",
|
||||
"f95",
|
||||
"fish",
|
||||
"for",
|
||||
"fpp",
|
||||
"frm",
|
||||
"fs",
|
||||
"fsproj",
|
||||
"fsx",
|
||||
"ftn",
|
||||
"gemrc",
|
||||
"gemspec",
|
||||
"gitattributes",
|
||||
"gitconfig",
|
||||
"gitignore",
|
||||
"gitkeep",
|
||||
"gitmodules",
|
||||
"go",
|
||||
"gpp",
|
||||
"gradle",
|
||||
"graphql",
|
||||
"groovy",
|
||||
"groupproj",
|
||||
"grunit",
|
||||
"gtmpl",
|
||||
"gvimrc",
|
||||
"h",
|
||||
"haml",
|
||||
"hbs",
|
||||
"hgignore",
|
||||
"hh",
|
||||
"hpp",
|
||||
"hrl",
|
||||
"hs",
|
||||
"hta",
|
||||
"htaccess",
|
||||
"htc",
|
||||
"htm",
|
||||
"html",
|
||||
"htpasswd",
|
||||
"hxx",
|
||||
"iced",
|
||||
"iml",
|
||||
"inc",
|
||||
"inf",
|
||||
"info",
|
||||
"ini",
|
||||
"ino",
|
||||
"int",
|
||||
"irbrc",
|
||||
"itcl",
|
||||
"itermcolors",
|
||||
"itk",
|
||||
"jade",
|
||||
"java",
|
||||
"jhtm",
|
||||
"jhtml",
|
||||
"js",
|
||||
"jscsrc",
|
||||
"jshintignore",
|
||||
"jshintrc",
|
||||
"json",
|
||||
"json5",
|
||||
"jsonld",
|
||||
"jsp",
|
||||
"jspx",
|
||||
"jsx",
|
||||
"ksh",
|
||||
"less",
|
||||
"lhs",
|
||||
"lisp",
|
||||
"log",
|
||||
"ls",
|
||||
"lsp",
|
||||
"lua",
|
||||
"m",
|
||||
"m4",
|
||||
"mak",
|
||||
"map",
|
||||
"markdown",
|
||||
"master",
|
||||
"md",
|
||||
"mdown",
|
||||
"mdwn",
|
||||
"mdx",
|
||||
"metadata",
|
||||
"mht",
|
||||
"mhtml",
|
||||
"mjs",
|
||||
"mk",
|
||||
"mkd",
|
||||
"mkdn",
|
||||
"mkdown",
|
||||
"ml",
|
||||
"mli",
|
||||
"mm",
|
||||
"mxml",
|
||||
"nfm",
|
||||
"nfo",
|
||||
"noon",
|
||||
"npmignore",
|
||||
"npmrc",
|
||||
"nuspec",
|
||||
"nvmrc",
|
||||
"ops",
|
||||
"pas",
|
||||
"pasm",
|
||||
"patch",
|
||||
"pbxproj",
|
||||
"pch",
|
||||
"pem",
|
||||
"pg",
|
||||
"php",
|
||||
"php3",
|
||||
"php4",
|
||||
"php5",
|
||||
"phpt",
|
||||
"phtml",
|
||||
"pir",
|
||||
"pl",
|
||||
"pm",
|
||||
"pmc",
|
||||
"pod",
|
||||
"pot",
|
||||
"prettierrc",
|
||||
"properties",
|
||||
"props",
|
||||
"pt",
|
||||
"pug",
|
||||
"purs",
|
||||
"py",
|
||||
"pyx",
|
||||
"r",
|
||||
"rake",
|
||||
"rb",
|
||||
"rbw",
|
||||
"rc",
|
||||
"rdoc",
|
||||
"rdoc_options",
|
||||
"resx",
|
||||
"rexx",
|
||||
"rhtml",
|
||||
"rjs",
|
||||
"rlib",
|
||||
"ron",
|
||||
"rs",
|
||||
"rss",
|
||||
"rst",
|
||||
"rtf",
|
||||
"rvmrc",
|
||||
"rxml",
|
||||
"s",
|
||||
"sass",
|
||||
"scala",
|
||||
"scm",
|
||||
"scss",
|
||||
"seestyle",
|
||||
"sh",
|
||||
"shtml",
|
||||
"sln",
|
||||
"sls",
|
||||
"spec",
|
||||
"sql",
|
||||
"sqlite",
|
||||
"sqlproj",
|
||||
"srt",
|
||||
"ss",
|
||||
"sss",
|
||||
"st",
|
||||
"strings",
|
||||
"sty",
|
||||
"styl",
|
||||
"stylus",
|
||||
"sub",
|
||||
"sublime-build",
|
||||
"sublime-commands",
|
||||
"sublime-completions",
|
||||
"sublime-keymap",
|
||||
"sublime-macro",
|
||||
"sublime-menu",
|
||||
"sublime-project",
|
||||
"sublime-settings",
|
||||
"sublime-workspace",
|
||||
"sv",
|
||||
"svc",
|
||||
"svg",
|
||||
"swift",
|
||||
"t",
|
||||
"tcl",
|
||||
"tcsh",
|
||||
"terminal",
|
||||
"tex",
|
||||
"text",
|
||||
"textile",
|
||||
"tg",
|
||||
"tk",
|
||||
"tmLanguage",
|
||||
"tmpl",
|
||||
"tmTheme",
|
||||
"toml",
|
||||
"tpl",
|
||||
"ts",
|
||||
"tsv",
|
||||
"tsx",
|
||||
"tt",
|
||||
"tt2",
|
||||
"ttml",
|
||||
"twig",
|
||||
"txt",
|
||||
"v",
|
||||
"vb",
|
||||
"vbproj",
|
||||
"vbs",
|
||||
"vcproj",
|
||||
"vcxproj",
|
||||
"vh",
|
||||
"vhd",
|
||||
"vhdl",
|
||||
"vim",
|
||||
"viminfo",
|
||||
"vimrc",
|
||||
"vm",
|
||||
"vue",
|
||||
"webapp",
|
||||
"webmanifest",
|
||||
"wsc",
|
||||
"x-php",
|
||||
"xaml",
|
||||
"xht",
|
||||
"xhtml",
|
||||
"xml",
|
||||
"xs",
|
||||
"xsd",
|
||||
"xsl",
|
||||
"xslt",
|
||||
"y",
|
||||
"yaml",
|
||||
"yml",
|
||||
"zsh",
|
||||
"zshrc",
|
||||
]
|
||||
.iter()
|
||||
.copied()
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
@ -1,5 +1,3 @@
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
/// Returns the index of the next character boundary in the given string.
|
||||
///
|
||||
/// If the given index is already a character boundary, it is returned as is.
|
||||
@ -151,14 +149,11 @@ pub fn try_parse_utf8_char(input: &[u8]) -> Option<(char, usize)> {
|
||||
decoded.map(|(seq, n)| (seq.chars().next().unwrap(), n))
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
/// The Unicode symbol to use for non-printable characters.
|
||||
static ref NULL_SYMBOL: char = char::from_u32(0x2400).unwrap();
|
||||
}
|
||||
|
||||
pub const EMPTY_STRING: &str = "";
|
||||
pub const TAB_WIDTH: usize = 4;
|
||||
|
||||
/// The Unicode symbol to use for non-printable characters.
|
||||
const NULL_SYMBOL: char = '\u{2400}';
|
||||
const TAB_CHARACTER: char = '\t';
|
||||
const LINE_FEED_CHARACTER: char = '\x0A';
|
||||
const DELETE_CHARACTER: char = '\x7F';
|
||||
@ -304,7 +299,7 @@ pub fn replace_non_printable(
|
||||
| BOM_CHARACTER
|
||||
if config.replace_control_characters =>
|
||||
{
|
||||
output.push(*NULL_SYMBOL);
|
||||
output.push(NULL_SYMBOL);
|
||||
}
|
||||
// CJK Unified Ideographs
|
||||
// ex: 解
|
||||
@ -337,13 +332,13 @@ pub fn replace_non_printable(
|
||||
}
|
||||
// Unicode characters above 0x0700 seem unstable with ratatui
|
||||
c if c > '\u{0700}' => {
|
||||
output.push(*NULL_SYMBOL);
|
||||
output.push(NULL_SYMBOL);
|
||||
}
|
||||
// everything else
|
||||
c => output.push(c),
|
||||
}
|
||||
} else {
|
||||
output.push(*NULL_SYMBOL);
|
||||
output.push(NULL_SYMBOL);
|
||||
idx += 1;
|
||||
}
|
||||
}
|
||||
|
@ -224,7 +224,6 @@ pub fn compute_highlights_for_line<'a>(
|
||||
// SOFTWARE.
|
||||
|
||||
use directories::BaseDirs;
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
use std::env;
|
||||
@ -258,13 +257,11 @@ impl BatProjectDirs {
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
pub static ref PROJECT_DIRS: BatProjectDirs = BatProjectDirs::new()
|
||||
.unwrap_or_else(|| panic!("Could not get home directory"));
|
||||
}
|
||||
|
||||
pub fn load_highlighting_assets() -> HighlightingAssets {
|
||||
HighlightingAssets::from_cache(PROJECT_DIRS.cache_dir())
|
||||
let project_dirs = BatProjectDirs::new()
|
||||
.unwrap_or_else(|| panic!("Could not get home directory"));
|
||||
|
||||
HighlightingAssets::from_cache(project_dirs.cache_dir())
|
||||
.unwrap_or_else(|_| HighlightingAssets::from_binary())
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,7 @@ use std::num::NonZeroUsize;
|
||||
/// This will use the number of available threads if possible, but will default to 1 if the number
|
||||
/// of available threads cannot be determined. It will also never use more than 32 threads to avoid
|
||||
/// startup overhead.
|
||||
pub fn default_num_threads() -> NonZeroUsize {
|
||||
pub fn default_num_threads() -> usize {
|
||||
// default to 1 thread if we can't determine the number of available threads
|
||||
let default = NonZeroUsize::MIN;
|
||||
// never use more than 32 threads to avoid startup overhead
|
||||
@ -14,4 +14,5 @@ pub fn default_num_threads() -> NonZeroUsize {
|
||||
std::thread::available_parallelism()
|
||||
.unwrap_or(default)
|
||||
.min(limit)
|
||||
.get()
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user