Przeglądaj źródła

Also escape characters in links and headings

Doing this meant that the escaping functionality got used in three places, so it was extracted into a generalised function in its own module.

This is slighly slower for the case where escaped characters are displayed in the same colour as the displayable characters, which happens when listing a directory’s name when recursing. Optimise this, yeah?
Benjamin Sago 8 lat temu
rodzic
commit
56d4d4c156

+ 7 - 0
Vagrantfile

@@ -172,6 +172,13 @@ Vagrant.configure(2) do |config|
         echo -ne "#{test_dir}/file-names/invalid-utf8-2: [\\xc3\\x28]"           | xargs -0 touch
         echo -ne "#{test_dir}/file-names/invalid-utf8-3: [\\xe2\\x82\\x28]"      | xargs -0 touch
         echo -ne "#{test_dir}/file-names/invalid-utf8-4: [\\xf0\\x28\\x8c\\x28]" | xargs -0 touch
+
+        echo -ne "#{test_dir}/file-names/new-line-dir: [\\n]"                | xargs -0 mkdir
+        echo -ne "#{test_dir}/file-names/new-line-dir: [\\n]/subfile"        | xargs -0 touch
+        echo -ne "#{test_dir}/file-names/new-line-dir: [\\n]/another: [\\n]" | xargs -0 touch
+
+        mkdir "#{test_dir}/file-names/links"
+        ln -s "#{test_dir}/file-names/new-line-dir"*/* "#{test_dir}/file-names/links"
     EOF
 
 

+ 6 - 1
src/exa.rs

@@ -23,9 +23,12 @@ use std::ffi::OsStr;
 use std::io::{stderr, Write, Result as IOResult};
 use std::path::{Component, Path};
 
+use ansi_term::{ANSIStrings, Style};
+
 use fs::{Dir, File};
 use options::{Options, View};
 pub use options::Misfire;
+use output::escape;
 
 mod fs;
 mod info;
@@ -116,7 +119,9 @@ impl<'w, W: Write + 'w> Exa<'w, W> {
             }
 
             if !is_only_dir {
-                writeln!(self.writer, "{}:", dir.path.display())?;
+                let mut bits = Vec::new();
+                escape(dir.path.display().to_string(), &mut bits, Style::default(), Style::default());
+                writeln!(self.writer, "{}:", ANSIStrings(&bits))?;
             }
 
             let mut children = Vec::new();

+ 25 - 0
src/output/escape.rs

@@ -0,0 +1,25 @@
+use ansi_term::{ANSIString, Style};
+
+
+pub fn escape<'a>(string: String, bits: &mut Vec<ANSIString<'a>>, good: Style, bad: Style) {
+    if string.chars().all(|c| c >= 0x20 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 >= 0x20 as char {
+                // TODO: This allocates way too much,
+                // hence the `all` check above.
+                let mut s = String::new();
+                s.push(c);
+                bits.push(good.paint(s));
+            } else {
+                let s = c.escape_default().collect::<String>();
+                bits.push(bad.paint(s));
+            }
+        }
+    }
+}

+ 4 - 24
src/output/file_name.rs

@@ -2,6 +2,7 @@ use ansi_term::{ANSIString, Style};
 
 use fs::{File, FileTarget};
 use output::Colours;
+use output::escape;
 use output::cell::TextCellContents;
 
 
@@ -29,7 +30,7 @@ impl<'a, 'dir> FileName<'a, 'dir> {
                     bits.push(self.colours.symlink_path.paint("/"));
                 }
                 else if coconut >= 1 {
-                    bits.push(self.colours.symlink_path.paint(parent.to_string_lossy().to_string()));
+                    escape(parent.to_string_lossy().to_string(), &mut bits, self.colours.symlink_path, self.colours.control_char);
                     bits.push(self.colours.symlink_path.paint("/"));
                 }
             }
@@ -55,7 +56,7 @@ impl<'a, 'dir> FileName<'a, 'dir> {
                             bits.push(self.colours.symlink_path.paint("/"));
                         }
                         else if coconut >= 1 {
-                            bits.push(self.colours.symlink_path.paint(parent.to_string_lossy().to_string()));
+                            escape(parent.to_string_lossy().to_string(), &mut bits, self.colours.symlink_path, self.colours.control_char);
                             bits.push(self.colours.symlink_path.paint("/"));
                         }
                     }
@@ -118,28 +119,7 @@ impl<'a, 'dir> FileName<'a, 'dir> {
     fn coloured_file_name<'unused>(&self) -> Vec<ANSIString<'unused>> {
         let file_style = self.style();
         let mut bits = Vec::new();
-
-        if self.file.name.chars().all(|c| c >= 0x20 as char) {
-            bits.push(file_style.paint(self.file.name.clone()));
-        }
-        else {
-            for c in self.file.name.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 >= 0x20 as char {
-                    // TODO: This allocates way too much,
-                    // hence the `all` check above.
-                    let mut s = String::new();
-                    s.push(c);
-                    bits.push(file_style.paint(s));
-                } else {
-                    let s = c.escape_default().collect::<String>();
-                    bits.push(self.colours.control_char.paint(s));
-                }
-            }
-        }
-
+        escape(self.file.name.clone(), &mut bits, file_style, self.colours.control_char);
         bits
     }
 

+ 2 - 0
src/output/mod.rs

@@ -4,6 +4,7 @@ pub use self::details::Details;
 pub use self::grid_details::GridDetails;
 pub use self::grid::Grid;
 pub use self::lines::Lines;
+pub use self::escape::escape;
 
 mod grid;
 pub mod details;
@@ -14,3 +15,4 @@ mod cell;
 mod colours;
 mod tree;
 pub mod file_name;
+mod escape;

+ 6 - 6
xtests/file_names

@@ -1,6 +1,6 @@
-ansi: [\u{1b}[34mblue\u{1b}[0m]  form-feed: [\u{c}]      return: [\r]
-ascii: hello                     invalid-utf8-1: [�]     tab: [\t]
-backspace: [\u{8}]               invalid-utf8-2: [�(]    utf-8: pâté
-bell: [\u{7}]                    invalid-utf8-3: [�(]    vertical-tab: [\u{b}]
-emoji: [🆒]                       invalid-utf8-4: [�(�(]  
-escape: [\u{1b}]                 new-line: [\n]          
+ansi: [\u{1b}[34mblue\u{1b}[0m]  form-feed: [\u{c}]      new-line-dir: [\n]
+ascii: hello                     invalid-utf8-1: [�]     new-line: [\n]
+backspace: [\u{8}]               invalid-utf8-2: [�(]    return: [\r]
+bell: [\u{7}]                    invalid-utf8-3: [�(]    tab: [\t]
+emoji: [🆒]                       invalid-utf8-4: [�(�(]  utf-8: pâté
+escape: [\u{1b}]                 links                   vertical-tab: [\u{b}]

+ 2 - 0
xtests/file_names_1

@@ -9,6 +9,8 @@ invalid-utf8-1: [�]
 invalid-utf8-2: [�(]
 invalid-utf8-3: [�(]
 invalid-utf8-4: [�(�(]
+links
+new-line-dir: [\n]
 new-line: [\n]
 return: [\r]
 tab: [\t]

+ 12 - 0
xtests/file_names_R

@@ -0,0 +1,12 @@
+ansi: [\u{1b}[34mblue\u{1b}[0m]  form-feed: [\u{c}]      new-line-dir: [\n]
+ascii: hello                     invalid-utf8-1: [�]     new-line: [\n]
+backspace: [\u{8}]               invalid-utf8-2: [�(]    return: [\r]
+bell: [\u{7}]                    invalid-utf8-3: [�(]    tab: [\t]
+emoji: [🆒]                       invalid-utf8-4: [�(�(]  utf-8: pâté
+escape: [\u{1b}]                 links                   vertical-tab: [\u{b}]
+
+/testcases/file-names/links:
+another: [\n]  subfile
+
+/testcases/file-names/new-line-dir: [\n]:
+another: [\n]  subfile

+ 27 - 0
xtests/file_names_T

@@ -0,0 +1,27 @@
+/testcases/file-names
+├── ansi: [\u{1b}[34mblue\u{1b}[0m]
+├── ascii: hello
+├── backspace: [\u{8}]
+├── bell: [\u{7}]
+├── emoji: [🆒]
+├── escape: [\u{1b}]
+├── form-feed: [\u{c}]
+├── invalid-utf8-1: [�]
+│  └── <Error: path somehow contained a NUL?>
+├── invalid-utf8-2: [�(]
+│  └── <Error: path somehow contained a NUL?>
+├── invalid-utf8-3: [�(]
+│  └── <Error: path somehow contained a NUL?>
+├── invalid-utf8-4: [�(�(]
+│  └── <Error: path somehow contained a NUL?>
+├── links
+│  ├── another: [\n] -> /testcases/file-names/new-line-dir: [\n]/another: [\n]
+│  └── subfile -> /testcases/file-names/new-line-dir: [\n]/subfile
+├── new-line-dir: [\n]
+│  ├── another: [\n]
+│  └── subfile
+├── new-line: [\n]
+├── return: [\r]
+├── tab: [\t]
+├── utf-8: pâté
+└── vertical-tab: [\u{b}]

+ 3 - 3
xtests/file_names_x

@@ -1,6 +1,6 @@
 ansi: [\u{1b}[34mblue\u{1b}[0m]  ascii: hello            backspace: [\u{8}]
 bell: [\u{7}]                    emoji: [🆒]              escape: [\u{1b}]
 form-feed: [\u{c}]               invalid-utf8-1: [�]     invalid-utf8-2: [�(]
-invalid-utf8-3: [�(]             invalid-utf8-4: [�(�(]  new-line: [\n]
-return: [\r]                     tab: [\t]               utf-8: pâté
-vertical-tab: [\u{b}]            
+invalid-utf8-3: [�(]             invalid-utf8-4: [�(�(]  links
+new-line-dir: [\n]               new-line: [\n]          return: [\r]
+tab: [\t]                        utf-8: pâté             vertical-tab: [\u{b}]

+ 3 - 0
xtests/run.sh

@@ -55,9 +55,12 @@ sudo -u cassowary $exa $testcases/permissions -lghR 2>&1 | diff -q - $results/pe
                   $exa $testcases/permissions -lghR 2>&1 | diff -q - $results/permissions       || exit 1
 
 # File names
+# (Mostly escaping control characters in file names)
 COLUMNS=80 $exa $testcases/file-names    2>&1 | diff -q - $results/file_names   || exit 1
 COLUMNS=80 $exa $testcases/file-names -x 2>&1 | diff -q - $results/file_names_x || exit 1
+COLUMNS=80 $exa $testcases/file-names -R 2>&1 | diff -q - $results/file_names_R || exit 1
            $exa $testcases/file-names -1 2>&1 | diff -q - $results/file_names_1 || exit 1
+           $exa $testcases/file-names -T 2>&1 | diff -q - $results/file_names_T || exit 1
 
 # File types
 $exa $testcases/file-names-exts -1 2>&1 | diff -q - $results/file-names-exts  || exit 1