mirror of
https://github.com/alexpasmantier/television.git
synced 2025-06-03 01:50:12 +00:00
refactor(ui): communicate ui state to tv using channels (#369)
The state of the UI is now synchronized with the `Television` struct using a dedicated channel and is available at `Television.ui_state`. This removes quite a bit of complexity from the existing code and should allow for nicer implementations of features that need the UI state to compute things in the background (typically knowing the target size of an image you wish to construct in the background, as in #363) The `UiState` currently only holds the UI layout: ```rs pub struct UiState { pub layout: Layout, } ```
This commit is contained in:
parent
e2a0fb2047
commit
63cb976027
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