mirror of
https://github.com/nushell/nushell.git
synced 2025-06-30 14:40:06 +02:00
enable ls_colors for the ls
command (#340)
* enable ls_colors for the `ls` command * added wrapping with ansi-cut so the ansi sequences don't bleed over * clippy
This commit is contained in:
@ -1,4 +1,5 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use lscolors::{LsColors, Style};
|
||||
use nu_engine::eval_expression;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
@ -32,6 +33,7 @@ impl Command for Ls {
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
let config = stack.get_config()?;
|
||||
let pattern = if let Some(expr) = call.positional.get(0) {
|
||||
let result = eval_expression(engine_state, stack, expr)?;
|
||||
let mut result = result.as_string()?;
|
||||
@ -51,6 +53,7 @@ impl Command for Ls {
|
||||
|
||||
let call_span = call.head;
|
||||
let glob = glob::glob(&pattern).unwrap();
|
||||
let ls_colors = LsColors::from_env().unwrap_or_default();
|
||||
|
||||
Ok(glob
|
||||
.into_iter()
|
||||
@ -60,13 +63,22 @@ impl Command for Ls {
|
||||
let is_file = metadata.is_file();
|
||||
let is_dir = metadata.is_dir();
|
||||
let filesize = metadata.len();
|
||||
|
||||
let mut cols = vec!["name".into(), "type".into(), "size".into()];
|
||||
let style = ls_colors.style_for_path(path.clone());
|
||||
let ansi_style = style.map(Style::to_crossterm_style).unwrap_or_default();
|
||||
let use_ls_colors = config.use_ls_colors;
|
||||
|
||||
let mut vals = vec![
|
||||
Value::String {
|
||||
val: path.to_string_lossy().to_string(),
|
||||
span: call_span,
|
||||
if use_ls_colors {
|
||||
Value::String {
|
||||
val: ansi_style.apply(path.to_string_lossy()).to_string(),
|
||||
span: call_span,
|
||||
}
|
||||
} else {
|
||||
Value::String {
|
||||
val: path.to_string_lossy().to_string(),
|
||||
span: call_span,
|
||||
}
|
||||
},
|
||||
if is_file {
|
||||
Value::string("file", call_span)
|
||||
@ -97,18 +109,31 @@ impl Command for Ls {
|
||||
span: call_span,
|
||||
}
|
||||
}
|
||||
Err(_) => Value::Record {
|
||||
cols: vec!["name".into(), "type".into(), "size".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: path.to_string_lossy().to_string(),
|
||||
span: call_span,
|
||||
},
|
||||
Value::Nothing { span: call_span },
|
||||
Value::Nothing { span: call_span },
|
||||
],
|
||||
span: call_span,
|
||||
},
|
||||
Err(_) => {
|
||||
let style = ls_colors.style_for_path(path.clone());
|
||||
let ansi_style = style.map(Style::to_crossterm_style).unwrap_or_default();
|
||||
let use_ls_colors = config.use_ls_colors;
|
||||
|
||||
Value::Record {
|
||||
cols: vec!["name".into(), "type".into(), "size".into()],
|
||||
vals: vec![
|
||||
if use_ls_colors {
|
||||
Value::String {
|
||||
val: ansi_style.apply(path.to_string_lossy()).to_string(),
|
||||
span: call_span,
|
||||
}
|
||||
} else {
|
||||
Value::String {
|
||||
val: path.to_string_lossy().to_string(),
|
||||
span: call_span,
|
||||
}
|
||||
},
|
||||
Value::Nothing { span: call_span },
|
||||
Value::Nothing { span: call_span },
|
||||
],
|
||||
span: call_span,
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => Value::Nothing { span: call_span },
|
||||
})
|
||||
|
@ -6,6 +6,7 @@ use crate::{ShellError, Value};
|
||||
pub struct Config {
|
||||
pub filesize_metric: bool,
|
||||
pub table_mode: String,
|
||||
pub use_ls_colors: bool,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
@ -13,6 +14,7 @@ impl Default for Config {
|
||||
Config {
|
||||
filesize_metric: false,
|
||||
table_mode: "rounded".into(),
|
||||
use_ls_colors: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -31,6 +33,9 @@ impl Value {
|
||||
"table_mode" => {
|
||||
config.table_mode = value.as_string()?;
|
||||
}
|
||||
"use_ls_colors" => {
|
||||
config.use_ls_colors = value.as_bool()?;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
nu-ansi-term = "0.39.0"
|
||||
|
||||
regex = "1.4"
|
||||
unicode-width = "0.1.8"
|
||||
strip-ansi-escapes = "0.1.1"
|
||||
ansi-cut = "0.1.1"
|
@ -611,15 +611,15 @@ impl Table {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ProcessedTable<'a> {
|
||||
pub headers: Vec<ProcessedCell<'a>>,
|
||||
pub data: Vec<Vec<ProcessedCell<'a>>>,
|
||||
pub struct ProcessedTable {
|
||||
pub headers: Vec<ProcessedCell>,
|
||||
pub data: Vec<Vec<ProcessedCell>>,
|
||||
pub theme: Theme,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ProcessedCell<'a> {
|
||||
pub contents: Vec<Vec<Subline<'a>>>,
|
||||
pub struct ProcessedCell {
|
||||
pub contents: Vec<Vec<Subline>>,
|
||||
pub style: TextStyle,
|
||||
}
|
||||
|
||||
@ -995,7 +995,7 @@ pub fn maybe_truncate_columns(termwidth: usize, processed_table: &mut ProcessedT
|
||||
|
||||
processed_table.headers.push(ProcessedCell {
|
||||
contents: vec![vec![Subline {
|
||||
subline: "...",
|
||||
subline: "...".to_string(),
|
||||
width: 3,
|
||||
}]],
|
||||
style: TextStyle::basic_center(),
|
||||
@ -1004,7 +1004,7 @@ pub fn maybe_truncate_columns(termwidth: usize, processed_table: &mut ProcessedT
|
||||
for entry in processed_table.data.iter_mut() {
|
||||
entry.push(ProcessedCell {
|
||||
contents: vec![vec![Subline {
|
||||
subline: "...",
|
||||
subline: "...".to_string(),
|
||||
width: 3,
|
||||
}]],
|
||||
style: TextStyle::basic_center(),
|
||||
|
@ -1,8 +1,9 @@
|
||||
use crate::table::TextStyle;
|
||||
use ansi_cut::AnsiCut;
|
||||
use nu_ansi_term::Style;
|
||||
use std::collections::HashMap;
|
||||
use std::{fmt::Display, iter::Iterator};
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Alignment {
|
||||
@ -12,14 +13,14 @@ pub enum Alignment {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Subline<'a> {
|
||||
pub subline: &'a str,
|
||||
pub struct Subline {
|
||||
pub subline: String,
|
||||
pub width: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Line<'a> {
|
||||
pub sublines: Vec<Subline<'a>>,
|
||||
pub struct Line {
|
||||
pub sublines: Vec<Subline>,
|
||||
pub width: usize,
|
||||
}
|
||||
|
||||
@ -37,7 +38,7 @@ pub struct WrappedCell {
|
||||
pub style: TextStyle,
|
||||
}
|
||||
|
||||
impl<'a> Display for Line<'a> {
|
||||
impl Display for Line {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let mut first = true;
|
||||
for subline in &self.sublines {
|
||||
@ -52,21 +53,42 @@ impl<'a> Display for Line<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn strip_ansi(astring: &str) -> String {
|
||||
if let Ok(bytes) = strip_ansi_escapes::strip(astring) {
|
||||
String::from_utf8_lossy(&bytes).to_string()
|
||||
} else {
|
||||
astring.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn unicode_width_strip_ansi(astring: &str) -> usize {
|
||||
let stripped_string: String = {
|
||||
if let Ok(bytes) = strip_ansi_escapes::strip(astring) {
|
||||
String::from_utf8_lossy(&bytes).to_string()
|
||||
} else {
|
||||
astring.to_string()
|
||||
}
|
||||
};
|
||||
|
||||
UnicodeWidthStr::width(&stripped_string[..])
|
||||
}
|
||||
|
||||
pub fn split_sublines(input: &str) -> Vec<Vec<Subline>> {
|
||||
input
|
||||
.split_terminator('\n')
|
||||
.map(|line| {
|
||||
line.split_terminator(' ')
|
||||
.map(|x| Subline {
|
||||
subline: x,
|
||||
subline: x.to_string(),
|
||||
width: {
|
||||
// We've tried UnicodeWidthStr::width(x), UnicodeSegmentation::graphemes(x, true).count()
|
||||
// and x.chars().count() with all types of combinations. Currently, it appears that
|
||||
// getting the max of char count and Unicode width seems to produce the best layout.
|
||||
// However, it's not perfect.
|
||||
let c = x.chars().count();
|
||||
let u = UnicodeWidthStr::width(x);
|
||||
std::cmp::max(c, u)
|
||||
// let c = x.chars().count();
|
||||
// let u = UnicodeWidthStr::width(x);
|
||||
// std::cmp::min(c, u)
|
||||
unicode_width_strip_ansi(x)
|
||||
},
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
@ -101,19 +123,18 @@ pub fn column_width(input: &[Vec<Subline>]) -> usize {
|
||||
}
|
||||
|
||||
fn split_word(cell_width: usize, word: &str) -> Vec<Subline> {
|
||||
use unicode_width::UnicodeWidthChar;
|
||||
|
||||
let mut output = vec![];
|
||||
let mut current_width = 0;
|
||||
let mut start_index = 0;
|
||||
let mut end_index;
|
||||
|
||||
for c in word.char_indices() {
|
||||
let word_no_ansi = strip_ansi(word);
|
||||
for c in word_no_ansi.char_indices() {
|
||||
if let Some(width) = c.1.width() {
|
||||
end_index = c.0;
|
||||
if current_width + width > cell_width {
|
||||
output.push(Subline {
|
||||
subline: &word[start_index..end_index],
|
||||
subline: word.cut(start_index..end_index),
|
||||
width: current_width,
|
||||
});
|
||||
|
||||
@ -127,7 +148,7 @@ fn split_word(cell_width: usize, word: &str) -> Vec<Subline> {
|
||||
|
||||
if start_index != word.len() {
|
||||
output.push(Subline {
|
||||
subline: &word[start_index..],
|
||||
subline: word.cut(start_index..),
|
||||
width: current_width,
|
||||
});
|
||||
}
|
||||
@ -135,9 +156,9 @@ fn split_word(cell_width: usize, word: &str) -> Vec<Subline> {
|
||||
output
|
||||
}
|
||||
|
||||
pub fn wrap<'a>(
|
||||
pub fn wrap(
|
||||
cell_width: usize,
|
||||
mut input: impl Iterator<Item = Subline<'a>>,
|
||||
mut input: impl Iterator<Item = Subline>,
|
||||
color_hm: &HashMap<String, Style>,
|
||||
re_leading: ®ex::Regex,
|
||||
re_trailing: ®ex::Regex,
|
||||
@ -165,7 +186,7 @@ pub fn wrap<'a>(
|
||||
// If this is a really long single word, we need to split the word
|
||||
if current_line.len() == 1 && current_width > cell_width {
|
||||
max_width = cell_width;
|
||||
let sublines = split_word(cell_width, current_line[0].subline);
|
||||
let sublines = split_word(cell_width, ¤t_line[0].subline);
|
||||
for subline in sublines {
|
||||
let width = subline.width;
|
||||
lines.push(Line {
|
||||
@ -200,7 +221,7 @@ pub fn wrap<'a>(
|
||||
None => {
|
||||
if current_width > cell_width {
|
||||
// We need to break up the last word
|
||||
let sublines = split_word(cell_width, current_line[0].subline);
|
||||
let sublines = split_word(cell_width, ¤t_line[0].subline);
|
||||
for subline in sublines {
|
||||
let width = subline.width;
|
||||
lines.push(Line {
|
||||
@ -235,7 +256,7 @@ pub fn wrap<'a>(
|
||||
first = false;
|
||||
current_line_width = subline.width;
|
||||
}
|
||||
current_line.push_str(subline.subline);
|
||||
current_line.push_str(&subline.subline);
|
||||
}
|
||||
|
||||
if current_line_width > current_max {
|
||||
|
Reference in New Issue
Block a user