mirror of
https://github.com/nushell/nushell
synced 2024-12-26 04:53:09 +00:00
Add a very silly table
This commit is contained in:
parent
0694245ccd
commit
26d50ebcd5
13 changed files with 1840 additions and 0 deletions
37
Cargo.lock
generated
37
Cargo.lock
generated
|
@ -2,6 +2,15 @@
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aho-corasick"
|
||||||
|
version = "0.7.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ansi_term"
|
name = "ansi_term"
|
||||||
version = "0.12.1"
|
version = "0.12.1"
|
||||||
|
@ -148,6 +157,7 @@ dependencies = [
|
||||||
"nu-engine",
|
"nu-engine",
|
||||||
"nu-parser",
|
"nu-parser",
|
||||||
"nu-protocol",
|
"nu-protocol",
|
||||||
|
"nu-table",
|
||||||
"pretty_assertions",
|
"pretty_assertions",
|
||||||
"reedline",
|
"reedline",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
|
@ -285,6 +295,7 @@ dependencies = [
|
||||||
"glob",
|
"glob",
|
||||||
"nu-engine",
|
"nu-engine",
|
||||||
"nu-protocol",
|
"nu-protocol",
|
||||||
|
"nu-table",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -310,6 +321,15 @@ dependencies = [
|
||||||
"codespan-reporting",
|
"codespan-reporting",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nu-table"
|
||||||
|
version = "0.36.0"
|
||||||
|
dependencies = [
|
||||||
|
"nu-ansi-term",
|
||||||
|
"regex",
|
||||||
|
"unicode-width",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-integer"
|
name = "num-integer"
|
||||||
version = "0.1.44"
|
version = "0.1.44"
|
||||||
|
@ -494,12 +514,29 @@ dependencies = [
|
||||||
"unicode-width",
|
"unicode-width",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
version = "1.5.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-automata"
|
name = "regex-automata"
|
||||||
version = "0.1.10"
|
version = "0.1.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
|
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.6.25"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "remove_dir_all"
|
name = "remove_dir_all"
|
||||||
version = "0.5.3"
|
version = "0.5.3"
|
||||||
|
|
|
@ -16,6 +16,7 @@ nu-command = { path="./crates/nu-command" }
|
||||||
nu-engine = { path="./crates/nu-engine" }
|
nu-engine = { path="./crates/nu-engine" }
|
||||||
nu-parser = { path="./crates/nu-parser" }
|
nu-parser = { path="./crates/nu-parser" }
|
||||||
nu-protocol = { path = "./crates/nu-protocol" }
|
nu-protocol = { path = "./crates/nu-protocol" }
|
||||||
|
nu-table = { path = "./crates/nu-table" }
|
||||||
|
|
||||||
# mimalloc = { version = "*", default-features = false }
|
# mimalloc = { version = "*", default-features = false }
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ edition = "2018"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-protocol = { path = "../nu-protocol" }
|
nu-protocol = { path = "../nu-protocol" }
|
||||||
nu-engine = { path = "../nu-engine" }
|
nu-engine = { path = "../nu-engine" }
|
||||||
|
nu-table = { path = "../nu-table" }
|
||||||
|
|
||||||
# Potential dependencies for extras
|
# Potential dependencies for extras
|
||||||
glob = "0.3.0"
|
glob = "0.3.0"
|
|
@ -7,6 +7,7 @@ use nu_protocol::{
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
where_::Where, Alias, Benchmark, BuildString, Def, Do, Each, For, If, Length, Let, LetEnv, Ls,
|
where_::Where, Alias, Benchmark, BuildString, Def, Do, Each, For, If, Length, Let, LetEnv, Ls,
|
||||||
|
Table,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn create_default_context() -> Rc<RefCell<EngineState>> {
|
pub fn create_default_context() -> Rc<RefCell<EngineState>> {
|
||||||
|
@ -45,6 +46,8 @@ pub fn create_default_context() -> Rc<RefCell<EngineState>> {
|
||||||
|
|
||||||
working_set.add_decl(Box::new(Ls));
|
working_set.add_decl(Box::new(Ls));
|
||||||
|
|
||||||
|
working_set.add_decl(Box::new(Table));
|
||||||
|
|
||||||
let sig = Signature::build("exit");
|
let sig = Signature::build("exit");
|
||||||
working_set.add_decl(sig.predeclare());
|
working_set.add_decl(sig.predeclare());
|
||||||
let sig = Signature::build("vars");
|
let sig = Signature::build("vars");
|
||||||
|
|
|
@ -11,6 +11,7 @@ mod length;
|
||||||
mod let_;
|
mod let_;
|
||||||
mod let_env;
|
mod let_env;
|
||||||
mod ls;
|
mod ls;
|
||||||
|
mod table;
|
||||||
mod where_;
|
mod where_;
|
||||||
|
|
||||||
pub use alias::Alias;
|
pub use alias::Alias;
|
||||||
|
@ -26,3 +27,4 @@ pub use length::Length;
|
||||||
pub use let_::Let;
|
pub use let_::Let;
|
||||||
pub use let_env::LetEnv;
|
pub use let_env::LetEnv;
|
||||||
pub use ls::Ls;
|
pub use ls::Ls;
|
||||||
|
pub use table::Table;
|
||||||
|
|
125
crates/nu-command/src/table.rs
Normal file
125
crates/nu-command/src/table.rs
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use nu_protocol::ast::{Call, PathMember};
|
||||||
|
use nu_protocol::engine::{Command, EvaluationContext};
|
||||||
|
use nu_protocol::{Signature, Span, Value};
|
||||||
|
use nu_table::StyledString;
|
||||||
|
|
||||||
|
pub struct Table;
|
||||||
|
|
||||||
|
//NOTE: this is not a real implementation :D. It's just a simple one to test with until we port the real one.
|
||||||
|
impl Command for Table {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"table"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Render the table."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
|
Signature::build("table")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
_context: &EvaluationContext,
|
||||||
|
call: &Call,
|
||||||
|
input: Value,
|
||||||
|
) -> Result<nu_protocol::Value, nu_protocol::ShellError> {
|
||||||
|
match input {
|
||||||
|
Value::List { vals, .. } => {
|
||||||
|
let table = convert_to_table(vals);
|
||||||
|
|
||||||
|
if let Some(table) = table {
|
||||||
|
let result = nu_table::draw_table(&table, 80, &HashMap::new());
|
||||||
|
|
||||||
|
Ok(Value::String {
|
||||||
|
val: result,
|
||||||
|
span: call.head,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Ok(Value::Nothing { span: call.head })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Value::Stream { stream, .. } => {
|
||||||
|
let table = convert_to_table(stream);
|
||||||
|
|
||||||
|
if let Some(table) = table {
|
||||||
|
let result = nu_table::draw_table(&table, 80, &HashMap::new());
|
||||||
|
|
||||||
|
Ok(Value::String {
|
||||||
|
val: result,
|
||||||
|
span: call.head,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Ok(Value::Nothing { span: call.head })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
x => Ok(x),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_to_table(iter: impl IntoIterator<Item = Value>) -> Option<nu_table::Table> {
|
||||||
|
let mut iter = iter.into_iter().peekable();
|
||||||
|
|
||||||
|
if let Some(first) = iter.peek() {
|
||||||
|
let mut headers = first.columns();
|
||||||
|
headers.insert(0, "#".into());
|
||||||
|
|
||||||
|
let mut data = vec![];
|
||||||
|
|
||||||
|
for (row_num, item) in iter.enumerate() {
|
||||||
|
let mut row = vec![row_num.to_string()];
|
||||||
|
|
||||||
|
for header in headers.iter().skip(1) {
|
||||||
|
let result = item.clone().follow_cell_path(&[PathMember::String {
|
||||||
|
val: header.into(),
|
||||||
|
span: Span::unknown(),
|
||||||
|
}]);
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(value) => row.push(value.into_string()),
|
||||||
|
Err(_) => row.push(String::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data.push(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(nu_table::Table {
|
||||||
|
headers: headers
|
||||||
|
.into_iter()
|
||||||
|
.map(|x| StyledString {
|
||||||
|
contents: x,
|
||||||
|
style: nu_table::TextStyle::default_header(),
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
data: data
|
||||||
|
.into_iter()
|
||||||
|
.map(|x| {
|
||||||
|
x.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(col, y)| {
|
||||||
|
if col == 0 {
|
||||||
|
StyledString {
|
||||||
|
contents: y,
|
||||||
|
style: nu_table::TextStyle::default_header(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
StyledString {
|
||||||
|
contents: y,
|
||||||
|
style: nu_table::TextStyle::basic_left(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<StyledString>>()
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
theme: nu_table::Theme::rounded(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
|
@ -288,6 +288,13 @@ impl Value {
|
||||||
pub fn is_true(&self) -> bool {
|
pub fn is_true(&self) -> bool {
|
||||||
matches!(self, Value::Bool { val: true, .. })
|
matches!(self, Value::Bool { val: true, .. })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn columns(&self) -> Vec<String> {
|
||||||
|
match self {
|
||||||
|
Value::Record { cols, .. } => cols.clone(),
|
||||||
|
_ => vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for Value {
|
impl PartialEq for Value {
|
||||||
|
|
22
crates/nu-table/.gitignore
vendored
Normal file
22
crates/nu-table/.gitignore
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
/target
|
||||||
|
/scratch
|
||||||
|
**/*.rs.bk
|
||||||
|
history.txt
|
||||||
|
tests/fixtures/nuplayground
|
||||||
|
crates/*/target
|
||||||
|
|
||||||
|
# Debian/Ubuntu
|
||||||
|
debian/.debhelper/
|
||||||
|
debian/debhelper-build-stamp
|
||||||
|
debian/files
|
||||||
|
debian/nu.substvars
|
||||||
|
debian/nu/
|
||||||
|
|
||||||
|
# macOS junk
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# JetBrains' IDE items
|
||||||
|
.idea/*
|
||||||
|
|
||||||
|
# VSCode's IDE items
|
||||||
|
.vscode/*
|
18
crates/nu-table/Cargo.toml
Normal file
18
crates/nu-table/Cargo.toml
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
[package]
|
||||||
|
authors = ["The Nu Project Contributors"]
|
||||||
|
description = "Nushell table printing"
|
||||||
|
edition = "2018"
|
||||||
|
license = "MIT"
|
||||||
|
name = "nu-table"
|
||||||
|
version = "0.36.0"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
[[bin]]
|
||||||
|
name = "table"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
nu-ansi-term = "0.36.0"
|
||||||
|
|
||||||
|
regex = "1.4"
|
||||||
|
unicode-width = "0.1.8"
|
5
crates/nu-table/src/lib.rs
Normal file
5
crates/nu-table/src/lib.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
mod table;
|
||||||
|
mod wrap;
|
||||||
|
|
||||||
|
pub use table::{draw_table, StyledString, Table, TextStyle, Theme};
|
||||||
|
pub use wrap::Alignment;
|
86
crates/nu-table/src/main.rs
Normal file
86
crates/nu-table/src/main.rs
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
use nu_table::{draw_table, StyledString, Table, TextStyle, Theme};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let args: Vec<_> = std::env::args().collect();
|
||||||
|
let mut width = 0;
|
||||||
|
|
||||||
|
if args.len() > 1 {
|
||||||
|
// Width in terminal characters
|
||||||
|
width = args[1].parse::<usize>().expect("Need a width in columns");
|
||||||
|
}
|
||||||
|
|
||||||
|
if width < 4 {
|
||||||
|
println!("Width must be greater than or equal to 4, setting width to 80");
|
||||||
|
width = 80;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The mocked up table data
|
||||||
|
let (table_headers, row_data) = make_table_data();
|
||||||
|
// The table headers
|
||||||
|
let headers = vec_of_str_to_vec_of_styledstr(&table_headers, true);
|
||||||
|
// The table rows
|
||||||
|
let rows = vec_of_str_to_vec_of_styledstr(&row_data, false);
|
||||||
|
// The table itself
|
||||||
|
let table = Table::new(headers, vec![rows; 3], Theme::rounded());
|
||||||
|
// FIXME: Config isn't available from here so just put these here to compile
|
||||||
|
let color_hm: HashMap<String, nu_ansi_term::Style> = HashMap::new();
|
||||||
|
// Capture the table as a string
|
||||||
|
let output_table = draw_table(&table, width, &color_hm);
|
||||||
|
// Draw the table
|
||||||
|
println!("{}", output_table)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_table_data() -> (Vec<&'static str>, Vec<&'static str>) {
|
||||||
|
let table_headers = vec![
|
||||||
|
"category",
|
||||||
|
"description",
|
||||||
|
"emoji",
|
||||||
|
"ios_version",
|
||||||
|
"unicode_version",
|
||||||
|
"aliases",
|
||||||
|
"tags",
|
||||||
|
"category2",
|
||||||
|
"description2",
|
||||||
|
"emoji2",
|
||||||
|
"ios_version2",
|
||||||
|
"unicode_version2",
|
||||||
|
"aliases2",
|
||||||
|
"tags2",
|
||||||
|
];
|
||||||
|
|
||||||
|
let row_data = vec![
|
||||||
|
"Smileys & Emotion",
|
||||||
|
"grinning face",
|
||||||
|
"😀",
|
||||||
|
"6",
|
||||||
|
"6.1",
|
||||||
|
"grinning",
|
||||||
|
"smile",
|
||||||
|
"Smileys & Emotion",
|
||||||
|
"grinning face",
|
||||||
|
"😀",
|
||||||
|
"6",
|
||||||
|
"6.1",
|
||||||
|
"grinning",
|
||||||
|
"smile",
|
||||||
|
];
|
||||||
|
|
||||||
|
(table_headers, row_data)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vec_of_str_to_vec_of_styledstr(data: &[&str], is_header: bool) -> Vec<StyledString> {
|
||||||
|
let mut v = vec![];
|
||||||
|
|
||||||
|
for x in data {
|
||||||
|
if is_header {
|
||||||
|
v.push(StyledString::new(
|
||||||
|
String::from(*x),
|
||||||
|
TextStyle::default_header(),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
v.push(StyledString::new(String::from(*x), TextStyle::basic_left()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v
|
||||||
|
}
|
1259
crates/nu-table/src/table.rs
Normal file
1259
crates/nu-table/src/table.rs
Normal file
File diff suppressed because it is too large
Load diff
274
crates/nu-table/src/wrap.rs
Normal file
274
crates/nu-table/src/wrap.rs
Normal file
|
@ -0,0 +1,274 @@
|
||||||
|
use crate::table::TextStyle;
|
||||||
|
use nu_ansi_term::Style;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::{fmt::Display, iter::Iterator};
|
||||||
|
use unicode_width::UnicodeWidthStr;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum Alignment {
|
||||||
|
Left,
|
||||||
|
Center,
|
||||||
|
Right,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Subline<'a> {
|
||||||
|
pub subline: &'a str,
|
||||||
|
pub width: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Line<'a> {
|
||||||
|
pub sublines: Vec<Subline<'a>>,
|
||||||
|
pub width: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct WrappedLine {
|
||||||
|
pub line: String,
|
||||||
|
pub width: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct WrappedCell {
|
||||||
|
pub lines: Vec<WrappedLine>,
|
||||||
|
pub max_width: usize,
|
||||||
|
|
||||||
|
pub style: TextStyle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Display for Line<'a> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let mut first = true;
|
||||||
|
for subline in &self.sublines {
|
||||||
|
if !first {
|
||||||
|
write!(f, " ")?;
|
||||||
|
} else {
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
write!(f, "{}", subline.subline)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn split_sublines(input: &str) -> Vec<Vec<Subline>> {
|
||||||
|
input
|
||||||
|
.split_terminator('\n')
|
||||||
|
.map(|line| {
|
||||||
|
line.split_terminator(' ')
|
||||||
|
.map(|x| Subline {
|
||||||
|
subline: x,
|
||||||
|
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)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn column_width(input: &[Vec<Subline>]) -> usize {
|
||||||
|
let mut max = 0;
|
||||||
|
|
||||||
|
for line in input {
|
||||||
|
let mut total = 0;
|
||||||
|
|
||||||
|
let mut first = true;
|
||||||
|
for inp in line {
|
||||||
|
if !first {
|
||||||
|
// Account for the space
|
||||||
|
total += 1;
|
||||||
|
} else {
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
total += inp.width;
|
||||||
|
}
|
||||||
|
|
||||||
|
if total > max {
|
||||||
|
max = total;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
max
|
||||||
|
}
|
||||||
|
|
||||||
|
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() {
|
||||||
|
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],
|
||||||
|
width: current_width,
|
||||||
|
});
|
||||||
|
|
||||||
|
start_index = c.0;
|
||||||
|
current_width = width;
|
||||||
|
} else {
|
||||||
|
current_width += width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if start_index != word.len() {
|
||||||
|
output.push(Subline {
|
||||||
|
subline: &word[start_index..],
|
||||||
|
width: current_width,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wrap<'a>(
|
||||||
|
cell_width: usize,
|
||||||
|
mut input: impl Iterator<Item = Subline<'a>>,
|
||||||
|
color_hm: &HashMap<String, Style>,
|
||||||
|
re_leading: ®ex::Regex,
|
||||||
|
re_trailing: ®ex::Regex,
|
||||||
|
) -> (Vec<WrappedLine>, usize) {
|
||||||
|
let mut lines = vec![];
|
||||||
|
let mut current_line: Vec<Subline> = vec![];
|
||||||
|
let mut current_width = 0;
|
||||||
|
let mut first = true;
|
||||||
|
let mut max_width = 0;
|
||||||
|
let lead_trail_space_bg_color = color_hm
|
||||||
|
.get("leading_trailing_space_bg")
|
||||||
|
.unwrap_or(&Style::default())
|
||||||
|
.to_owned();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match input.next() {
|
||||||
|
Some(item) => {
|
||||||
|
if !first {
|
||||||
|
current_width += 1;
|
||||||
|
} else {
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if item.width + current_width > cell_width {
|
||||||
|
// 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);
|
||||||
|
for subline in sublines {
|
||||||
|
let width = subline.width;
|
||||||
|
lines.push(Line {
|
||||||
|
sublines: vec![subline],
|
||||||
|
width,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
first = true;
|
||||||
|
|
||||||
|
current_width = item.width;
|
||||||
|
current_line = vec![item];
|
||||||
|
} else {
|
||||||
|
if !current_line.is_empty() {
|
||||||
|
lines.push(Line {
|
||||||
|
sublines: current_line,
|
||||||
|
width: current_width,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
first = true;
|
||||||
|
|
||||||
|
current_width = item.width;
|
||||||
|
current_line = vec![item];
|
||||||
|
max_width = std::cmp::max(max_width, current_width);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
current_width += item.width;
|
||||||
|
current_line.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
if current_width > cell_width {
|
||||||
|
// We need to break up the last word
|
||||||
|
let sublines = split_word(cell_width, current_line[0].subline);
|
||||||
|
for subline in sublines {
|
||||||
|
let width = subline.width;
|
||||||
|
lines.push(Line {
|
||||||
|
sublines: vec![subline],
|
||||||
|
width,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if current_width > 0 {
|
||||||
|
lines.push(Line {
|
||||||
|
sublines: current_line,
|
||||||
|
width: current_width,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut current_max = 0;
|
||||||
|
let mut output = vec![];
|
||||||
|
|
||||||
|
for line in lines {
|
||||||
|
let mut current_line_width = 0;
|
||||||
|
let mut first = true;
|
||||||
|
let mut current_line = String::new();
|
||||||
|
|
||||||
|
for subline in line.sublines {
|
||||||
|
if !first {
|
||||||
|
current_line_width += 1 + subline.width;
|
||||||
|
current_line.push(' ');
|
||||||
|
} else {
|
||||||
|
first = false;
|
||||||
|
current_line_width = subline.width;
|
||||||
|
}
|
||||||
|
current_line.push_str(subline.subline);
|
||||||
|
}
|
||||||
|
|
||||||
|
if current_line_width > current_max {
|
||||||
|
current_max = current_line_width;
|
||||||
|
}
|
||||||
|
|
||||||
|
// highlight leading and trailing spaces so they stand out.
|
||||||
|
let mut bg_color_string = Style::default().prefix().to_string();
|
||||||
|
// right now config settings can only set foreground colors so, in this
|
||||||
|
// instance we take the foreground color and make it a background color
|
||||||
|
if let Some(bg) = lead_trail_space_bg_color.foreground {
|
||||||
|
bg_color_string = Style::default().on(bg).prefix().to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(leading_match) = re_leading.find(¤t_line.clone()) {
|
||||||
|
String::insert_str(
|
||||||
|
&mut current_line,
|
||||||
|
leading_match.end(),
|
||||||
|
nu_ansi_term::ansi::RESET,
|
||||||
|
);
|
||||||
|
String::insert_str(&mut current_line, leading_match.start(), &bg_color_string);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(trailing_match) = re_trailing.find(¤t_line.clone()) {
|
||||||
|
String::insert_str(&mut current_line, trailing_match.start(), &bg_color_string);
|
||||||
|
current_line += nu_ansi_term::ansi::RESET;
|
||||||
|
}
|
||||||
|
|
||||||
|
output.push(WrappedLine {
|
||||||
|
line: current_line,
|
||||||
|
width: current_line_width,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
(output, current_max)
|
||||||
|
}
|
Loading…
Reference in a new issue