use tui::{
backend::Backend,
layout::{Constraint, Direction, Layout, Rect},
style::{Color, Modifier, Style},
text::{Span, Spans, Text},
widgets::{Block, Borders, List, ListItem, Paragraph, Tabs},
Frame,
};
use tui_logger::TuiLoggerWidget;
use crate::app::core::{App, InputMode, TabItem};
pub struct Scroll {
pub x: u16,
pub y: u16,
}
pub fn draw_ui<B: Backend>(f: &mut Frame<B>, app: &mut App) {
match app.tab_item {
TabItem::Editor => draw_sql_editor_tab(f, app),
TabItem::QueryHistory => draw_query_history_tab(f, app),
TabItem::Context => draw_context_tab(f, app),
TabItem::Logs => draw_logs_tab(f, app),
}
}
fn draw_sql_editor_tab<B: Backend>(f: &mut Frame<B>, app: &mut App) {
let chunks = Layout::default()
.direction(Direction::Vertical)
.margin(2)
.constraints(
[
Constraint::Length(1),
Constraint::Length(3),
Constraint::Length(20),
Constraint::Min(1),
]
.as_ref(),
)
.split(f.size());
let help_message = draw_sql_editor_help(app);
f.render_widget(help_message, chunks[0]);
let tabs = draw_tabs(app);
f.render_widget(tabs, chunks[1]);
let editor = draw_editor(app);
f.render_widget(editor, chunks[2]);
draw_cursor(app, f, &chunks);
let query_results = draw_query_results(app);
f.render_widget(query_results, chunks[3]);
}
fn draw_query_history_tab<B: Backend>(f: &mut Frame<B>, app: &mut App) {
let chunks = Layout::default()
.direction(Direction::Vertical)
.margin(2)
.constraints(
[
Constraint::Length(1),
Constraint::Length(3),
Constraint::Min(1),
]
.as_ref(),
)
.split(f.size());
let help_message = draw_default_help();
f.render_widget(help_message, chunks[0]);
let tabs = draw_tabs(app);
f.render_widget(tabs, chunks[1]);
let query_history = draw_query_history(app);
f.render_widget(query_history, chunks[2])
}
fn draw_context_tab<B: Backend>(f: &mut Frame<B>, app: &mut App) {
let chunks = Layout::default()
.direction(Direction::Vertical)
.margin(2)
.constraints(
[
Constraint::Length(1),
Constraint::Length(3),
Constraint::Min(1),
]
.as_ref(),
)
.split(f.size());
let help_message = draw_default_help();
f.render_widget(help_message, chunks[0]);
let tabs = draw_tabs(app);
f.render_widget(tabs, chunks[1]);
draw_context(f, app, chunks[2]);
}
fn draw_logs_tab<B: Backend>(f: &mut Frame<B>, app: &mut App) {
let chunks = Layout::default()
.direction(Direction::Vertical)
.margin(2)
.constraints(
[
Constraint::Length(1),
Constraint::Length(3),
Constraint::Min(1),
]
.as_ref(),
)
.split(f.size());
let help_message = draw_default_help();
f.render_widget(help_message, chunks[0]);
let tabs = draw_tabs(app);
f.render_widget(tabs, chunks[1]);
let logs = draw_logs();
f.render_widget(logs, chunks[2])
}
fn draw_sql_editor_help<'a>(app: &mut App) -> Paragraph<'a> {
let (msg, style) = match app.input_mode {
InputMode::Normal => (
vec![
Span::raw("Press "),
Span::styled("q", Style::default().add_modifier(Modifier::BOLD)),
Span::raw(" to exit, "),
Span::styled("e", Style::default().add_modifier(Modifier::BOLD)),
Span::raw(" to start editing, "),
Span::styled("c", Style::default().add_modifier(Modifier::BOLD)),
Span::raw(" to clear the editor, "),
Span::styled("r", Style::default().add_modifier(Modifier::BOLD)),
Span::raw(" start 'rc' mode, "),
Span::styled("#", Style::default().add_modifier(Modifier::BOLD)),
Span::raw(" to change tabs."),
],
Style::default().add_modifier(Modifier::RAPID_BLINK),
),
InputMode::Editing => (
vec![
Span::raw("Press "),
Span::styled("Esc", Style::default().add_modifier(Modifier::BOLD)),
Span::raw(" to stop editing, "),
Span::styled("Enter", Style::default().add_modifier(Modifier::BOLD)),
Span::raw(" to execute query after closing ';'"),
],
Style::default(),
),
InputMode::Rc => (
vec![
Span::raw("Press "),
Span::styled("Esc", Style::default().add_modifier(Modifier::BOLD)),
Span::raw(" to exit Rc mode, "),
Span::styled("l", Style::default().add_modifier(Modifier::BOLD)),
Span::raw(" to load 'rc' file into editor, "),
Span::styled("r", Style::default().add_modifier(Modifier::BOLD)),
Span::raw(" to reload 'rc' file, "),
Span::styled("w", Style::default().add_modifier(Modifier::BOLD)),
Span::raw(" to write 'rc' editor contents to file."),
],
Style::default(),
),
};
let mut text = Text::from(Spans::from(msg));
text.patch_style(style);
Paragraph::new(text)
}
fn draw_default_help<'a>() -> Paragraph<'a> {
let (msg, style) = (
vec![
Span::raw("Press "),
Span::styled("q", Style::default().add_modifier(Modifier::BOLD)),
Span::raw(" to exit, "),
Span::styled("#", Style::default().add_modifier(Modifier::BOLD)),
Span::raw(" to change tabs."),
],
Style::default().add_modifier(Modifier::RAPID_BLINK),
);
let mut text = Text::from(Spans::from(msg));
text.patch_style(style);
Paragraph::new(text)
}
fn draw_editor<'a>(app: &mut App) -> Paragraph<'a> {
Paragraph::new(app.editor.input.combine_visible_lines())
.style(match app.input_mode {
InputMode::Editing => Style::default().fg(Color::Yellow),
_ => Style::default(),
})
.block(Block::default().borders(Borders::ALL).title("SQL Editor"))
}
fn draw_cursor<B: Backend>(app: &mut App, f: &mut Frame<B>, chunks: &[Rect]) {
if let InputMode::Editing = app.input_mode {
f.set_cursor(
chunks[2].x + app.editor.get_cursor_column() + 1,
chunks[2].y + app.editor.get_cursor_row() + 1,
)
}
}
fn draw_query_results(app: &mut App) -> Paragraph {
let (query_results, duration) = match &app.query_results {
Some(query_results) => {
let query_meta = app.editor.history.last().unwrap();
let results = if query_meta.query.starts_with("CREATE") {
Paragraph::new(String::from("Table created"))
} else {
Paragraph::new(query_results.pretty_batches.as_str())
.scroll((query_results.scroll.x, query_results.scroll.y))
};
let query_duration_info = query_results.format_timing_info();
(results, query_duration_info)
}
None => {
let last_query = app.editor.history.last();
let no_queries_text = match last_query {
Some(query_meta) => {
if let Some(err) = &query_meta.error {
Paragraph::new(err.as_str())
} else {
Paragraph::new(query_meta.query.as_str())
}
}
None => Paragraph::new("No queries yet"),
};
(no_queries_text, String::new())
}
};
let title = format!("Query Results {}", duration);
query_results.block(Block::default().borders(Borders::TOP).title(title))
}
fn draw_tabs<'a>(app: &mut App) -> Tabs<'a> {
let titles = TabItem::all_values()
.iter()
.map(|tab| tab.title_with_key())
.map(|t| Spans::from(vec![Span::styled(t, Style::default())]))
.collect();
Tabs::new(titles)
.block(Block::default().borders(Borders::ALL).title("Tabs"))
.select(app.tab_item.list_index())
.style(Style::default())
.highlight_style(Style::default().add_modifier(Modifier::BOLD))
}
fn draw_query_history<'a>(app: &mut App) -> List<'a> {
let messages: Vec<ListItem> = app
.editor
.history
.iter()
.enumerate()
.map(|(i, m)| {
let content = vec![
Spans::from(Span::raw(format!(
"Query {} [ {} rows took {:.3} seconds ]",
i, m.rows, m.query_duration
))),
Spans::from(Span::raw(m.query.clone())),
Spans::from(Span::raw(String::new())),
];
ListItem::new(content)
})
.collect();
List::new(messages).block(
Block::default()
.borders(Borders::ALL)
.title("Query History"),
)
}
fn draw_logs<'a>() -> TuiLoggerWidget<'a> {
TuiLoggerWidget::default()
.style_error(Style::default().fg(Color::Red))
.style_debug(Style::default().fg(Color::Green))
.style_warn(Style::default().fg(Color::Yellow))
.style_trace(Style::default().fg(Color::Gray))
.style_info(Style::default().fg(Color::Blue))
.block(
Block::default()
.title("Logs")
.border_style(Style::default())
.borders(Borders::ALL),
)
}
fn draw_context<B: Backend>(f: &mut Frame<B>, app: &mut App, area: Rect) {
let context = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
.split(area);
let exec_config = draw_execution_config(app);
f.render_widget(exec_config, context[0]);
let physical_opts = draw_physical_optimizers(app);
f.render_widget(physical_opts, context[1]);
}
fn draw_execution_config(app: &mut App) -> List {
let exec_config = app.context.format_execution_config().unwrap();
let config: Vec<ListItem> = exec_config
.iter()
.map(|i| {
let content = vec![Spans::from(Span::raw(i.to_string()))];
ListItem::new(content)
})
.collect();
List::new(config).block(
Block::default()
.borders(Borders::ALL)
.title("ExecutionConfig"),
)
}
fn draw_physical_optimizers(app: &mut App) -> List {
let physical_optimizers = app.context.format_physical_optimizers().unwrap();
let opts: Vec<ListItem> = physical_optimizers
.iter()
.map(|i| {
let content = vec![Spans::from(Span::raw(i.to_string()))];
ListItem::new(content)
})
.collect();
List::new(opts).block(
Block::default()
.borders(Borders::ALL)
.title("Physical Optimizers"),
)
}