Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add quotations around filenames with spaces. exa pr#1165 #318

Merged
merged 4 commits into from
Sep 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 };

PThorpe92 marked this conversation as resolved.
Show resolved Hide resolved
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,

PThorpe92 marked this conversation as resolved.
Show resolved Hide resolved
&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,
}

PThorpe92 marked this conversation as resolved.
Show resolved Hide resolved
/// 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