Просмотр исходного кода

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

PThorpe92 2 лет назад
Родитель
Сommit
1412841cfd
6 измененных файлов с 76 добавлено и 25 удалено
  1. 6 1
      src/main.rs
  2. 14 1
      src/options/file_name.rs
  3. 5 1
      src/options/flags.rs
  4. 2 0
      src/options/help.rs
  5. 29 22
      src/output/escape.rs
  6. 20 0
      src/output/file_name.rs

+ 6 - 1
src/main.rs

@@ -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;
@@ -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.
@@ -247,6 +251,7 @@ impl<'args> Exa<'args> {
                     &mut bits,
                     Style::default(),
                     Style::default(),
+                    quote_style,
                 );
                 writeln!(&mut self.writer, "{}:", ANSIStrings(&bits))?;
             }

+ 14 - 1
src/options/file_name.rs

@@ -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,
         })
     }
@@ -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)?;

+ 5 - 1
src/options/flags.rs

@@ -5,6 +5,7 @@ pub static VERSION: Arg = Arg { short: Some(b'v'), long: "version",  takes_value
 pub static HELP:    Arg = Arg { short: Some(b'?'), long: "help",     takes_value: TakesValue::Forbidden };
 
 // display options
+
 pub static ONE_LINE:    Arg = Arg { short: Some(b'1'), long: "oneline",     takes_value: TakesValue::Forbidden };
 pub static LONG:        Arg = Arg { short: Some(b'l'), long: "long",        takes_value: TakesValue::Forbidden };
 pub static GRID:        Arg = Arg { short: Some(b'G'), long: "grid",        takes_value: TakesValue::Forbidden };
@@ -14,6 +15,8 @@ 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)) };
@@ -80,7 +83,8 @@ 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,

+ 2 - 0
src/options/help.rs

@@ -23,9 +23,11 @@ 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
 
+
 FILTERING AND SORTING OPTIONS
   -a, --all                  show hidden and 'dot' files. Use this twice to also show the '.' and '..' directories
   -d, --list-dirs            list directories as files; don't list their contents

+ 29 - 22
src/output/escape.rs

@@ -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 - 0
src/output/file_name.rs

@@ -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,
 }
@@ -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> {
@@ -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,
                         };
 
@@ -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,
                     );
                 }
 
@@ -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
@@ -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 {