use {
super::*,
crate::{
app::Mode,
cli::{
Args,
TriBool,
},
conf::*,
content_search,
display::LayoutInstructions,
errors::*,
file_sum,
icon::*,
kitty::{
KittyGraphicsDisplay,
TransmissionMedium,
},
path::SpecialPaths,
pattern::SearchModeMap,
preview::PreviewTransformers,
skin::ExtColorMap,
syntactic::SyntaxTheme,
tree::TreeOptions,
verb::*,
},
crokey::crossterm::tty::IsTty,
std::{
convert::{
TryFrom,
TryInto,
},
io,
num::NonZeroUsize,
path::{
Path,
PathBuf,
},
},
};
pub struct AppContext {
pub is_tty: bool,
pub initial_root: PathBuf,
pub initial_file: Option<PathBuf>,
pub initial_tree_options: TreeOptions,
pub config_paths: Vec<PathBuf>,
pub launch_args: Args,
pub config_default_args: Option<Args>,
pub verb_store: VerbStore,
pub special_paths: SpecialPaths,
pub search_modes: SearchModeMap,
pub show_selection_mark: bool,
pub ext_colors: ExtColorMap,
pub syntax_theme: Option<SyntaxTheme>,
pub standard_status: StandardStatus,
pub true_colors: bool,
pub icons: Option<Box<dyn IconPlugin + Send + Sync>>,
pub modal: bool,
pub initial_mode: Mode,
pub capture_mouse: bool,
pub max_panels_count: usize,
pub quit_on_last_cancel: bool,
pub file_sum_threads_count: usize,
pub max_staged_count: usize,
pub auto_open_staging_area: bool,
pub content_search_max_file_size: usize,
pub terminal_title_pattern: Option<ExecPattern>,
pub reset_terminal_title_on_exit: bool,
pub update_work_dir: bool,
pub keyboard_enhanced: bool,
pub kitty_graphics_transmission: TransmissionMedium,
pub kitty_graphics_display: KittyGraphicsDisplay,
pub kept_kitty_temp_files: NonZeroUsize,
pub lines_after_match_in_preview: usize,
pub lines_before_match_in_preview: usize,
pub preview_transformers: PreviewTransformers,
pub layout_instructions: LayoutInstructions,
pub server_name: Option<String>,
}
impl AppContext {
pub fn from(
launch_args: Args,
verb_store: VerbStore,
config: &Conf,
) -> Result<Self, ProgramError> {
let is_tty = std::io::stdout().is_tty();
let config_default_args = config
.default_flags
.as_ref()
.map(|flags| parse_default_flags(flags))
.transpose()?;
let config_paths = config.files.clone();
let standard_status = StandardStatus::new(&verb_store);
let true_colors = if let Some(value) = config.true_colors {
value
} else {
are_true_colors_available()
};
let icons = config.icon_theme.as_ref().and_then(|itn| icon_plugin(itn));
let mut special_paths: SpecialPaths = (&config.special_paths).try_into()?;
special_paths.add_defaults();
let search_modes = config
.search_modes
.as_ref()
.map(TryInto::try_into)
.transpose()?
.unwrap_or_default();
let ext_colors = ExtColorMap::try_from(&config.ext_colors).map_err(ConfError::from)?;
let file_sum_threads_count = config
.file_sum_threads_count
.unwrap_or(file_sum::DEFAULT_THREAD_COUNT);
if !(1..=50).contains(&file_sum_threads_count) {
return Err(ConfError::InvalidThreadsCount {
count: file_sum_threads_count,
}
.into());
}
let max_panels_count = config.max_panels_count.unwrap_or(2).clamp(2, 100);
let capture_mouse = match (config.capture_mouse, config.disable_mouse_capture) {
(Some(b), _) => b, (_, Some(b)) => !b,
_ => true,
};
let max_staged_count = config.max_staged_count.unwrap_or(10_000).clamp(10, 100_000);
let auto_open_staging_area = config.auto_open_staging_area.unwrap_or(true);
let (initial_root, initial_file) = initial_root_file(&launch_args)?;
let mut initial_tree_options = TreeOptions::default();
initial_tree_options.apply_config(config)?;
if let Some(args) = &config_default_args {
initial_tree_options.apply_launch_args(args);
}
initial_tree_options.apply_launch_args(&launch_args);
if launch_args.color == TriBool::No {
initial_tree_options.show_selection_mark = true;
}
let content_search_max_file_size = config
.content_search_max_file_size
.map(|u64value| usize::try_from(u64value).unwrap_or(usize::MAX))
.unwrap_or(content_search::DEFAULT_MAX_FILE_SIZE);
let terminal_title_pattern = config.terminal_title.clone();
let reset_terminal_title_on_exit = config.reset_terminal_title_on_exit.unwrap_or(false);
let preview_transformers = PreviewTransformers::new(&config.preview_transformers)?;
let layout_instructions = config.layout_instructions.clone().unwrap_or_default();
let kept_kitty_temp_files = config.kept_kitty_temp_files.unwrap_or(
#[expect(clippy::missing_panics_doc, reason = "infallible")]
std::num::NonZeroUsize::new(500).unwrap(),
);
let server_name = build_server_name(&launch_args)
.or_else(|| build_server_name(config_default_args.as_ref()?));
Ok(Self {
is_tty,
initial_root,
initial_file,
initial_tree_options,
config_paths,
launch_args,
config_default_args,
verb_store,
special_paths,
search_modes,
show_selection_mark: config.show_selection_mark.unwrap_or(false),
ext_colors,
syntax_theme: config.syntax_theme,
standard_status,
true_colors,
icons,
modal: config.modal.unwrap_or(false),
initial_mode: config.initial_mode.unwrap_or(Mode::Command),
capture_mouse,
max_panels_count,
quit_on_last_cancel: config.quit_on_last_cancel.unwrap_or(false),
file_sum_threads_count,
max_staged_count,
auto_open_staging_area,
content_search_max_file_size,
terminal_title_pattern,
reset_terminal_title_on_exit,
update_work_dir: config.update_work_dir.unwrap_or(true),
keyboard_enhanced: false,
kitty_graphics_transmission: config.kitty_graphics_transmission.unwrap_or_default(),
kitty_graphics_display: config.kitty_graphics_display.unwrap_or_default(),
kept_kitty_temp_files,
lines_after_match_in_preview: config.lines_after_match_in_preview.unwrap_or(0),
lines_before_match_in_preview: config.lines_before_match_in_preview.unwrap_or(0),
preview_transformers,
layout_instructions,
server_name,
})
}
pub fn cmd(&self) -> Option<&str> {
self.launch_args
.cmd
.as_ref()
.or(self
.config_default_args
.as_ref()
.and_then(|args| args.cmd.as_ref()))
.map(String::as_str)
}
#[must_use]
pub fn initial_mode(&self) -> Mode {
if self.modal {
self.initial_mode
} else {
Mode::Input
}
}
}
#[cfg(test)]
impl Default for AppContext {
fn default() -> Self {
let mut config = Conf::default();
let verb_store = VerbStore::new(&mut config).unwrap();
let launch_args = parse_default_flags("").unwrap();
Self::from(launch_args, verb_store, &config).unwrap()
}
}
fn are_true_colors_available() -> bool {
if let Ok(colorterm) = std::env::var("COLORTERM") {
debug!("COLORTERM env variable = {colorterm:?}");
if colorterm.contains("truecolor") || colorterm.contains("24bit") {
debug!("true colors are available");
true
} else {
false
}
} else {
true
}
}
fn initial_root_file(cli_args: &Args) -> Result<(PathBuf, Option<PathBuf>), ProgramError> {
let mut file = None;
let mut root = match cli_args.root.as_ref() {
Some(path) => canonicalize_root(path)?,
None => std::env::current_dir()?,
};
if !root.exists() {
return Err(TreeBuildError::FileNotFound {
path: format!("{root:?}"),
}
.into());
}
if !root.is_dir() {
if let Some(parent) = root.parent() {
file = Some(root.clone());
info!("Passed path isn't a directory => opening parent instead");
root = parent.to_path_buf();
} else {
return Err(TreeBuildError::NotADirectory {
path: format!("{root:?}"),
}
.into());
}
}
Ok((root, file))
}
#[cfg(not(windows))]
fn canonicalize_root(root: &Path) -> io::Result<PathBuf> {
root.canonicalize()
}
#[cfg(windows)]
fn canonicalize_root(root: &Path) -> io::Result<PathBuf> {
Ok(if root.is_relative() {
std::env::current_dir()?.join(root)
} else {
root.to_path_buf()
})
}
#[allow(unused_variables)]
fn build_server_name(args: &Args) -> Option<String> {
#[cfg(unix)]
if let Some(name) = &args.listen {
return Some(name.clone());
}
#[cfg(unix)]
if args.listen_auto {
return Some(crate::net::random_server_name());
}
None
}