use std::collections::{HashMap, HashSet};
use std::rc::Rc;
use gpui_kit::component::input::{InputEvent, InputState};
use gpui_kit::component::menu::{ContextMenuExt as _, PopupMenu};
use gpui_kit::component::table::{ColumnSort, DataTable, TableEvent, TableState};
use gpui_kit::component::{ActiveTheme, Sizable, h_flex, v_flex};
use gpui_kit::prelude::*;
use gpui_kit::{
App, Context, Entity, EventEmitter, FocusHandle, Focusable, MouseUpEvent, Window, actions, div,
};
use crate::db::Engine;
use crate::db::export::Format;
use crate::db::query::{self, Cell, QueryResult};
use crate::settings::{self, Settings};
use crate::ui::value_dialog::{self, ValueRequest};
mod clipboard;
mod delegate;
mod format;
mod layout;
#[cfg(test)]
mod test_support;
use delegate::ResultDelegate;
use layout::measure_columns;
pub use format::{format_value, needs_a_window};
actions!(
zippa_db,
[
ViewCell,
CopyValue,
CopyWithHeaders,
SelectAllRows,
ClearRowSelection,
ToggleRow,
ExtendSelectionUp,
ExtendSelectionDown,
]
);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Sorting {
InPlace,
Delegated,
}
type SortReporter = Rc<dyn Fn(String, ColumnSort, &mut App)>;
type ChangeReporter = Rc<dyn Fn(&mut App)>;
type ViewReporter = Rc<dyn Fn(usize, usize, &mut App)>;
type NavReporter = Rc<dyn Fn(usize, usize, &mut App)>;
type CopyReporter = Rc<dyn Fn(Option<(usize, usize)>, &mut App)>;
type ExportReporter = Rc<dyn Fn(Format, &mut App)>;
type CopyAsReporter = Rc<dyn Fn(CopyAs, &mut App)>;
pub struct SortRequested {
pub column: String,
pub sort: ColumnSort,
}
pub enum GridEdit {
Staged,
RowLeft { row: usize },
RowFocused(Option<usize>),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RowStatus {
Loaded,
Draft,
Deleted,
}
pub struct GridNavigate {
pub row: usize,
pub col: usize,
}
pub struct BinaryPreviewRequested {
pub row: usize,
pub column: String,
}
pub struct ExportRequested {
pub format: Format,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CopyAs {
Rows(Format),
ColumnValues(usize),
ColumnInList(usize),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Scope {
Picked,
All,
Column(usize),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Snapshot {
pub columns: Vec<String>,
pub types: Vec<String>,
pub rows: Vec<Vec<Cell>>,
}
pub struct Copied {
pub message: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StagedRow {
pub row: usize,
pub cells: Vec<(usize, Cell)>,
}
pub struct DataGrid {
table: Entity<TableState<ResultDelegate>>,
has_result: bool,
editor: Entity<InputState>,
}
impl Focusable for DataGrid {
fn focus_handle(&self, cx: &App) -> FocusHandle {
self.table.read(cx).focus_handle(cx)
}
}
impl EventEmitter<SortRequested> for DataGrid {}
impl EventEmitter<GridEdit> for DataGrid {}
impl EventEmitter<GridNavigate> for DataGrid {}
impl EventEmitter<BinaryPreviewRequested> for DataGrid {}
impl EventEmitter<ExportRequested> for DataGrid {}
impl EventEmitter<Copied> for DataGrid {}
impl DataGrid {
pub fn new(engine: Engine, window: &mut Window, cx: &mut Context<Self>) -> Self {
Self::with_sorting(Sorting::InPlace, engine, window, cx)
}
pub fn with_sorting(
sorting: Sorting,
engine: Engine,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
let font = settings::grid_font(cx);
let grid = cx.entity().downgrade();
let report_sort: SortReporter = Rc::new(move |column, sort, cx| {
if let Some(grid) = grid.upgrade() {
grid.update(cx, |_, cx| cx.emit(SortRequested { column, sort }));
}
});
let report_change: ChangeReporter = Rc::new({
let grid = cx.weak_entity();
move |cx: &mut App| {
let Some(grid) = grid.upgrade() else {
return;
};
grid.update(cx, |_, cx| cx.emit(GridEdit::Staged));
}
});
let report_view: ViewReporter = Rc::new({
let grid = cx.weak_entity();
move |row_ix: usize, col_ix: usize, cx: &mut App| {
let Some(grid) = grid.upgrade() else {
return;
};
grid.update(cx, |grid, cx| grid.view_cell(row_ix, col_ix, cx));
}
});
let report_navigate: NavReporter = Rc::new({
let grid = cx.weak_entity();
move |row_ix: usize, col_ix: usize, cx: &mut App| {
let Some(grid) = grid.upgrade() else {
return;
};
grid.update(cx, |_, cx| {
cx.emit(GridNavigate {
row: row_ix,
col: col_ix,
})
});
}
});
let report_copy: CopyReporter = Rc::new({
let grid = cx.weak_entity();
move |cell: Option<(usize, usize)>, cx: &mut App| {
let Some(grid) = grid.upgrade() else {
return;
};
grid.update(cx, |grid, cx| grid.copy(cell, cx));
}
});
let report_export: ExportReporter = Rc::new({
let grid = cx.weak_entity();
move |format: Format, cx: &mut App| {
let Some(grid) = grid.upgrade() else {
return;
};
grid.update(cx, |_, cx| cx.emit(ExportRequested { format }));
}
});
let report_copy_as: CopyAsReporter = Rc::new({
let grid = cx.weak_entity();
move |what: CopyAs, cx: &mut App| {
let Some(grid) = grid.upgrade() else {
return;
};
grid.update(cx, |grid, cx| grid.copy_as(what, cx));
}
});
let editor = cx.new(|cx| InputState::new(window, cx));
cx.subscribe_in(&editor, window, Self::on_editor_event)
.detach();
let table = cx.new(|cx| {
TableState::new(
ResultDelegate {
result: QueryResult::default(),
order: Vec::new(),
widths: Vec::new(),
focused_row: None,
font,
sorting,
sorted_by: None,
report_sort,
edits: HashMap::new(),
editable: false,
rows_selected: HashSet::new(),
anchor: None,
sweeping: None,
menu_row: None,
menu_cell: None,
deletions: HashSet::new(),
report_change,
report_view,
report_navigate,
report_copy,
report_export,
report_copy_as,
engine,
table: None,
foreign_keys: HashSet::new(),
drafts: Vec::new(),
editing: None,
editor: editor.clone(),
},
window,
cx,
)
.cell_selectable(true)
.row_header(false)
.row_selectable(false)
.loop_selection(false)
.col_movable(false)
});
cx.observe_global::<Settings>(|this, cx| {
let font = settings::grid_font(cx);
this.table.update(cx, |table, cx| {
if table.delegate().font != font {
table.delegate_mut().font = font;
cx.notify();
}
});
cx.notify();
})
.detach();
cx.subscribe_in(&table, window, Self::on_table_event)
.detach();
Self {
table,
has_result: false,
editor,
}
}
fn on_table_event(
&mut self,
table: &Entity<TableState<ResultDelegate>>,
event: &TableEvent,
window: &mut Window,
cx: &mut Context<Self>,
) {
if let TableEvent::SelectCell(row_ix, col_ix) = event
&& table.read(cx).delegate().data_column(*col_ix).is_none()
{
let row_ix = *row_ix;
if !self.is_empty(cx) {
let first = ResultDelegate::shown_column(0);
table.update(cx, |table, cx| table.set_selected_cell(row_ix, first, cx));
}
return;
}
if let TableEvent::SelectRow(row_ix) = event {
let row_ix = *row_ix;
if !self.is_empty(cx) {
let first = ResultDelegate::shown_column(0);
table.update(cx, |table, cx| table.set_selected_cell(row_ix, first, cx));
}
return;
}
if let TableEvent::DoubleClickedCell(row_ix, col_ix) = event {
let row_ix = *row_ix;
let Some(col_ix) = table.read(cx).delegate().data_column(*col_ix) else {
return;
};
let delegate = table.read(cx).delegate();
let type_name = delegate
.result
.column_types
.get(col_ix)
.cloned()
.unwrap_or_default();
let big = needs_a_window(delegate.cell(row_ix, col_ix), &type_name);
if big {
self.view_cell(row_ix, col_ix, cx);
} else {
self.begin_edit(row_ix, col_ix, window, cx);
}
return;
}
if matches!(event, TableEvent::ClearSelection) {
self.clear_row_selection(cx);
}
let row = match event {
TableEvent::SelectCell(row_ix, _) => Some(*row_ix),
TableEvent::ClearSelection => None,
_ => return,
};
self.commit_editor(cx);
let previous = table.read(cx).delegate().focused_row;
if previous == row {
return;
}
table.update(cx, |table, cx| {
table.delegate_mut().focused_row = row;
cx.notify();
});
cx.emit(GridEdit::RowFocused(row));
if let Some(left) = previous
&& let Some(source) = table.read(cx).delegate().source(left)
&& self.row_has_edits(source, cx)
{
cx.emit(GridEdit::RowLeft { row: source });
}
}
fn on_editor_event(
&mut self,
_: &Entity<InputState>,
event: &InputEvent,
window: &mut Window,
cx: &mut Context<Self>,
) {
if !matches!(event, InputEvent::PressEnter { .. } | InputEvent::Blur) {
return;
}
let finished = matches!(event, InputEvent::PressEnter { .. });
self.commit_editor(cx);
if finished {
self.focus_table(window, cx);
}
}
fn focus_table(&self, window: &mut Window, cx: &mut App) {
self.focus_handle(cx).focus(window, cx);
}
pub fn set_editable(&mut self, editable: bool, cx: &mut Context<Self>) {
self.table.update(cx, |table, cx| {
if table.delegate().editable == editable {
return;
}
let delegate = table.delegate_mut();
delegate.editable = editable;
delegate.editing = None;
if !editable {
delegate.edits.clear();
delegate.drafts.clear();
delegate.deletions.clear();
}
cx.notify();
});
}
pub fn set_foreign_keys(&mut self, columns: HashSet<usize>, cx: &mut Context<Self>) {
self.table.update(cx, |table, cx| {
table.delegate_mut().foreign_keys = columns;
cx.notify();
});
}
pub fn set_table(&mut self, name: Option<String>, cx: &mut Context<Self>) {
self.table.update(cx, |table, cx| {
if table.delegate().table == name {
return;
}
table.delegate_mut().table = name;
cx.notify();
});
}
pub fn begin_edit(
&mut self,
row_ix: usize,
col_ix: usize,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.commit_editor(cx);
if !self.table.read(cx).delegate().is_editable(row_ix, col_ix) {
return;
}
let value = self
.table
.read(cx)
.delegate()
.cell(row_ix, col_ix)
.clone()
.unwrap_or_default();
self.editor
.update(cx, |editor, cx| editor.set_value(value, window, cx));
self.table.update(cx, |table, cx| {
table.delegate_mut().editing = Some((row_ix, col_ix));
cx.notify();
});
self.editor
.update(cx, |editor, cx| editor.focus(window, cx));
}
fn selected_cell(&self, cx: &App) -> Option<(usize, usize)> {
let table = self.table.read(cx);
let (row_ix, col_ix) = table.selected_cell()?;
Some((row_ix, table.delegate().data_column(col_ix)?))
}
pub fn edit_selected(&mut self, window: &mut Window, cx: &mut Context<Self>) {
if let Some((row_ix, col_ix)) = self.selected_cell(cx) {
self.begin_edit(row_ix, col_ix, window, cx);
}
}
pub fn set_null(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let Some((row_ix, col_ix)) = self.selected_cell(cx) else {
return;
};
if !self.table.read(cx).delegate().is_editable(row_ix, col_ix) {
return;
}
self.cancel_editor(window, cx);
self.table.update(cx, |table, cx| {
table.delegate_mut().stage(row_ix, col_ix, None);
cx.notify();
});
cx.emit(GridEdit::Staged);
}
pub fn commit_editor(&mut self, cx: &mut Context<Self>) {
let Some((row_ix, col_ix)) = self.table.read(cx).delegate().editing else {
return;
};
let text = self.editor.read(cx).value().to_string();
let value = staged_value(text, Settings::global(cx).coerce_null_literal);
self.table.update(cx, |table, cx| {
let delegate = table.delegate_mut();
delegate.editing = None;
delegate.stage(row_ix, col_ix, value);
cx.notify();
});
cx.emit(GridEdit::Staged);
}
pub fn cancel_editor(&mut self, window: &mut Window, cx: &mut Context<Self>) {
if self.close_editor(cx) {
self.focus_table(window, cx);
}
}
fn close_editor(&mut self, cx: &mut Context<Self>) -> bool {
self.table.update(cx, |table, cx| {
if table.delegate().editing.is_none() {
return false;
}
table.delegate_mut().editing = None;
cx.notify();
true
})
}
pub fn add_draft(&mut self, cx: &mut Context<Self>) {
if !self.table.read(cx).delegate().editable {
return;
}
self.commit_editor(cx);
self.table.update(cx, |table, cx| {
table.delegate_mut().drafts.push(HashMap::new());
table.refresh(cx);
});
cx.emit(GridEdit::Staged);
}
pub fn drafts(&self, cx: &App) -> Vec<Vec<(usize, Cell)>> {
self.table
.read(cx)
.delegate()
.drafts
.iter()
.map(|draft| {
let mut cells: Vec<(usize, Cell)> = draft
.iter()
.map(|(col, value)| (*col, value.clone()))
.collect();
cells.sort_by_key(|(col, _)| *col);
cells
})
.collect()
}
pub fn pending(&self, cx: &App) -> usize {
let (edited, inserted, deleted) = self.pending_counts(cx);
edited + inserted + deleted
}
pub fn pending_counts(&self, cx: &App) -> (usize, usize, usize) {
let delegate = self.table.read(cx).delegate();
let edited = self
.staged(cx)
.into_iter()
.filter(|staged| !delegate.deletions.contains(&staged.row))
.count();
(edited, delegate.drafts.len(), delegate.deletions.len())
}
pub fn has_unsaved_edits(&self, cx: &App) -> bool {
if self.pending(cx) > 0 {
return true;
}
let delegate = self.table.read(cx).delegate();
let Some((row_ix, col_ix)) = delegate.editing else {
return false;
};
let text = delegate.editor.read(cx).value().to_string();
let value = staged_value(text, Settings::global(cx).coerce_null_literal);
delegate.cell(row_ix, col_ix) != &value
}
fn row_menu(
&self,
) -> impl Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu + 'static {
let table = self.table.downgrade();
move |menu, window, cx| {
let Some(table) = table.upgrade() else {
return menu;
};
let (row_ix, selected_column) = {
let table = table.read(cx);
(
table.delegate().menu_row,
table
.selected_cell()
.and_then(|(_, col)| table.delegate().data_column(col)),
)
};
let Some(row_ix) = row_ix else {
return menu;
};
table.update(cx, |table, cx| {
table
.delegate_mut()
.row_menu_items(row_ix, menu, selected_column, window, cx)
})
}
}
fn on_view_cell(&mut self, _: &ViewCell, _window: &mut Window, cx: &mut Context<Self>) {
self.view_selected(cx);
}
fn on_select_all_rows(
&mut self,
_: &SelectAllRows,
_window: &mut Window,
cx: &mut Context<Self>,
) {
self.table.update(cx, |table, cx| {
let rows = table.delegate().rows();
table.delegate_mut().pick_all(true, rows);
cx.notify();
});
}
fn on_clear_row_selection(
&mut self,
_: &ClearRowSelection,
_window: &mut Window,
cx: &mut Context<Self>,
) {
self.clear_row_selection(cx);
}
fn on_toggle_row(&mut self, _: &ToggleRow, _window: &mut Window, cx: &mut Context<Self>) {
self.table.update(cx, |table, cx| {
let Some(row_ix) = table.delegate().focused_row else {
return;
};
table.delegate_mut().pick(row_ix, false);
cx.notify();
});
}
fn on_extend_up(&mut self, _: &ExtendSelectionUp, window: &mut Window, cx: &mut Context<Self>) {
self.extend_selection(-1, window, cx);
}
fn on_extend_down(
&mut self,
_: &ExtendSelectionDown,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.extend_selection(1, window, cx);
}
fn extend_selection(&mut self, step: isize, window: &mut Window, cx: &mut Context<Self>) {
self.commit_editor(cx);
let rows = self.table.read(cx).delegate().rows();
if rows == 0 {
return;
}
let (row_ix, col_ix) = match self.table.read(cx).selected_cell() {
Some(cell) => cell,
None => (0, ResultDelegate::shown_column(0)),
};
let next = row_ix.saturating_add_signed(step).min(rows - 1);
self.table.update(cx, |table, cx| {
let delegate = table.delegate_mut();
if delegate.anchor.is_none() {
delegate.anchor_at(row_ix);
}
delegate.sweep_to(next, true);
table.set_selected_cell(next, col_ix, cx);
});
self.focus_table(window, cx);
}
pub fn view_cell(&mut self, row_ix: usize, col_ix: usize, cx: &mut Context<Self>) {
self.commit_editor(cx);
let delegate = self.table.read(cx).delegate();
let Some(column) = delegate.result.columns.get(col_ix).cloned() else {
return;
};
let type_name = delegate
.result
.column_types
.get(col_ix)
.cloned()
.unwrap_or_default();
let value = delegate.cell(row_ix, col_ix).clone();
let binary_preview_row = (query::is_placeholder(&value)
&& query::is_binary_type(&type_name))
.then(|| delegate.source(row_ix))
.flatten();
let editable = delegate.is_editable(row_ix, col_ix);
let note = if query::is_placeholder(&value) {
Some("This value was not read back from the server.")
} else if !editable {
Some("This value cannot be edited here.")
} else {
None
};
cx.spawn(async move |this, cx| {
let text = cx
.background_spawn(async move { format_value(&value, &type_name) })
.await;
this.update_in(cx, |_this, _window, cx| {
let save: Option<value_dialog::Save> = editable.then(|| {
let grid = cx.weak_entity();
let opened_with = text.clone();
Rc::new(move |typed: String, cx: &mut App| {
let Some(grid) = grid.upgrade() else {
return;
};
if typed == opened_with {
return;
}
grid.update(cx, |grid, cx| {
let value =
staged_value(typed, Settings::global(cx).coerce_null_literal);
grid.stage_cell(row_ix, col_ix, value, cx)
});
}) as value_dialog::Save
});
value_dialog::open(
ValueRequest {
column: column.clone().into(),
text,
note: note.map(Into::into),
save,
preview: None,
},
cx,
);
if let Some(row) = binary_preview_row {
cx.emit(BinaryPreviewRequested { row, column });
}
})
.ok();
})
.detach();
}
pub fn view_selected(&mut self, cx: &mut Context<Self>) {
if let Some((row_ix, col_ix)) = self.selected_cell(cx) {
self.view_cell(row_ix, col_ix, cx);
}
}
pub fn stage_cell(
&mut self,
row_ix: usize,
col_ix: usize,
value: Cell,
cx: &mut Context<Self>,
) {
self.table.update(cx, |table, cx| {
if !table.delegate().is_editable(row_ix, col_ix) {
return;
}
table.delegate_mut().stage(row_ix, col_ix, value);
cx.notify();
});
cx.emit(GridEdit::Staged);
}
pub fn delete_selected(&mut self, cx: &mut Context<Self>) {
self.set_selected_deleted(true, cx);
}
pub fn restore_selected(&mut self, cx: &mut Context<Self>) {
self.set_selected_deleted(false, cx);
}
fn set_selected_deleted(&mut self, deleted: bool, cx: &mut Context<Self>) {
self.table.update(cx, |table, cx| {
if !table.delegate().editable {
return;
}
table.delegate_mut().set_deleted(deleted);
table.refresh(cx);
});
cx.emit(GridEdit::Staged);
}
pub fn deletions(&self, cx: &App) -> Vec<usize> {
let mut rows: Vec<usize> = self
.table
.read(cx)
.delegate()
.deletions
.iter()
.copied()
.collect();
rows.sort_unstable();
rows
}
pub fn clear_row_selection(&mut self, cx: &mut Context<Self>) {
self.table.update(cx, |table, cx| {
let delegate = table.delegate_mut();
if delegate.rows_selected.is_empty() {
return;
}
delegate.rows_selected.clear();
delegate.anchor = None;
cx.notify();
});
}
pub fn staged(&self, cx: &App) -> Vec<StagedRow> {
let delegate = self.table.read(cx).delegate();
let mut rows: HashMap<usize, Vec<(usize, Cell)>> = HashMap::new();
for ((row, col), value) in &delegate.edits {
rows.entry(*row).or_default().push((*col, value.clone()));
}
let mut staged: Vec<StagedRow> = rows
.into_iter()
.map(|(row, mut cells)| {
cells.sort_by_key(|(col, _)| *col);
StagedRow { row, cells }
})
.collect();
staged.sort_by_key(|staged| staged.row);
staged
}
fn row_has_edits(&self, row: usize, cx: &App) -> bool {
self.table
.read(cx)
.delegate()
.edits
.keys()
.any(|(staged, _)| *staged == row)
}
pub fn baseline_row(&self, row: usize, cx: &App) -> Option<Vec<Cell>> {
self.table.read(cx).delegate().result.rows.get(row).cloned()
}
pub fn focused_row(&self, cx: &App) -> Option<usize> {
self.table.read(cx).delegate().focused_row
}
pub fn row_cells(&self, row_ix: usize, cx: &App) -> Option<Vec<Cell>> {
let delegate = self.table.read(cx).delegate();
if row_ix >= delegate.rows() {
return None;
}
Some(
(0..delegate.result.columns.len())
.map(|col_ix| delegate.cell(row_ix, col_ix).clone())
.collect(),
)
}
pub fn is_field_editable(&self, row_ix: usize, col_ix: usize, cx: &App) -> bool {
self.table.read(cx).delegate().is_editable(row_ix, col_ix)
}
pub fn is_field_staged(&self, row_ix: usize, col_ix: usize, cx: &App) -> bool {
self.table.read(cx).delegate().is_staged(row_ix, col_ix)
}
pub fn reset_cell(&mut self, row_ix: usize, col_ix: usize, cx: &mut Context<Self>) {
self.table.update(cx, |table, cx| {
let delegate = table.delegate_mut();
if let Some(draft) = delegate.draft(row_ix) {
delegate.drafts[draft].remove(&col_ix);
} else if let Some(source) = delegate.source(row_ix) {
delegate.edits.remove(&(source, col_ix));
}
cx.notify();
});
cx.emit(GridEdit::Staged);
}
pub fn row_status(&self, row_ix: usize, cx: &App) -> Option<RowStatus> {
let delegate = self.table.read(cx).delegate();
if row_ix >= delegate.rows() {
return None;
}
Some(if delegate.draft(row_ix).is_some() {
RowStatus::Draft
} else if delegate.is_deleted(row_ix) {
RowStatus::Deleted
} else {
RowStatus::Loaded
})
}
pub fn apply_staged(&mut self, rows: &[usize], cx: &mut Context<Self>) {
self.table.update(cx, |table, cx| {
let delegate = table.delegate_mut();
for row in rows {
let columns: Vec<usize> = delegate
.edits
.keys()
.filter(|(staged, _)| staged == row)
.map(|(_, col)| *col)
.collect();
for col in columns {
let Some(value) = delegate.edits.remove(&(*row, col)) else {
continue;
};
if let Some(cell) = delegate
.result
.rows
.get_mut(*row)
.and_then(|row| row.get_mut(col))
{
*cell = value;
}
}
}
cx.notify();
});
cx.emit(GridEdit::Staged);
}
pub fn set_sort_marker(&mut self, sort: Option<(String, ColumnSort)>, cx: &mut Context<Self>) {
self.table.update(cx, |table, cx| {
if table.delegate().sorted_by == sort {
return;
}
table.delegate_mut().sorted_by = sort;
cx.notify();
});
}
pub fn discard(&mut self, cx: &mut Context<Self>) {
self.table.update(cx, |table, cx| {
let delegate = table.delegate_mut();
if delegate.edits.is_empty()
&& delegate.editing.is_none()
&& delegate.drafts.is_empty()
&& delegate.deletions.is_empty()
{
return;
}
delegate.edits.clear();
delegate.editing = None;
delegate.drafts.clear();
delegate.deletions.clear();
table.refresh(cx);
});
cx.emit(GridEdit::Staged);
}
pub fn set_result(&mut self, result: QueryResult, cx: &mut Context<Self>) {
self.set_sorted_result(result, None, cx);
}
pub fn reveal_column(&mut self, name: &str, cx: &mut Context<Self>) -> bool {
let Some(data_ix) = self
.table
.read(cx)
.delegate()
.result
.columns
.iter()
.position(|column| column == name)
else {
return false;
};
let shown = ResultDelegate::shown_column(data_ix);
let has_row = !self.table.read(cx).delegate().result.rows.is_empty();
self.table.update(cx, |table, cx| {
if has_row {
table.set_selected_cell(0, shown, cx);
}
table.scroll_to_col(shown, cx);
});
true
}
pub fn set_sorted_result(
&mut self,
result: QueryResult,
sort: Option<(String, ColumnSort)>,
cx: &mut Context<Self>,
) {
self.has_result = true;
self.table.update(cx, |table, cx| {
let delegate = table.delegate_mut();
delegate.widths = measure_columns(&result);
delegate.order = (0..result.rows.len()).collect();
delegate.result = result;
delegate.focused_row = None;
delegate.sorted_by = sort;
delegate.edits.clear();
delegate.editing = None;
delegate.drafts.clear();
delegate.deletions.clear();
delegate.rows_selected.clear();
delegate.anchor = None;
delegate.sweeping = None;
table.clear_selection(cx);
table.refresh(cx);
});
cx.emit(GridEdit::RowFocused(None));
cx.notify();
}
pub fn clear(&mut self, cx: &mut Context<Self>) {
self.has_result = false;
self.table.update(cx, |table, cx| {
let delegate = table.delegate_mut();
delegate.result = QueryResult::default();
delegate.order = Vec::new();
delegate.widths = Vec::new();
delegate.focused_row = None;
delegate.sorted_by = None;
delegate.edits.clear();
delegate.editing = None;
delegate.drafts.clear();
delegate.deletions.clear();
delegate.rows_selected.clear();
delegate.anchor = None;
delegate.sweeping = None;
table.clear_selection(cx);
table.refresh(cx);
});
cx.emit(GridEdit::RowFocused(None));
cx.notify();
}
fn is_empty(&self, cx: &App) -> bool {
self.table.read(cx).delegate().result.columns.is_empty()
}
}
fn staged_value(text: String, coerce: bool) -> Cell {
if coerce && text.eq_ignore_ascii_case("null") {
None
} else {
Some(text)
}
}
impl Render for DataGrid {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
if self.is_empty(cx) {
let message = if self.has_result {
"Statement returned no columns"
} else {
"Run a query to see results"
};
return v_flex()
.size_full()
.items_center()
.justify_center()
.child(
div()
.text_sm()
.text_color(cx.theme().muted_foreground)
.child(message),
)
.into_any_element();
}
h_flex()
.size_full()
.id("grid")
.relative()
.key_context("DataGrid")
.on_action(cx.listener(Self::on_view_cell))
.on_action(cx.listener(Self::on_copy_value))
.on_action(cx.listener(Self::on_copy_with_headers))
.on_action(cx.listener(Self::on_select_all_rows))
.on_action(cx.listener(Self::on_clear_row_selection))
.on_action(cx.listener(Self::on_toggle_row))
.on_action(cx.listener(Self::on_extend_up))
.on_action(cx.listener(Self::on_extend_down))
.capture_any_mouse_up(cx.listener(|this, _: &MouseUpEvent, _window, cx| {
this.table.update(cx, |table, _cx| {
table.delegate_mut().sweeping = None;
});
}))
.context_menu(self.row_menu())
.child(
DataTable::new(&self.table)
.xsmall()
.stripe(Settings::global(cx).stripe_rows)
.bordered(false),
)
.into_any_element()
}
}