Repository: tursodatabase/limbo (language: rust)

Issue:
Weird plus sign after `CREATE TABLE` in pretty mode
```
limbo> .mode pretty
limbo> CREATE TABLE t(x);
+
```


Discussion / hints:

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index 819311184..3d610591d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -399,29 +399,6 @@ version = "0.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
 
-[[package]]
-name = "cli-table"
-version = "0.4.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b53f9241f288a7b12c56565f04aaeaeeab6b8923d42d99255d4ca428b4d97f89"
-dependencies = [
- "cli-table-derive",
- "csv",
- "termcolor",
- "unicode-width",
-]
-
-[[package]]
-name = "cli-table-derive"
-version = "0.4.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3e83a93253aaae7c74eb7428ce4faa6e219ba94886908048888701819f82fb94"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 1.0.109",
-]
-
 [[package]]
 name = "clipboard-win"
 version = "4.5.0"
@@ -449,6 +426,17 @@ dependencies = [
  "memchr",
 ]
 
+[[package]]
+name = "comfy-table"
+version = "7.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a65ebfec4fb190b6f90e944a817d60499ee0744e582530e2c9900a22e591d9a"
+dependencies = [
+ "crossterm",
+ "unicode-segmentation",
+ "unicode-width 0.2.0",
+]
+
 [[package]]
 name = "comma"
 version = "1.0.0"
@@ -595,6 +583,28 @@ version = "0.8.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
 
+[[package]]
+name = "crossterm"
+version = "0.28.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6"
+dependencies = [
+ "bitflags 2.8.0",
+ "crossterm_winapi",
+ "parking_lot",
+ "rustix",
+ "winapi",
+]
+
+[[package]]
+name = "crossterm_winapi"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
+dependencies = [
+ "winapi",
+]
+
 [[package]]
 name = "crunchy"
 version = "0.2.2"
@@ -1564,7 +1574,7 @@ version = "0.0.14"
 dependencies = [
  "anyhow",
  "clap",
- "cli-table",
+ "comfy-table",
  "csv",
  "ctrlc",
  "dirs",
@@ -1848,7 +1858,7 @@ dependencies = [
  "terminal_size",
  "textwrap",
  "thiserror 1.0.69",
- "unicode-width",
+ "unicode-width 0.1.14",
 ]
 
 [[package]]
@@ -2704,7 +2714,7 @@ dependencies = [
  "radix_trie",
  "scopeguard",
  "unicode-segmentation",
- "unicode-width",
+ "unicode-width 0.1.14",
  "utf8parse",
  "winapi",
 ]
@@ -3033,7 +3043,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9"
 dependencies = [
  "unicode-linebreak",
- "unicode-width",
+ "unicode-width 0.1.14",
 ]
 
 [[package]]
@@ -3201,6 +3211,12 @@ version = "0.1.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
 
+[[package]]
+name = "unicode-width"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
+
 [[package]]
 name = "unindent"
 version = "0.2.3"
diff --git a/cli/Cargo.toml b/cli/Cargo.toml
index 1886627e4..03857adb2 100644
--- a/cli/Cargo.toml
+++ b/cli/Cargo.toml
@@ -21,7 +21,7 @@ path = "main.rs"
 [dependencies]
 anyhow = "1.0.75"
 clap = { version = "4.5", features = ["derive"] }
-cli-table = "0.4.7"
+comfy-table = "7.1.4"
 dirs = "5.0.1"
 env_logger = "0.10.1"
 limbo_core = { path = "../core" }
diff --git a/cli/app.rs b/cli/app.rs
index 7fa7b70f0..45dd14896 100644
--- a/cli/app.rs
+++ b/cli/app.rs
@@ -2,8 +2,7 @@ use crate::{
     import::{ImportFile, IMPORT_HELP},
     opcodes_dictionary::OPCODE_DESCRIPTIONS,
 };
-use cli_table::format::{Border, HorizontalLine, Separator, VerticalLine};
-use cli_table::{Cell, Style, Table};
+use comfy_table::{Attribute, Cell, CellAlignment, ContentArrangement, Row, Table};
 use limbo_core::{Database, LimboError, Statement, StepResult, Value};
 
 use clap::{Parser, ValueEnum};
@@ -670,35 +669,42 @@ impl Limbo {
                         println!("Query interrupted.");
                         return Ok(());
                     }
-                    let mut table_rows: Vec<Vec<_>> = vec![];
+                    let mut table = Table::new();
+                    table
+                        .set_content_arrangement(ContentArrangement::Dynamic)
+                        .set_truncation_indicator("…")
+                        .apply_modifier("││──├─┼┤│─┼├┤┬┴┌┐└┘");
                     if rows.num_columns() > 0 {
-                        let columns = (0..rows.num_columns())
+                        let header = (0..rows.num_columns())
                             .map(|i| {
-                                rows.get_column_name(i)
-                                    .map(|name| name.cell().bold(true))
-                                    .unwrap_or_else(|| " ".cell())
+                                let name = rows.get_column_name(i).cloned().unwrap_or_default();
+                                Cell::new(name).add_attribute(Attribute::Bold)
                             })
                             .collect::<Vec<_>>();
-                        table_rows.push(columns);
+                        table.set_header(header);
                     }
                     loop {
                         match rows.step() {
                             Ok(StepResult::Row) => {
-                                let row = rows.row().unwrap();
-                                table_rows.push(
-                                    row.values
-                                        .iter()
-                                        .map(|value| match value.to_value() {
-                                            Value::Null => self.opts.null_value.clone().cell(),
-                                            Value::Integer(i) => i.to_string().cell(),
-                                            Value::Float(f) => f.to_string().cell(),
-                                            Value::Text(s) => s.cell(),
-                                            Value::Blob(b) => {
-                                                format!("{}", String::from_utf8_lossy(b)).cell()
-                                            }
-                                        })
-                                        .collect(),
-                                );
+                                let record = rows.row().unwrap();
+                                let mut row = Row::new();
+                                row.max_height(1);
+                                for value in &record.values {
+                                    let (content, alignment) = match value.to_value() {
+                                        Value::Null => {
+                                            (self.opts.null_value.clone(), CellAlignment::Left)
+                                        }
+                                        Value::Integer(i) => (i.to_string(), CellAlignment::Right),
+                                        Value::Float(f) => (f.to_string(), CellAlignment::Right),
+                                        Value::Text(s) => (s.to_string(), CellAlignment::Left),
+                                        Value::Blob(b) => (
+                                            String::from_utf8_lossy(b).to_string(),
+                                            CellAlignment::Left,
+                                        ),
+                                    };
+                                    row.add_cell(Cell::new(content).set_alignment(alignment));
+                                }
+                                table.add_row(row);
                             }
                             Ok(StepResult::IO) => {
                                 self.io.run_once()?;
@@ -718,7 +724,10 @@ impl Limbo {
                             }
                         }
                     }
-                    self.print_table(table_rows);
+
+                    if table.header().is_some() {
+                        let _ = self.write_fmt(format_args!("{}", table));
+                    }
                 }
             },
             Ok(None) => {}
@@ -734,40 +743,6 @@ impl Limbo {
         Ok(())
     }
 
-    fn print_table(&mut self, table_rows: Vec<Vec<cli_table::CellStruct>>) {
-        if table_rows.is_empty() {
-            return;
-        }
-
-        let horizontal_line = HorizontalLine::new('┌', '┐', '┬', '─');
-        let horizontal_line_mid = HorizontalLine::new('├', '┤', '┼', '─');
-        let horizontal_line_bottom = HorizontalLine::new('└', '┘', '┴', '─');
-        let vertical_line = VerticalLine::new('│');
-
-        let border = Border::builder()
-            .top(horizontal_line)
-            .bottom(horizontal_line_bottom)
-            .left(vertical_line.clone())
-            .right(vertical_line.clone())
-            .build();
-
-        let separator = Separator::builder()
-            .column(Some(vertical_line))
-            .row(Some(horizontal_line_mid))
-            .build();
-
-        if let Ok(table) = table_rows
-            .table()
-            .border(border)
-            .separator(separator)
-            .display()
-        {
-            let _ = self.write_fmt(format_args!("{}", table));
-        } else {
-            let _ = self.writeln("Error displaying table.");
-        }
-    }
-
     fn display_schema(&mut self, table: Option<&str>) -> anyhow::Result<()> {
         let sql = match table {
         Some(table_name) => format!(


Test patch: