diff --git a/Cargo.lock b/Cargo.lock index 6ff3d45f91..502f84e313 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -102,7 +102,16 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cf4578926a981ab0ca955dc023541d19de37112bc24c1a197bd806d3d86ad1d" dependencies = [ - "ansitok", + "ansitok 0.2.0", +] + +[[package]] +name = "ansi-str" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "060de1453b69f46304b28274f382132f4e72c55637cf362920926a70d090890d" +dependencies = [ + "ansitok 0.3.0", ] [[package]] @@ -115,6 +124,16 @@ dependencies = [ "vte 0.10.1", ] +[[package]] +name = "ansitok" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0a8acea8c2f1c60f0a92a8cd26bf96ca97db56f10bbcab238bbe0cceba659ee" +dependencies = [ + "nom 7.1.3", + "vte 0.14.1", +] + [[package]] name = "anstream" version = "0.6.18" @@ -3838,7 +3857,7 @@ dependencies = [ name = "nu-explore" version = "0.104.2" dependencies = [ - "ansi-str", + "ansi-str 0.8.0", "anyhow", "crossterm", "log", @@ -4663,12 +4682,12 @@ checksum = "fb37767f6569cd834a413442455e0f066d0d522de8630436e2a1761d9726ba56" [[package]] name = "papergrid" -version = "0.13.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b0f8def1f117e13c895f3eda65a7b5650688da29d6ad04635f61bc7b92eebd" +checksum = "6978128c8b51d8f4080631ceb2302ab51e32cc6e8615f735ee2f83fd269ae3f1" dependencies = [ - "ansi-str", - "ansitok", + "ansi-str 0.9.0", + "ansitok 0.3.0", "bytecount", "fnv", "unicode-width 0.2.0", @@ -7126,13 +7145,14 @@ dependencies = [ [[package]] name = "tabled" -version = "0.17.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6709222f3973137427ce50559cd564dc187a95b9cfe01613d2f4e93610e510a" +checksum = "e39a2ee1fbcd360805a771e1b300f78cc88fec7b8d3e2f71cd37bbf23e725c7d" dependencies = [ - "ansi-str", - "ansitok", + "ansi-str 0.9.0", + "ansitok 0.3.0", "papergrid", + "testing_table", ] [[package]] @@ -7205,6 +7225,16 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" +[[package]] +name = "testing_table" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f8daae29995a24f65619e19d8d31dea5b389f3d853d8bf297bbf607cd0014cc" +dependencies = [ + "ansitok 0.3.0", + "unicode-width 0.2.0", +] + [[package]] name = "textwrap" version = "0.16.1" @@ -7973,6 +8003,16 @@ dependencies = [ "vte_generate_state_changes", ] +[[package]] +name = "vte" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "231fdcd7ef3037e8330d8e17e61011a2c244126acc0a982f4040ac3f9f0bc077" +dependencies = [ + "arrayvec 0.7.6", + "memchr", +] + [[package]] name = "vte_generate_state_changes" version = "0.1.2" diff --git a/Cargo.toml b/Cargo.toml index 086eeeb3d1..a328b30ddb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -161,7 +161,7 @@ strum = "0.26" strum_macros = "0.26" syn = "2.0" sysinfo = "0.33" -tabled = { version = "0.17.0", default-features = false } +tabled = { version = "0.20", default-features = false } tempfile = "3.20" titlecase = "3.5" toml = "0.8" diff --git a/crates/nu-command/src/debug/inspect_table.rs b/crates/nu-command/src/debug/inspect_table.rs index 04eaa240ad..6da5ad7729 100644 --- a/crates/nu-command/src/debug/inspect_table.rs +++ b/crates/nu-command/src/debug/inspect_table.rs @@ -1,14 +1,17 @@ // note: Seems like could be simplified // IMHO: it shall not take 300+ lines :) +// TODO: Simplify +// NOTE: Pool table could be used? +// FIXME: `inspect` wrapping produces too much new lines with small terminal -use self::{global_horizontal_char::SetHorizontalChar, set_widths::SetWidths}; +use self::global_horizontal_char::SetHorizontalChar; use nu_protocol::Value; use nu_protocol::engine::EngineState; use nu_table::{string_width, string_wrap}; use tabled::{ Table, grid::config::ColoredConfig, - settings::{Settings, Style, peaker::Priority, width::Wrap}, + settings::{Style, peaker::Priority, width::Wrap}, }; pub fn build_table( @@ -60,13 +63,12 @@ pub fn build_table( desc_table.with(Style::rounded().remove_bottom().remove_horizontals()); let mut val_table = Table::from_iter(data); - val_table.with( - Settings::default() - .with(Style::rounded().corner_top_left('├').corner_top_right('┤')) - .with(SetWidths(widths)) - .with(Wrap::new(width).priority(Priority::max(true))) - .with(SetHorizontalChar::new('┼', '┴', 11 + 2 + 1)), - ); + val_table.get_dimension_mut().set_widths(widths); + val_table.with(Style::rounded().corner_top_left('├').corner_top_right('┤')); + val_table.with(( + Wrap::new(width).priority(Priority::max(true)), + SetHorizontalChar::new('┼', '┴', 11 + 2 + 1), + )); format!("{desc_table}\n{val_table}") } @@ -315,10 +317,11 @@ mod util { } mod global_horizontal_char { + use nu_table::NuRecords; use tabled::{ grid::{ - config::{ColoredConfig, Offset}, - dimension::{CompleteDimensionVecRecords, Dimension}, + config::{ColoredConfig, Offset, Position}, + dimension::{CompleteDimension, Dimension}, records::{ExactRecords, Records}, }, settings::TableOption, @@ -340,14 +343,12 @@ mod global_horizontal_char { } } - impl TableOption> - for SetHorizontalChar - { + impl TableOption for SetHorizontalChar { fn change( self, - records: &mut R, + records: &mut NuRecords, cfg: &mut ColoredConfig, - dimension: &mut CompleteDimensionVecRecords<'_>, + dimension: &mut CompleteDimension, ) { let count_columns = records.count_columns(); let count_rows = records.count_rows(); @@ -360,9 +361,9 @@ mod global_horizontal_char { let has_vertical = cfg.has_vertical(0, count_columns); if has_vertical && self.index == 0 { - let mut border = cfg.get_border((0, 0), (count_rows, count_columns)); + let mut border = cfg.get_border(Position::new(0, 0), (count_rows, count_columns)); border.left_top_corner = Some(self.intersection); - cfg.set_border((0, 0), border); + cfg.set_border(Position::new(0, 0), border); return; } @@ -370,7 +371,7 @@ mod global_horizontal_char { for (col, width) in widths.into_iter().enumerate() { if self.index < i + width { let o = self.index - i; - cfg.set_horizontal_char((0, col), self.split, Offset::Begin(o)); + cfg.set_horizontal_char(Position::new(0, col), Offset::Start(o), self.split); return; } @@ -379,9 +380,10 @@ mod global_horizontal_char { let has_vertical = cfg.has_vertical(col, count_columns); if has_vertical { if self.index == i { - let mut border = cfg.get_border((0, col), (count_rows, count_columns)); + let mut border = + cfg.get_border(Position::new(0, col), (count_rows, count_columns)); border.right_top_corner = Some(self.intersection); - cfg.set_border((0, col), border); + cfg.set_border(Position::new(0, col), border); return; } @@ -391,7 +393,7 @@ mod global_horizontal_char { } } - fn get_widths(dims: &CompleteDimensionVecRecords<'_>, count_columns: usize) -> Vec { + fn get_widths(dims: &CompleteDimension, count_columns: usize) -> Vec { let mut widths = vec![0; count_columns]; for (col, width) in widths.iter_mut().enumerate() { *width = dims.get_width(col); @@ -400,23 +402,3 @@ mod global_horizontal_char { widths } } - -mod set_widths { - use tabled::{ - grid::{config::ColoredConfig, dimension::CompleteDimensionVecRecords}, - settings::TableOption, - }; - - pub struct SetWidths(pub Vec); - - impl TableOption> for SetWidths { - fn change( - self, - _: &mut R, - _: &mut ColoredConfig, - dims: &mut CompleteDimensionVecRecords<'_>, - ) { - dims.set_widths(self.0); - } - } -} diff --git a/crates/nu-command/tests/commands/table.rs b/crates/nu-command/tests/commands/table.rs index 5426ec5a0a..644dade193 100644 --- a/crates/nu-command/tests/commands/table.rs +++ b/crates/nu-command/tests/commands/table.rs @@ -671,10 +671,10 @@ fn test_expand_big_0() { let expected = join_lines([ "╭──────────────────┬───────────────────────────────────────────────────────────╮", "│ │ ╭───────────────┬───────────────────────────────────────╮ │", - "│ package │ │ │ ╭───┬───────────────────────────────╮ │ │", - "│ │ │ authors │ │ 0 │ The Nushell Project │ │ │", - "│ │ │ │ │ │ Developers │ │ │", - "│ │ │ │ ╰───┴───────────────────────────────╯ │ │", + "│ package │ │ │ ╭───┬──────────────────────╮ │ │", + "│ │ │ authors │ │ 0 │ The Nushell Project │ │ │", + "│ │ │ │ │ │ Developers │ │ │", + "│ │ │ │ ╰───┴──────────────────────╯ │ │", "│ │ │ default-run │ nu │ │", "│ │ │ description │ A new type of shell │ │", "│ │ │ documentation │ https://www.nushell.sh/book/ │ │", @@ -852,6 +852,8 @@ fn test_expand_big_0() { "╰──────────────────┴───────────────────────────────────────────────────────────╯", ]); + _print_lines(&actual.out, 80); + assert_eq!(actual.out, expected); let actual = nu!( @@ -1045,6 +1047,8 @@ fn test_expand_big_0() { "╰──────────────────┴───────────────────────────────────────────────────────────────────────────────────────────────────╯", ]); + _print_lines(&actual.out, 120); + assert_eq!(actual.out, expected); let actual = nu!( @@ -1188,19 +1192,19 @@ fn test_expand_big_0() { "│ │ │ time │ 0.3.12 │ │", "│ │ ╰───────────────┴───────────────────╯ │", "│ target │ {record 3 fields} │", - "│ │ ╭───────────────────┬───────────────╮ │", - "│ dev-dependencies │ │ nu-test-support │ {record 2 │ │", - "│ │ │ │ fields} │ │", - "│ │ │ tempfile │ 3.2.0 │ │", - "│ │ │ assert_cmd │ 2.0.2 │ │", - "│ │ │ criterion │ 0.4 │ │", - "│ │ │ pretty_assertions │ 1.0.0 │ │", - "│ │ │ serial_test │ 0.10.0 │ │", - "│ │ │ hamcrest2 │ 0.3.0 │ │", - "│ │ │ rstest │ {record 2 │ │", - "│ │ │ │ fields} │ │", - "│ │ │ itertools │ 0.10.3 │ │", - "│ │ ╰───────────────────┴───────────────╯ │", + "│ │ ╭─────────────────────┬─────────────╮ │", + "│ dev-dependencies │ │ nu-test-support │ {record 2 │ │", + "│ │ │ │ fields} │ │", + "│ │ │ tempfile │ 3.2.0 │ │", + "│ │ │ assert_cmd │ 2.0.2 │ │", + "│ │ │ criterion │ 0.4 │ │", + "│ │ │ pretty_assertions │ 1.0.0 │ │", + "│ │ │ serial_test │ 0.10.0 │ │", + "│ │ │ hamcrest2 │ 0.3.0 │ │", + "│ │ │ rstest │ {record 2 │ │", + "│ │ │ │ fields} │ │", + "│ │ │ itertools │ 0.10.3 │ │", + "│ │ ╰─────────────────────┴─────────────╯ │", "│ │ ╭─────────────────────┬─────────────╮ │", "│ features │ │ │ ╭───┬─────╮ │ │", "│ │ │ plugin │ │ 0 │ nu- │ │ │", @@ -2624,11 +2628,10 @@ fn test_collapse_big_0() { "│ ├───────────────┼──────────┬───────────┬────────────────────┤", "│ │ metadata │ binstall │ pkg-url │ { repo }/releases/ │", "│ │ │ │ │ download/{ v │", - "│ │ │ │ │ ersion │", - "│ │ │ │ │ }/{ name }-{ vers │", - "│ │ │ │ │ ion }- │", - "│ │ │ │ │ { target }.{ │", - "│ │ │ │ │ archive-format } │", + "│ │ │ │ │ ersion }/{ name }- │", + "│ │ │ │ │ { version }- │", + "│ │ │ │ │ { target }.{ archi │", + "│ │ │ │ │ ve-format } │", "│ │ │ ├───────────┼────────────────────┤", "│ │ │ │ pkg-fmt │ tgz │", "│ │ │ ├───────────┼────────────────────┤", diff --git a/crates/nu-table/src/lib.rs b/crates/nu-table/src/lib.rs index eb4934d87e..b5e98f8528 100644 --- a/crates/nu-table/src/lib.rs +++ b/crates/nu-table/src/lib.rs @@ -10,7 +10,7 @@ pub mod common; pub use common::{StringResult, TableResult}; pub use nu_color_config::TextStyle; -pub use table::{NuRecordsValue, NuTable}; +pub use table::{NuRecords, NuRecordsValue, NuTable}; pub use table_theme::TableTheme; pub use types::{CollapsedTable, ExpandedTable, JustTable, TableOpts, TableOutput}; pub use unstructured_table::UnstructuredTable; diff --git a/crates/nu-table/src/table.rs b/crates/nu-table/src/table.rs index 0723efc5d6..6c0336f029 100644 --- a/crates/nu-table/src/table.rs +++ b/crates/nu-table/src/table.rs @@ -2,6 +2,8 @@ // TODO: Stop building `tabled` when it's clear we are out of terminal // NOTE: TODO the above we could expose something like [`WidthCtrl`] in which case we could also laverage the width list build right away. // currently it seems like we do recacalculate it for `table -e`? +// TODO: (not hard) We could properly handle dimension - we already do it for width - just need to do height as well +// TODO: (need to check) Maybe Vec::with_dimension and insert "Iterators" would be better instead of preallocated Vec> and index. use std::cmp::{max, min}; @@ -14,14 +16,12 @@ use tabled::{ builder::Builder, grid::{ ansi::ANSIBuf, - colors::Colors, config::{ - AlignmentHorizontal, ColoredConfig, Entity, EntityMap, Indent, Position, Sides, - SpannedConfig, + AlignmentHorizontal, ColoredConfig, Entity, Indent, Position, Sides, SpannedConfig, }, - dimension::{CompleteDimensionVecRecords, SpannedGridDimension}, + dimension::{CompleteDimension, PeekableGridDimension}, records::{ - IntoRecords, IterRecords, Records, + IterRecords, vec_records::{Cell, Text, VecRecords}, }, }, @@ -92,12 +92,10 @@ impl NuTable { self.count_cols } - pub fn insert(&mut self, pos: Position, text: String) { + pub fn insert(&mut self, pos: (usize, usize), text: String) { let text = Text::new(text); - self.widths[pos.1] = max( - self.widths[pos.1], - text.width() + indent_sum(self.config.indent), - ); + let width = text.width() + indent_sum(self.config.indent); + self.widths[pos.1] = max(self.widths[pos.1], width); self.data[pos.0][pos.1] = text; } @@ -148,7 +146,7 @@ impl NuTable { self.count_cols += 1; } - pub fn insert_style(&mut self, pos: Position, style: TextStyle) { + pub fn insert_style(&mut self, pos: (usize, usize), style: TextStyle) { if let Some(style) = style.color_style { let style = convert_style(style); self.styles.cfg.set_color(pos.into(), style.into()); @@ -241,7 +239,8 @@ impl NuTable { pub fn clear_all_colors(&mut self) { self.clear_border_color(); - self.styles.cfg.set_colors(EntityMap::default()); + let cfg = std::mem::take(&mut self.styles.cfg); + self.styles.cfg = ColoredConfig::new(cfg.into_inner()); } /// Converts a table to a String. @@ -280,9 +279,7 @@ impl From>>> for NuTable { fn table_recalculate_widths(t: &mut NuTable) { let pad = indent_sum(t.config.indent); - let records = IterRecords::new(&t.data, t.count_cols, Some(t.count_rows)); - let widths = build_width(records, pad); - t.widths = widths; + t.widths = build_width(&t.data, t.count_cols, t.count_rows, pad); } #[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Copy, Hash)] @@ -425,15 +422,14 @@ fn remove_header(t: &mut NuTable) -> HeadInfo { let alignment = *t .styles .cfg - .get_alignment_horizontal(Entity::Cell(row, col)); + .get_alignment_horizontal(Position::new(row, col)); if alignment != t.styles.alignments.data { t.styles .cfg .set_alignment_horizontal(Entity::Cell(row - 1, col), alignment); } - // TODO: use get_color from upstream (when released) - let color = t.styles.cfg.get_colors().get_color((row, col)).cloned(); + let color = t.styles.cfg.get_color(Position::new(row, col)).cloned(); if let Some(color) = color { t.styles.cfg.set_color(Entity::Cell(row - 1, col), color); } @@ -511,6 +507,7 @@ fn table_set_border_header(table: &mut Table, head: Option, cfg: &Tabl }; } + // todo: Move logic to SetLineHeaders - so it be faster - cleaner if with_footer { let last_row = table.count_rows(); table.with(SetLineHeaders::new(head.clone(), last_row, pad)); @@ -535,6 +532,8 @@ fn set_indent(table: &mut Table, indent: TableIndent) { } fn table_to_string(table: Table, termwidth: usize) -> Option { + // Note: this is a "safe" path; presumable it must never happen cause we must made all the checks already + // TODO: maybe remove it? I think so? let total_width = table.total_width(); if total_width > termwidth { None @@ -598,13 +597,8 @@ impl WidthEstimation { } } -impl TableOption> for WidthCtrl { - fn change( - self, - recs: &mut NuRecords, - cfg: &mut ColoredConfig, - dims: &mut CompleteDimensionVecRecords<'_>, - ) { +impl TableOption for WidthCtrl { + fn change(self, recs: &mut NuRecords, cfg: &mut ColoredConfig, dims: &mut CompleteDimension) { if self.width.truncate { width_ctrl_truncate(self, recs, cfg, dims); return; @@ -618,24 +612,29 @@ impl TableOption> for // NOTE: just an optimization; to not recalculate it internally dims.set_widths(self.width.needed); } + + fn hint_change(&self) -> Option { + None + } } fn width_ctrl_expand( ctrl: WidthCtrl, recs: &mut NuRecords, cfg: &mut ColoredConfig, - dims: &mut CompleteDimensionVecRecords, + dims: &mut CompleteDimension, ) { let opt = Width::increase(ctrl.max_width); - TableOption::, _, _>::change(opt, recs, cfg, dims); + TableOption::::change(opt, recs, cfg, dims); } fn width_ctrl_truncate( ctrl: WidthCtrl, recs: &mut NuRecords, cfg: &mut ColoredConfig, - dims: &mut CompleteDimensionVecRecords, + dims: &mut CompleteDimension, ) { + // todo: maybe general for loop better for (col, (&width, width_original)) in ctrl .width .needed @@ -677,10 +676,22 @@ fn align_table( table.with(AlignmentStrategy::PerLine); if structure.with_header { - table.modify(Rows::first(), Alignment::from(alignments.header)); + table.modify( + Rows::first(), + ( + AlignmentStrategy::PerCell, + Alignment::from(alignments.header), + ), + ); if structure.with_footer { - table.modify(Rows::last(), Alignment::from(alignments.header)); + table.modify( + Rows::last(), + ( + AlignmentStrategy::PerCell, + Alignment::from(alignments.header), + ), + ); } } @@ -1054,22 +1065,27 @@ fn convert_alignment(alignment: nu_color_config::Alignment) -> AlignmentHorizont } } -fn build_width(records: R, pad: usize) -> Vec -where - R: Records, - ::Cell: AsRef, -{ +fn build_width( + records: &[Vec], + count_cols: usize, + count_rows: usize, + pad: usize, +) -> Vec { // TODO: Expose not spaned version (could be optimized). let mut cfg = SpannedConfig::default(); - let padding = Sides { - left: Indent::spaced(pad), - ..Default::default() - }; + cfg.set_padding( + Entity::Global, + Sides::new( + Indent::spaced(pad), + Indent::zero(), + Indent::zero(), + Indent::zero(), + ), + ); - cfg.set_padding(Entity::Global, padding); + let records = IterRecords::new(records, count_cols, Some(count_rows)); - // TODO: Use peekable width - SpannedGridDimension::width(records, &cfg) + PeekableGridDimension::width(records, &cfg) } // It's laverages a use of guuaranted cached widths before hand @@ -1086,13 +1102,8 @@ impl SetLineHeaders { } } -impl TableOption> for SetLineHeaders { - fn change( - self, - recs: &mut NuRecords, - cfg: &mut ColoredConfig, - dims: &mut CompleteDimensionVecRecords<'_>, - ) { +impl TableOption for SetLineHeaders { + fn change(self, recs: &mut NuRecords, cfg: &mut ColoredConfig, dims: &mut CompleteDimension) { let widths = match dims.get_widths() { Some(widths) => widths, None => { @@ -1134,20 +1145,6 @@ fn theme_copy_horizontal_line(theme: &mut tabled::settings::Theme, from: usize, } } -// todo: create a method -#[derive(Debug, Default)] -struct GetDims(Vec); - -impl TableOption> for &mut GetDims { - fn change(self, _: &mut R, _: &mut C, dims: &mut CompleteDimensionVecRecords<'_>) { - self.0 = dims.get_widths().expect("expected to get it").to_vec(); - } - - fn hint_change(&self) -> Option { - None - } -} - pub fn get_color_if_exists(c: &Color) -> Option { if !is_color_empty(c) { Some(c.clone()) diff --git a/crates/nu-table/tests/constrains.rs b/crates/nu-table/tests/constrains.rs index 3b16dcf487..80cd7afe40 100644 --- a/crates/nu-table/tests/constrains.rs +++ b/crates/nu-table/tests/constrains.rs @@ -94,13 +94,13 @@ fn wrap_test() { ( 29, Some( - "┏━━━━━━━━━━━┳━━━━━━━━━┳━━━━━┓\n┃ 123 45678 ┃ qweqw e ┃ ... ┃\n┃ ┃ qwe ┃ ┃\n┣━━━━━━━━━━━╋━━━━━━━━━╋━━━━━┫\n┃ 0 ┃ 1 ┃ ... ┃\n┃ 0 ┃ 1 ┃ ... ┃\n┗━━━━━━━━━━━┻━━━━━━━━━┻━━━━━┛", + "┏━━━━━━━━━━━┳━━━━━━━━━┳━━━━━┓\n┃ 123 45678 ┃ qweqw e ┃ ... ┃\n┃ ┃ qwe ┃ ┃\n┣━━━━━━━━━━━╋━━━━━━━━━╋━━━━━┫\n┃ 0 ┃ 1 ┃ ... ┃\n┃ 0 ┃ 1 ┃ ... ┃\n┗━━━━━━━━━━━┻━━━━━━━━━┻━━━━━┛", ), ), ( 49, Some( - "┏━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━┓\n┃ 123 45678 ┃ qweqw eqwe ┃ xxx xx xx x xx ┃ ... ┃\n┃ ┃ ┃ x xx xx ┃ ┃\n┣━━━━━━━━━━━╋━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━╋━━━━━┫\n┃ 0 ┃ 1 ┃ 2 ┃ ... ┃\n┃ 0 ┃ 1 ┃ 2 ┃ ... ┃\n┗━━━━━━━━━━━┻━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━┻━━━━━┛", + "┏━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━┓\n┃ 123 45678 ┃ qweqw eqwe ┃ xxx xx xx x xx ┃ ... ┃\n┃ ┃ ┃ x xx xx ┃ ┃\n┣━━━━━━━━━━━╋━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━╋━━━━━┫\n┃ 0 ┃ 1 ┃ 2 ┃ ... ┃\n┃ 0 ┃ 1 ┃ 2 ┃ ... ┃\n┗━━━━━━━━━━━┻━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━┻━━━━━┛", ), ), ];