Skip to content

Commit

Permalink
Merge pull request #318 from eza-community/p-quote_filenames
Browse files Browse the repository at this point in the history
feat: add quotations around filenames with spaces. exa pr#1165
  • Loading branch information
cafkafk authored Sep 27, 2023
2 parents 7cdc743 + 31fa89d commit 51790ed
Show file tree
Hide file tree
Showing 9 changed files with 78 additions and 27 deletions.
3 changes: 2 additions & 1 deletion completions/fish/eza.fish
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ complete -c eza -l color-scale \
-l colour-scale -d "Highlight levels of file sizes distinctly"
complete -c eza -l icons -d "Display icons"
complete -c eza -l no-icons -d "Don't display icons"
complete -c eza -l no-quotes -d "Don't quote file names with spaces"
complete -c eza -l hyperlink -d "Display entries as hyperlinks"

# Filtering and sorting options
Expand All @@ -28,7 +29,7 @@ complete -c eza -l git-ignore -d "Ignore files mentioned in '.gitignore'"
complete -c eza -s a -l all -d "Show hidden and 'dot' files. Use this twice to also show the '.' and '..' directories"
complete -c eza -s d -l list-dirs -d "List directories like regular files"
complete -c eza -s L -l level -d "Limit the depth of recursion" -x -a "1 2 3 4 5 6 7 8 9"
complete -c eza -s w -l width -d "Limits column output of grid, 0 implies auto-width"
complete -c eza -s w -l width -d "Limits column output of grid, 0 implies auto-width"
complete -c eza -s r -l reverse -d "Reverse the sort order"
complete -c eza -s s -l sort -d "Which field to sort by" -x -a "
accessed\t'Sort by file accessed time'
Expand Down
1 change: 1 addition & 0 deletions completions/zsh/_eza
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ __eza() {
--colo{,u}r-scale"[Highlight levels of file sizes distinctly]" \
--icons"[Display icons]" \
--no-icons"[Hide icons]" \
--no-quotes"[Don't quote filenames with spaces]" \
--hyperlink"[Display entries as hyperlinks]" \
--group-directories-first"[Sort directories before other files]" \
--git-ignore"[Ignore files mentioned in '.gitignore']" \
Expand Down
3 changes: 3 additions & 0 deletions man/eza.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ Valid settings are ‘`always`’, ‘`automatic`’, and ‘`never`’.
`--no-icons`
: Don't display icons. (Always overrides --icons)

`--no-quotes`
: Don't quote file names with spaces.

`--hyperlink`
: Display entries as hyperlinks

Expand Down
7 changes: 6 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ use crate::fs::feature::git::GitCache;
use crate::fs::filter::GitIgnore;
use crate::fs::{Dir, File};
use crate::options::{vars, Options, OptionsResult, Vars};
use crate::output::{details, escape, grid, grid_details, lines, Mode, View};
use crate::output::{details, escape, file_name, grid, grid_details, lines, Mode, View};
use crate::theme::Theme;

mod fs;
Expand Down Expand Up @@ -231,6 +231,10 @@ impl<'args> Exa<'args> {
is_only_dir: bool,
exit_status: i32,
) -> io::Result<i32> {
let View {
file_style: file_name::Options { quote_style, .. },
..
} = self.options.view;
for dir in dir_files {
// Put a gap between directories, or between the list of files and
// the first directory.
Expand All @@ -247,6 +251,7 @@ impl<'args> Exa<'args> {
&mut bits,
Style::default(),
Style::default(),
quote_style,
);
writeln!(&mut self.writer, "{}:", ANSIStrings(&bits))?;
}
Expand Down
15 changes: 14 additions & 1 deletion src/options/file_name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,20 @@ use crate::options::parser::MatchedFlags;
use crate::options::vars::{self, Vars};
use crate::options::{flags, NumberSource, OptionsError};

use crate::output::file_name::{Classify, EmbedHyperlinks, Options, ShowIcons};
use crate::output::file_name::{Classify, EmbedHyperlinks, Options, QuoteStyle, ShowIcons};

impl Options {
pub fn deduce<V: Vars>(matches: &MatchedFlags<'_>, vars: &V) -> Result<Self, OptionsError> {
let classify = Classify::deduce(matches)?;
let show_icons = ShowIcons::deduce(matches, vars)?;

let quote_style = QuoteStyle::deduce(matches)?;
let embed_hyperlinks = EmbedHyperlinks::deduce(matches)?;

Ok(Self {
classify,
show_icons,
quote_style,
embed_hyperlinks,
})
}
Expand Down Expand Up @@ -54,6 +57,16 @@ impl ShowIcons {
}
}

impl QuoteStyle {
pub fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
if matches.has(&flags::NO_QUOTES)? {
Ok(Self::NoQuotes)
} else {
Ok(Self::QuoteSpaces)
}
}
}

impl EmbedHyperlinks {
fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
let flagged = matches.has(&flags::HYPERLINK)?;
Expand Down
4 changes: 2 additions & 2 deletions src/options/flags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub static TREE: Arg = Arg { short: Some(b'T'), long: "tree", take
pub static CLASSIFY: Arg = Arg { short: Some(b'F'), long: "classify", takes_value: TakesValue::Forbidden };
pub static DEREF_LINKS: Arg = Arg { short: Some(b'X'), long: "dereference", takes_value: TakesValue::Forbidden };
pub static WIDTH: Arg = Arg { short: Some(b'w'), long: "width", takes_value: TakesValue::Necessary(None) };
pub static NO_QUOTES:Arg = Arg { short: None, long: "no-quotes",takes_value: TakesValue::Forbidden };

pub static COLOR: Arg = Arg { short: None, long: "color", takes_value: TakesValue::Necessary(Some(COLOURS)) };
pub static COLOUR: Arg = Arg { short: None, long: "colour", takes_value: TakesValue::Necessary(Some(COLOURS)) };
Expand Down Expand Up @@ -75,12 +76,11 @@ pub static EXTENDED: Arg = Arg { short: Some(b'@'), long: "extended",
pub static OCTAL: Arg = Arg { short: Some(b'o'), long: "octal-permissions", takes_value: TakesValue::Forbidden };
pub static SECURITY_CONTEXT: Arg = Arg { short: Some(b'Z'), long: "context", takes_value: TakesValue::Forbidden };


pub static ALL_ARGS: Args = Args(&[
&VERSION, &HELP,

&ONE_LINE, &LONG, &GRID, &ACROSS, &RECURSE, &TREE, &CLASSIFY, &DEREF_LINKS,
&COLOR, &COLOUR, &COLOR_SCALE, &COLOUR_SCALE, &WIDTH,
&COLOR, &COLOUR, &COLOR_SCALE, &COLOUR_SCALE, &WIDTH, &NO_QUOTES,

&ALL, &ALMOST_ALL, &LIST_DIRS, &LEVEL, &REVERSE, &SORT, &DIRS_FIRST,
&IGNORE_GLOB, &GIT_IGNORE, &ONLY_DIRS, &ONLY_FILES,
Expand Down
1 change: 1 addition & 0 deletions src/options/help.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ DISPLAY OPTIONS
--colo[u]r-scale highlight levels of file sizes distinctly
--icons display icons
--no-icons don't display icons (always overrides --icons)
--no-quotes don't quote file names with spaces
--hyperlink display entries as hyperlinks
-w, --width COLS set screen width in columns
Expand Down
51 changes: 29 additions & 22 deletions src/output/escape.rs
Original file line number Diff line number Diff line change
@@ -1,31 +1,38 @@
use super::file_name::QuoteStyle;
use ansiterm::{ANSIString, Style};

pub fn escape(string: String, bits: &mut Vec<ANSIString<'_>>, good: Style, bad: Style) {
// if the string has no control character
if string.chars().all(|c| !c.is_control()) {
bits.push(good.paint(string));
return;
}
pub fn escape(
string: String,
bits: &mut Vec<ANSIString<'_>>,
good: Style,
bad: Style,
quote_style: QuoteStyle,
) {
let needs_quotes = string.contains(' ') || string.contains('\'');
let quote_bit = good.paint(if string.contains('\'') { "\"" } else { "\'" });

// the lengthier string of non control character can’t be bigger than the whole string
let mut regular_char_buff = String::with_capacity(string.len());
for c in string.chars() {
// The `escape_default` method on `char` is *almost* what we want here, but
// it still escapes non-ASCII UTF-8 characters, which are still printable.
if string
.chars()
.all(|c| c >= 0x20 as char && c != 0x7f as char)
{
bits.push(good.paint(string));
} else {
for c in string.chars() {
// The `escape_default` method on `char` is *almost* what we want here, but
// it still escapes non-ASCII UTF-8 characters, which are still printable.

if c.is_control() {
if !regular_char_buff.is_empty() {
bits.push(good.paint(std::mem::take(&mut regular_char_buff)));
// TODO: This allocates way too much,
// hence the `all` check above.
if c >= 0x20 as char && c != 0x7f as char {
bits.push(good.paint(c.to_string()));
} else {
bits.push(bad.paint(c.escape_default().to_string()));
}
regular_char_buff.extend(c.escape_default());
// biased towards regular characters, we push control characters immediately
bits.push(bad.paint(std::mem::take(&mut regular_char_buff)));
} else {
regular_char_buff.push(c);
}
}
// if last character was not a control character, the buffer is not empty!
if !regular_char_buff.is_empty() {
bits.push(good.paint(std::mem::take(&mut regular_char_buff)));

if quote_style != QuoteStyle::NoQuotes && needs_quotes {
bits.insert(0, quote_bit.clone());
bits.push(quote_bit);
}
}
20 changes: 20 additions & 0 deletions src/output/file_name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ pub struct Options {
/// Whether to prepend icon characters before file names.
pub show_icons: ShowIcons,

/// How to display file names with spaces (with or without quotes).
pub quote_style: QuoteStyle,

/// Whether to make file names hyperlinks.
pub embed_hyperlinks: EmbedHyperlinks,
}
Expand Down Expand Up @@ -108,6 +111,17 @@ pub enum EmbedHyperlinks {
On,
}

/// Whether or not to wrap file names with spaces in quotes.
#[derive(PartialEq, Debug, Copy, Clone)]
pub enum QuoteStyle {
/// Don't ever quote file names.
NoQuotes,

/// Use single quotes for file names that contain spaces and no single quotes
/// Use double quotes for file names that contain single quotes.
QuoteSpaces,
}

/// A **file name** holds all the information necessary to display the name
/// of the given file. This is used in all of the views.
pub struct FileName<'a, 'dir, C> {
Expand Down Expand Up @@ -208,6 +222,9 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
let target_options = Options {
classify: Classify::JustFilenames,
show_icons: ShowIcons::Off,

quote_style: QuoteStyle::QuoteSpaces,

embed_hyperlinks: EmbedHyperlinks::Off,
};

Expand Down Expand Up @@ -243,6 +260,7 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
&mut bits,
self.colours.broken_filename(),
self.colours.broken_control_char(),
self.options.quote_style,
);
}

Expand Down Expand Up @@ -287,6 +305,7 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
bits,
self.colours.symlink_path(),
self.colours.control_char(),
self.options.quote_style,
);
bits.push(
self.colours
Expand Down Expand Up @@ -373,6 +392,7 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
&mut bits,
file_style,
self.colours.control_char(),
self.options.quote_style,
);

if display_hyperlink {
Expand Down

0 comments on commit 51790ed

Please sign in to comment.