-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
92c3de5
commit 9266514
Showing
6 changed files
with
342 additions
and
103 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
// This file is part of the uutils coreutils package. | ||
// | ||
// For the full copyright and license information, please view the LICENSE | ||
// file that was distributed with this source code. | ||
use super::get_metadata_with_deref_opt; | ||
use super::PathData; | ||
use lscolors::{Indicator, LsColors, Style}; | ||
use std::fs::{DirEntry, Metadata}; | ||
use std::io::{BufWriter, Stdout}; | ||
|
||
/// We need this struct to be able to store the previous style. | ||
/// This because we need to check the previous value in case we don't need | ||
/// the reset | ||
pub(crate) struct StyleManager<'a> { | ||
/// last style that is applied, if `None` that means reset is applied. | ||
pub(crate) current_style: Option<Style>, | ||
/// `true` if the initial reset is applied | ||
pub(crate) initial_reset_is_done: bool, | ||
pub(crate) colors: &'a LsColors, | ||
} | ||
|
||
impl<'a> StyleManager<'a> { | ||
pub(crate) fn new(colors: &'a LsColors) -> Self { | ||
Self { | ||
initial_reset_is_done: false, | ||
current_style: None, | ||
colors, | ||
} | ||
} | ||
|
||
pub(crate) fn apply_style(&mut self, new_style: Option<&Style>, name: &str) -> String { | ||
let mut style_code = String::new(); | ||
let mut force_suffix_reset: bool = false; | ||
|
||
// if reset is done we need to apply normal style before applying new style | ||
if self.is_reset() { | ||
if let Some(norm_sty) = self.get_normal_style().copied() { | ||
style_code.push_str(&self.get_style_code(&norm_sty)); | ||
} | ||
} | ||
|
||
if let Some(new_style) = new_style { | ||
// we only need to apply a new style if it's not the same as the current | ||
// style for example if normal is the current style and a file with | ||
// normal style is to be printed we could skip printing new color | ||
// codes | ||
if !self.is_current_style(new_style) { | ||
style_code.push_str(&self.reset(!self.initial_reset_is_done)); | ||
style_code.push_str(&self.get_style_code(new_style)); | ||
} | ||
} | ||
// if new style is None and current style is Normal we should reset it | ||
else if matches!(self.get_normal_style().copied(), Some(norm_style) if self.is_current_style(&norm_style)) | ||
{ | ||
style_code.push_str(&self.reset(false)); | ||
// even though this is an unnecessary reset for gnu compatibility we allow it here | ||
force_suffix_reset = true; | ||
} | ||
|
||
format!("{}{}{}", style_code, name, self.reset(force_suffix_reset)) | ||
} | ||
|
||
/// Resets the current style and returns the default ANSI reset code to | ||
/// reset all text formatting attributes. If `force` is true, the reset is | ||
/// done even if the reset has been applied before. | ||
pub(crate) fn reset(&mut self, force: bool) -> String { | ||
// todo: | ||
// We need to use style from `Indicator::Reset` but as of now ls colors | ||
// uses a fallback mechanism and because of that if `Indicator::Reset` | ||
// is not specified it would fallback to `Indicator::Normal` which seems | ||
// to be non compatible with gnu | ||
if self.current_style.is_some() || force { | ||
self.initial_reset_is_done = true; | ||
self.current_style = None; | ||
return "\x1b[0m".to_string(); | ||
} | ||
String::new() | ||
} | ||
|
||
pub(crate) fn get_style_code(&mut self, new_style: &Style) -> String { | ||
self.current_style = Some(*new_style); | ||
let mut nu_a_style = new_style.to_nu_ansi_term_style(); | ||
nu_a_style.prefix_with_reset = false; | ||
let mut ret = nu_a_style.paint("").to_string(); | ||
// remove the suffix reset | ||
ret.truncate(ret.len() - 4); | ||
ret | ||
} | ||
|
||
pub(crate) fn is_current_style(&mut self, new_style: &Style) -> bool { | ||
matches!(&self.current_style,Some(style) if style == new_style ) | ||
} | ||
|
||
pub(crate) fn is_reset(&mut self) -> bool { | ||
self.current_style.is_none() | ||
} | ||
|
||
pub(crate) fn get_normal_style(&self) -> Option<&Style> { | ||
self.colors.style_for_indicator(Indicator::Normal) | ||
} | ||
pub(crate) fn apply_normal(&mut self) -> String { | ||
if let Some(sty) = self.get_normal_style().copied() { | ||
return self.get_style_code(&sty); | ||
} | ||
String::new() | ||
} | ||
|
||
pub(crate) fn apply_style_based_on_metadata( | ||
&mut self, | ||
path: &PathData, | ||
md_option: Option<&Metadata>, | ||
name: &str, | ||
) -> String { | ||
let style = self | ||
.colors | ||
.style_for_path_with_metadata(&path.p_buf, md_option); | ||
self.apply_style(style, name) | ||
} | ||
|
||
pub(crate) fn apply_style_based_on_dir_entry( | ||
&mut self, | ||
dir_entry: &DirEntry, | ||
name: &str, | ||
) -> String { | ||
let style = self.colors.style_for(dir_entry); | ||
self.apply_style(style, name) | ||
} | ||
} | ||
|
||
/// Colors the provided name based on the style determined for the given path | ||
/// This function is quite long because it tries to leverage DirEntry to avoid | ||
/// unnecessary calls to stat() | ||
/// and manages the symlink errors | ||
pub(crate) fn color_name( | ||
name: &str, | ||
path: &PathData, | ||
style_manager: &mut StyleManager, | ||
out: &mut BufWriter<Stdout>, | ||
target_symlink: Option<&PathData>, | ||
) -> String { | ||
if !path.must_dereference { | ||
// If we need to dereference (follow) a symlink, we will need to get the metadata | ||
if let Some(de) = &path.de { | ||
// There is a DirEntry, we don't need to get the metadata for the color | ||
return style_manager.apply_style_based_on_dir_entry(de, name); | ||
} | ||
} | ||
|
||
if let Some(target) = target_symlink { | ||
// use the optional target_symlink | ||
// Use fn get_metadata_with_deref_opt instead of get_metadata() here because ls | ||
// should not exit with an err, if we are unable to obtain the target_metadata | ||
let md_res = get_metadata_with_deref_opt(&target.p_buf, path.must_dereference); | ||
let md = md_res.or(path.p_buf.symlink_metadata()); | ||
style_manager.apply_style_based_on_metadata(path, md.ok().as_ref(), name) | ||
} else { | ||
let md_option = path.get_metadata(out); | ||
let symlink_metadata = path.p_buf.symlink_metadata().ok(); | ||
let md = md_option.or(symlink_metadata.as_ref()); | ||
style_manager.apply_style_based_on_metadata(path, md, name) | ||
} | ||
} |
Oops, something went wrong.