mirror of
https://github.com/aljazceru/turso.git
synced 2026-01-28 12:24:23 +01:00
Merge 'Syntax highlighting and hinting' from Pedro Muniz
Start of syntax highlighting and hinting. Still need to figure out how to sublime-syntax works to produce good highlights. Edit: Personally, I believe there are more interesting syntax highlighting possibilities with `reedline` crate, but currently, we cannot use it as the our DB `Connection` would have to be `Send`. This PR is an introduction and quality of life changes for the users of the CLI and for us developers, as we now won't have to look at black and white text only. I want to have a config file to personalize the color pallets, that will be made in a following PR. Closes #1101
This commit is contained in:
574
Cargo.lock
generated
574
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,14 +1,14 @@
|
||||
# Copyright 2023 the Limbo authors. All rights reserved. MIT license.
|
||||
|
||||
[package]
|
||||
name = "limbo_cli"
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
default-run = "limbo"
|
||||
description = "The Limbo interactive SQL shell"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
name = "limbo_cli"
|
||||
repository.workspace = true
|
||||
description = "The Limbo interactive SQL shell"
|
||||
version.workspace = true
|
||||
|
||||
[package.metadata.dist]
|
||||
dist = true
|
||||
@@ -36,7 +36,14 @@ miette = { version = "7.4.0", features = ["fancy"] }
|
||||
cfg-if = "1.0.0"
|
||||
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
||||
tracing = "0.1.41"
|
||||
syntect = "5.2.0"
|
||||
nu-ansi-term = "0.50.1"
|
||||
|
||||
|
||||
[features]
|
||||
default = ["io_uring"]
|
||||
io_uring = ["limbo_core/io_uring"]
|
||||
|
||||
[build-dependencies]
|
||||
syntect = "5.2.0"
|
||||
|
||||
|
||||
1669
cli/SQL.sublime-syntax
Normal file
1669
cli/SQL.sublime-syntax
Normal file
File diff suppressed because it is too large
Load Diff
17
cli/app.rs
17
cli/app.rs
@@ -4,7 +4,7 @@ use crate::{
|
||||
input::{get_io, get_writer, DbLocation, OutputMode, Settings, HELP_MSG},
|
||||
opcodes_dictionary::OPCODE_DESCRIPTIONS,
|
||||
};
|
||||
use comfy_table::{Attribute, Cell, CellAlignment, ContentArrangement, Row, Table};
|
||||
use comfy_table::{Attribute, Cell, CellAlignment, Color, ContentArrangement, Row, Table};
|
||||
use limbo_core::{Database, LimboError, OwnedValue, Statement, StepResult};
|
||||
|
||||
use clap::{Parser, ValueEnum};
|
||||
@@ -200,6 +200,8 @@ macro_rules! query_internal {
|
||||
}};
|
||||
}
|
||||
|
||||
static COLORS: &[Color] = &[Color::DarkRed, Color::DarkGreen, Color::DarkBlue];
|
||||
|
||||
impl<'a> Limbo<'a> {
|
||||
pub fn new(rl: &'a mut rustyline::Editor<LimboHelper, DefaultHistory>) -> anyhow::Result<Self> {
|
||||
let opts = Opts::parse();
|
||||
@@ -249,6 +251,7 @@ impl<'a> Limbo<'a> {
|
||||
opts: Settings::from(&opts),
|
||||
rl,
|
||||
};
|
||||
|
||||
if opts.sql.is_some() {
|
||||
app.handle_first_input(opts.sql.as_ref().unwrap());
|
||||
}
|
||||
@@ -752,7 +755,9 @@ impl<'a> Limbo<'a> {
|
||||
let header = (0..rows.num_columns())
|
||||
.map(|i| {
|
||||
let name = rows.get_column_name(i);
|
||||
Cell::new(name).add_attribute(Attribute::Bold)
|
||||
Cell::new(name)
|
||||
.add_attribute(Attribute::Bold)
|
||||
.fg(Color::AnsiValue(49)) // Green color for headers
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
table.set_header(header);
|
||||
@@ -763,7 +768,7 @@ impl<'a> Limbo<'a> {
|
||||
let record = rows.row().unwrap();
|
||||
let mut row = Row::new();
|
||||
row.max_height(1);
|
||||
for value in record.get_values() {
|
||||
for (idx, value) in record.get_values().iter().enumerate() {
|
||||
let (content, alignment) = match value {
|
||||
OwnedValue::Null => {
|
||||
(self.opts.null_value.clone(), CellAlignment::Left)
|
||||
@@ -781,7 +786,11 @@ impl<'a> Limbo<'a> {
|
||||
),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
row.add_cell(Cell::new(content).set_alignment(alignment));
|
||||
row.add_cell(
|
||||
Cell::new(content)
|
||||
.set_alignment(alignment)
|
||||
.fg(COLORS[idx % COLORS.len()]),
|
||||
);
|
||||
}
|
||||
table.add_row(row);
|
||||
}
|
||||
|
||||
26
cli/build.rs
Normal file
26
cli/build.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
//! Build.rs script to generate a binary syntax set for syntect
|
||||
//! based on the SQL.sublime-syntax file.
|
||||
|
||||
use std::env;
|
||||
use std::path::Path;
|
||||
|
||||
use syntect::dumps::dump_to_uncompressed_file;
|
||||
use syntect::parsing::SyntaxDefinition;
|
||||
use syntect::parsing::SyntaxSet;
|
||||
|
||||
fn main() {
|
||||
println!("cargo::rerun-if-changed=SQL.sublime-syntax");
|
||||
println!("cargo::rerun-if-changed=build.rs");
|
||||
|
||||
let out_dir = env::var_os("OUT_DIR").unwrap();
|
||||
let syntax =
|
||||
SyntaxDefinition::load_from_str(include_str!("./SQL.sublime-syntax"), false, None).unwrap();
|
||||
let mut ps = SyntaxSet::new().into_builder();
|
||||
ps.add(syntax);
|
||||
let ps = ps.build();
|
||||
dump_to_uncompressed_file(
|
||||
&ps,
|
||||
Path::new(&out_dir).join("SQL_syntax_set_dump.packdump"),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
@@ -2,9 +2,16 @@ use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use limbo_core::{Connection, StepResult};
|
||||
use nu_ansi_term::{Color, Style};
|
||||
use rustyline::completion::{extract_word, Completer, Pair};
|
||||
use rustyline::highlight::Highlighter;
|
||||
use rustyline::hint::HistoryHinter;
|
||||
use rustyline::{Completer, Helper, Hinter, Validator};
|
||||
use syntect::dumps::from_uncompressed_data;
|
||||
use syntect::easy::HighlightLines;
|
||||
use syntect::highlighting::ThemeSet;
|
||||
use syntect::parsing::{Scope, SyntaxSet};
|
||||
use syntect::util::{as_24_bit_terminal_escaped, LinesWithEndings};
|
||||
|
||||
macro_rules! try_result {
|
||||
($expr:expr, $err:expr) => {
|
||||
@@ -19,17 +26,92 @@ macro_rules! try_result {
|
||||
pub struct LimboHelper {
|
||||
#[rustyline(Completer)]
|
||||
completer: SqlCompleter,
|
||||
syntax_set: SyntaxSet,
|
||||
theme_set: ThemeSet,
|
||||
#[rustyline(Hinter)]
|
||||
hinter: HistoryHinter,
|
||||
}
|
||||
|
||||
impl LimboHelper {
|
||||
pub fn new(conn: Rc<Connection>, io: Arc<dyn limbo_core::IO>) -> Self {
|
||||
// Load only predefined syntax
|
||||
let ps = from_uncompressed_data(include_bytes!(concat!(
|
||||
env!("OUT_DIR"),
|
||||
"/SQL_syntax_set_dump.packdump"
|
||||
)))
|
||||
.unwrap();
|
||||
let ts = ThemeSet::load_defaults();
|
||||
LimboHelper {
|
||||
completer: SqlCompleter::new(conn, io),
|
||||
syntax_set: ps,
|
||||
theme_set: ts,
|
||||
hinter: HistoryHinter::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Highlighter for LimboHelper {}
|
||||
impl Highlighter for LimboHelper {
|
||||
fn highlight<'l>(&self, line: &'l str, pos: usize) -> std::borrow::Cow<'l, str> {
|
||||
let _ = pos;
|
||||
// TODO use lifetimes to store highlight lines
|
||||
let syntax = self
|
||||
.syntax_set
|
||||
.find_syntax_by_scope(Scope::new("source.sql").unwrap())
|
||||
.unwrap();
|
||||
let mut h = HighlightLines::new(syntax, &self.theme_set.themes["base16-ocean.dark"]);
|
||||
let ranges = {
|
||||
let mut ret_ranges = Vec::new();
|
||||
for new_line in LinesWithEndings::from(line) {
|
||||
let ranges: Vec<(syntect::highlighting::Style, &str)> =
|
||||
h.highlight_line(new_line, &self.syntax_set).unwrap();
|
||||
ret_ranges.extend(ranges);
|
||||
}
|
||||
ret_ranges
|
||||
};
|
||||
let mut ret_line = as_24_bit_terminal_escaped(&ranges[..], false);
|
||||
// Push this escape sequence to reset color modes at the end of the string
|
||||
ret_line.push_str("\x1b[0m");
|
||||
std::borrow::Cow::Owned(ret_line)
|
||||
}
|
||||
|
||||
fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
|
||||
&'s self,
|
||||
prompt: &'p str,
|
||||
default: bool,
|
||||
) -> std::borrow::Cow<'b, str> {
|
||||
let _ = default;
|
||||
// Make prompt bold
|
||||
let style = Style::new().bold().fg(Color::Green);
|
||||
let styled_str = style.paint(prompt);
|
||||
std::borrow::Cow::Owned(styled_str.to_string())
|
||||
}
|
||||
|
||||
fn highlight_hint<'h>(&self, hint: &'h str) -> std::borrow::Cow<'h, str> {
|
||||
let style = Style::new()
|
||||
.bold()
|
||||
.dimmed()
|
||||
.underline()
|
||||
.fg(Color::Fixed(246));
|
||||
let styled_str = style.paint(hint);
|
||||
std::borrow::Cow::Owned(styled_str.to_string()) // Bold dim grey underline
|
||||
}
|
||||
|
||||
fn highlight_candidate<'c>(
|
||||
&self,
|
||||
candidate: &'c str,
|
||||
completion: rustyline::CompletionType,
|
||||
) -> std::borrow::Cow<'c, str> {
|
||||
let _ = completion;
|
||||
let style = Style::new().fg(Color::Fixed(69));
|
||||
let styled_str = style.paint(candidate);
|
||||
std::borrow::Cow::Owned(styled_str.to_string())
|
||||
}
|
||||
|
||||
fn highlight_char(&self, line: &str, pos: usize, kind: rustyline::highlight::CmdKind) -> bool {
|
||||
let _ = (line, pos);
|
||||
!matches!(kind, rustyline::highlight::CmdKind::MoveCursor)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SqlCompleter {
|
||||
conn: Rc<Connection>,
|
||||
|
||||
@@ -23,7 +23,7 @@ tempfile = "3.0.7"
|
||||
env_logger = "0.10.1"
|
||||
regex = "1.11.1"
|
||||
regex-syntax = { version = "0.8.5", default-features = false, features = ["unicode"] }
|
||||
anarchist-readable-name-generator-lib = "0.1.2"
|
||||
anarchist-readable-name-generator-lib = "=0.1.2"
|
||||
clap = { version = "4.5", features = ["derive"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = { version = "1.0" }
|
||||
|
||||
Reference in New Issue
Block a user