mirror of
https://github.com/Llewellynvdm/exa.git
synced 2024-11-30 07:33:53 +00:00
commit
3ebc22580a
33
Vagrantfile
vendored
33
Vagrantfile
vendored
@ -142,6 +142,39 @@ Vagrant.configure(2) do |config|
|
|||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
|
||||||
|
# File name testcases.
|
||||||
|
# bash really doesn’t want you to create a file with escaped characters
|
||||||
|
# in its name, so we have to resort to the echo builtin and touch!
|
||||||
|
#
|
||||||
|
# The double backslashes are not strictly necessary; without them, Ruby
|
||||||
|
# will interpolate them instead of bash, but because Vagrant prints out
|
||||||
|
# each command it runs, your *own* terminal will go “ding” from the alarm!
|
||||||
|
config.vm.provision :shell, privileged: false, inline: <<-EOF
|
||||||
|
set -xe
|
||||||
|
mkdir "#{test_dir}/file-names"
|
||||||
|
|
||||||
|
echo -ne "#{test_dir}/file-names/ascii: hello" | xargs -0 touch
|
||||||
|
echo -ne "#{test_dir}/file-names/emoji: [🆒]" | xargs -0 touch
|
||||||
|
echo -ne "#{test_dir}/file-names/utf-8: pâté" | xargs -0 touch
|
||||||
|
|
||||||
|
echo -ne "#{test_dir}/file-names/bell: [\\a]" | xargs -0 touch
|
||||||
|
echo -ne "#{test_dir}/file-names/backspace: [\\b]" | xargs -0 touch
|
||||||
|
echo -ne "#{test_dir}/file-names/form-feed: [\\f]" | xargs -0 touch
|
||||||
|
echo -ne "#{test_dir}/file-names/new-line: [\\n]" | xargs -0 touch
|
||||||
|
echo -ne "#{test_dir}/file-names/return: [\\r]" | xargs -0 touch
|
||||||
|
echo -ne "#{test_dir}/file-names/tab: [\\t]" | xargs -0 touch
|
||||||
|
echo -ne "#{test_dir}/file-names/vertical-tab: [\\v]" | xargs -0 touch
|
||||||
|
|
||||||
|
echo -ne "#{test_dir}/file-names/escape: [\\033]" | xargs -0 touch
|
||||||
|
echo -ne "#{test_dir}/file-names/ansi: [\\033[34mblue\\033[0m]" | xargs -0 touch
|
||||||
|
|
||||||
|
echo -ne "#{test_dir}/file-names/invalid-utf8-1: [\\xFF]" | xargs -0 touch
|
||||||
|
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
|
||||||
|
EOF
|
||||||
|
|
||||||
|
|
||||||
# Special file testcases.
|
# Special file testcases.
|
||||||
config.vm.provision :shell, privileged: false, inline: <<-EOF
|
config.vm.provision :shell, privileged: false, inline: <<-EOF
|
||||||
set -xe
|
set -xe
|
||||||
|
@ -5,8 +5,6 @@ use std::ops::{Add, Deref, DerefMut};
|
|||||||
use ansi_term::{Style, ANSIString, ANSIStrings};
|
use ansi_term::{Style, ANSIString, ANSIStrings};
|
||||||
use unicode_width::UnicodeWidthStr;
|
use unicode_width::UnicodeWidthStr;
|
||||||
|
|
||||||
use fs::File;
|
|
||||||
|
|
||||||
|
|
||||||
/// An individual cell that holds text in a table, used in the details and
|
/// An individual cell that holds text in a table, used in the details and
|
||||||
/// lines views to store ANSI-terminal-formatted data before it is printed.
|
/// lines views to store ANSI-terminal-formatted data before it is printed.
|
||||||
@ -161,6 +159,11 @@ impl TextCellContents {
|
|||||||
pub fn strings(&self) -> ANSIStrings {
|
pub fn strings(&self) -> ANSIStrings {
|
||||||
ANSIStrings(&self.0)
|
ANSIStrings(&self.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn width(&self) -> DisplayWidth {
|
||||||
|
let foo = self.0.iter().map(|anstr| anstr.chars().count()).sum();
|
||||||
|
DisplayWidth(foo)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -180,19 +183,6 @@ impl TextCellContents {
|
|||||||
#[derive(PartialEq, Debug, Clone, Copy, Default)]
|
#[derive(PartialEq, Debug, Clone, Copy, Default)]
|
||||||
pub struct DisplayWidth(usize);
|
pub struct DisplayWidth(usize);
|
||||||
|
|
||||||
impl DisplayWidth {
|
|
||||||
pub fn from_file(file: &File, classify: bool) -> DisplayWidth {
|
|
||||||
let name_width = *DisplayWidth::from(&*file.name);
|
|
||||||
if classify {
|
|
||||||
if file.is_executable_file() || file.is_directory() ||
|
|
||||||
file.is_pipe() || file.is_link() || file.is_socket() {
|
|
||||||
return DisplayWidth(name_width + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DisplayWidth(name_width)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> From<&'a str> for DisplayWidth {
|
impl<'a> From<&'a str> for DisplayWidth {
|
||||||
fn from(input: &'a str) -> DisplayWidth {
|
fn from(input: &'a str) -> DisplayWidth {
|
||||||
DisplayWidth(UnicodeWidthStr::width(input))
|
DisplayWidth(UnicodeWidthStr::width(input))
|
||||||
|
@ -22,6 +22,7 @@ pub struct Colours {
|
|||||||
pub symlink_path: Style,
|
pub symlink_path: Style,
|
||||||
pub broken_arrow: Style,
|
pub broken_arrow: Style,
|
||||||
pub broken_filename: Style,
|
pub broken_filename: Style,
|
||||||
|
pub control_char: Style,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||||
@ -170,7 +171,8 @@ impl Colours {
|
|||||||
|
|
||||||
symlink_path: Cyan.normal(),
|
symlink_path: Cyan.normal(),
|
||||||
broken_arrow: Red.normal(),
|
broken_arrow: Red.normal(),
|
||||||
broken_filename: Red.underline()
|
broken_filename: Red.underline(),
|
||||||
|
control_char: Red.normal(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -306,7 +306,9 @@ impl Details {
|
|||||||
for (index, egg) in file_eggs.into_iter().enumerate() {
|
for (index, egg) in file_eggs.into_iter().enumerate() {
|
||||||
let mut files = Vec::new();
|
let mut files = Vec::new();
|
||||||
let mut errors = egg.errors;
|
let mut errors = egg.errors;
|
||||||
let mut width = DisplayWidth::from_file(&egg.file, self.classify);
|
|
||||||
|
let filename = filename(&egg.file, &self.colours, true, self.classify);
|
||||||
|
let mut width = filename.width();
|
||||||
|
|
||||||
if egg.file.dir.is_none() {
|
if egg.file.dir.is_none() {
|
||||||
if let Some(parent) = egg.file.path.parent() {
|
if let Some(parent) = egg.file.path.parent() {
|
||||||
@ -315,7 +317,7 @@ impl Details {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let name = TextCell {
|
let name = TextCell {
|
||||||
contents: filename(&egg.file, &self.colours, true, self.classify),
|
contents: filename,
|
||||||
width: width,
|
width: width,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -456,7 +458,8 @@ impl<'a, U: Users+Groups+'a> Table<'a, U> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn filename_cell(&self, file: File, links: bool) -> TextCell {
|
pub fn filename_cell(&self, file: File, links: bool) -> TextCell {
|
||||||
let mut width = DisplayWidth::from_file(&file, self.opts.classify);
|
let filename = filename(&file, &self.opts.colours, links, self.opts.classify);
|
||||||
|
let mut width = filename.width();
|
||||||
|
|
||||||
if file.dir.is_none() {
|
if file.dir.is_none() {
|
||||||
if let Some(parent) = file.path.parent() {
|
if let Some(parent) = file.path.parent() {
|
||||||
@ -465,7 +468,7 @@ impl<'a, U: Users+Groups+'a> Table<'a, U> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TextCell {
|
TextCell {
|
||||||
contents: filename(&file, &self.opts.colours, links, self.opts.classify),
|
contents: filename,
|
||||||
width: width,
|
width: width,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,8 +29,9 @@ impl Grid {
|
|||||||
grid.reserve(files.len());
|
grid.reserve(files.len());
|
||||||
|
|
||||||
for file in files.iter() {
|
for file in files.iter() {
|
||||||
let mut width = DisplayWidth::from_file(file, self.classify);
|
let filename = filename(file, &self.colours, false, self.classify);
|
||||||
|
|
||||||
|
let mut width = filename.width();
|
||||||
if file.dir.is_none() {
|
if file.dir.is_none() {
|
||||||
if let Some(parent) = file.path.parent() {
|
if let Some(parent) = file.path.parent() {
|
||||||
width = width + 1 + DisplayWidth::from(parent.to_string_lossy().as_ref());
|
width = width + 1 + DisplayWidth::from(parent.to_string_lossy().as_ref());
|
||||||
@ -38,7 +39,7 @@ impl Grid {
|
|||||||
}
|
}
|
||||||
|
|
||||||
grid.add(grid::Cell {
|
grid.add(grid::Cell {
|
||||||
contents: filename(file, &self.colours, false, self.classify).strings().to_string(),
|
contents: filename.strings().to_string(),
|
||||||
width: *width,
|
width: *width,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use ansi_term::Style;
|
use ansi_term::{ANSIString, Style};
|
||||||
|
|
||||||
use fs::{File, FileTarget};
|
use fs::{File, FileTarget};
|
||||||
|
|
||||||
@ -22,6 +22,8 @@ mod tree;
|
|||||||
pub fn filename(file: &File, colours: &Colours, links: bool, classify: bool) -> TextCellContents {
|
pub fn filename(file: &File, colours: &Colours, links: bool, classify: bool) -> TextCellContents {
|
||||||
let mut bits = Vec::new();
|
let mut bits = Vec::new();
|
||||||
|
|
||||||
|
// TODO: This long function could do with some splitting up.
|
||||||
|
|
||||||
if file.dir.is_none() {
|
if file.dir.is_none() {
|
||||||
if let Some(parent) = file.path.parent() {
|
if let Some(parent) = file.path.parent() {
|
||||||
let coconut = parent.components().count();
|
let coconut = parent.components().count();
|
||||||
@ -37,7 +39,9 @@ pub fn filename(file: &File, colours: &Colours, links: bool, classify: bool) ->
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !file.name.is_empty() {
|
if !file.name.is_empty() {
|
||||||
bits.push(file_colour(colours, file).paint(file.name.clone()));
|
for bit in coloured_file_name(file, colours) {
|
||||||
|
bits.push(bit);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if links && file.is_link() {
|
if links && file.is_link() {
|
||||||
@ -92,6 +96,44 @@ pub fn filename(file: &File, colours: &Colours, links: bool, classify: bool) ->
|
|||||||
bits.into()
|
bits.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns at least one ANSI-highlighted string representing this file’s
|
||||||
|
/// name using the given set of colours.
|
||||||
|
///
|
||||||
|
/// Ordinarily, this will be just one string: the file’s complete name,
|
||||||
|
/// coloured according to its file type. If the name contains control
|
||||||
|
/// characters such as newlines or escapes, though, we can’t just print them
|
||||||
|
/// to the screen directly, because then there’ll be newlines in weird places.
|
||||||
|
///
|
||||||
|
/// So in that situation, those characters will be escaped and highlighted in
|
||||||
|
/// a different colour.
|
||||||
|
fn coloured_file_name<'a>(file: &File, colours: &Colours) -> Vec<ANSIString<'a>> {
|
||||||
|
let colour = file_colour(colours, file);
|
||||||
|
let mut bits = Vec::new();
|
||||||
|
|
||||||
|
if file.name.chars().all(|c| c >= 0x20 as char) {
|
||||||
|
bits.push(colour.paint(file.name.clone()));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for c in 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(colour.paint(s));
|
||||||
|
} else {
|
||||||
|
let s = c.escape_default().collect::<String>();
|
||||||
|
bits.push(colours.control_char.paint(s));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bits
|
||||||
|
}
|
||||||
|
|
||||||
pub fn file_colour(colours: &Colours, file: &File) -> Style {
|
pub fn file_colour(colours: &Colours, file: &File) -> Style {
|
||||||
match file {
|
match file {
|
||||||
f if f.is_directory() => colours.filetypes.directory,
|
f if f.is_directory() => colours.filetypes.directory,
|
||||||
|
6
xtests/file_names
Normal file
6
xtests/file_names
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
ansi: [[31m\u{1b}[0m[34mblue[31m\u{1b}[0m[0m] form-feed: [[31m\u{c}[0m] return: [[31m\r[0m]
|
||||||
|
ascii: hello invalid-utf8-1: [<5B>] tab: [[31m\t[0m]
|
||||||
|
backspace: [[31m\u{8}[0m] invalid-utf8-2: [<5B>(] utf-8: pâté
|
||||||
|
bell: [[31m\u{7}[0m] invalid-utf8-3: [<5B>(] vertical-tab: [[31m\u{b}[0m]
|
||||||
|
emoji: [🆒] invalid-utf8-4: [<5B>(<28>(]
|
||||||
|
escape: [[31m\u{1b}[0m] new-line: [[31m\n[0m]
|
16
xtests/file_names_1
Normal file
16
xtests/file_names_1
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
ansi: [[31m\u{1b}[0m[34mblue[31m\u{1b}[0m[0m]
|
||||||
|
ascii: hello
|
||||||
|
backspace: [[31m\u{8}[0m]
|
||||||
|
bell: [[31m\u{7}[0m]
|
||||||
|
emoji: [🆒]
|
||||||
|
escape: [[31m\u{1b}[0m]
|
||||||
|
form-feed: [[31m\u{c}[0m]
|
||||||
|
invalid-utf8-1: [<5B>]
|
||||||
|
invalid-utf8-2: [<5B>(]
|
||||||
|
invalid-utf8-3: [<5B>(]
|
||||||
|
invalid-utf8-4: [<5B>(<28>(]
|
||||||
|
new-line: [[31m\n[0m]
|
||||||
|
return: [[31m\r[0m]
|
||||||
|
tab: [[31m\t[0m]
|
||||||
|
utf-8: pâté
|
||||||
|
vertical-tab: [[31m\u{b}[0m]
|
6
xtests/file_names_x
Normal file
6
xtests/file_names_x
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
ansi: [[31m\u{1b}[0m[34mblue[31m\u{1b}[0m[0m] ascii: hello backspace: [[31m\u{8}[0m]
|
||||||
|
bell: [[31m\u{7}[0m] emoji: [🆒] escape: [[31m\u{1b}[0m]
|
||||||
|
form-feed: [[31m\u{c}[0m] invalid-utf8-1: [<5B>] invalid-utf8-2: [<5B>(]
|
||||||
|
invalid-utf8-3: [<5B>(] invalid-utf8-4: [<5B>(<28>(] new-line: [[31m\n[0m]
|
||||||
|
return: [[31m\r[0m] tab: [[31m\t[0m] utf-8: pâté
|
||||||
|
vertical-tab: [[31m\u{b}[0m]
|
@ -54,6 +54,10 @@ $exa $testcases/passwd -lgh | diff -q - $results/passwd || exit 1
|
|||||||
sudo -u cassowary $exa $testcases/permissions -lghR 2>&1 | diff -q - $results/permissions_sudo || exit 1
|
sudo -u cassowary $exa $testcases/permissions -lghR 2>&1 | diff -q - $results/permissions_sudo || exit 1
|
||||||
$exa $testcases/permissions -lghR 2>&1 | diff -q - $results/permissions || exit 1
|
$exa $testcases/permissions -lghR 2>&1 | diff -q - $results/permissions || exit 1
|
||||||
|
|
||||||
|
# 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
|
||||||
|
$exa $testcases/file-names -1 2>&1 | diff -q - $results/file_names_1 || exit 1
|
||||||
|
|
||||||
# File types
|
# File types
|
||||||
$exa $testcases/file-names-exts -1 2>&1 | diff -q - $results/file-names-exts || exit 1
|
$exa $testcases/file-names-exts -1 2>&1 | diff -q - $results/file-names-exts || exit 1
|
||||||
|
Loading…
Reference in New Issue
Block a user