Skip to content

Commit b79d64b

Browse files
authored
Vx browse supports rendering visual segment map of the file (#3195)
Renders 2d grid based on column names from struct layout https://github.com/user-attachments/assets/b84797d0-9e7a-4e08-924e-13e794b236b4
1 parent de47414 commit b79d64b

File tree

8 files changed

+574
-45
lines changed

8 files changed

+574
-45
lines changed

Cargo.lock

Lines changed: 28 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ simplelog = "0.12"
155155
sketches-ddsketch = "0.3.0"
156156
static_assertions = "1.1"
157157
tabled = { version = "0.19.0", default-features = false }
158+
taffy = "0.8.0"
158159
tar = "0.4"
159160
tempfile = "3"
160161
thiserror = "2.0.3"

vortex-tui/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@ version = { workspace = true }
1515

1616
[dependencies]
1717
anyhow = { workspace = true }
18-
async-trait = { workspace = true }
1918
clap = { workspace = true, features = ["derive"] }
2019
crossterm = { workspace = true }
2120
futures-util = { workspace = true }
2221
humansize = { workspace = true }
2322
indicatif = { workspace = true, features = ["futures"] }
2423
parquet = { workspace = true, features = ["arrow", "async"] }
2524
ratatui = { workspace = true }
25+
taffy = { workspace = true }
2626
tokio = { workspace = true, features = ["rt-multi-thread"] }
2727
vortex = { workspace = true, features = ["tokio", "parquet"] }
2828
vortex-layout = { workspace = true }

vortex-tui/src/browse/app.rs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::path::Path;
22
use std::sync::Arc;
33

4+
use ratatui::prelude::Size;
45
use ratatui::widgets::ListState;
56
use vortex::dtype::DType;
67
use vortex::error::{VortexExpect, VortexResult, VortexUnwrap, vortex_panic};
@@ -20,13 +21,16 @@ use vortex_layout::{
2021
STATS_LAYOUT_ID, STRUCT_LAYOUT_ID,
2122
};
2223

24+
use crate::browse::ui::SegmentGridState;
25+
2326
#[derive(Default, Copy, Clone, Eq, PartialEq)]
2427
pub enum Tab {
2528
/// The layout tree browser.
2629
#[default]
2730
Layout,
28-
/// The encoding tree viewer
29-
Encodings,
31+
32+
/// Show a segment map of the file
33+
Segments,
3034
// TODO(aduffy): SQL query page powered by DF
3135
// Query,
3236
}
@@ -207,7 +211,7 @@ pub enum KeyMode {
207211
/// State saved across all Tabs.
208212
///
209213
/// Holding them all allows us to switch between tabs without resetting view state.
210-
pub struct AppState {
214+
pub struct AppState<'a> {
211215
pub key_mode: KeyMode,
212216
pub search_filter: String,
213217
pub filter: Option<Vec<bool>>,
@@ -218,17 +222,19 @@ pub struct AppState {
218222

219223
/// List state for the Layouts view
220224
pub layouts_list_state: ListState,
225+
pub segment_grid_state: SegmentGridState<'a>,
226+
pub frame_size: Size,
221227
}
222228

223-
impl AppState {
229+
impl AppState<'_> {
224230
pub fn clear_search(&mut self) {
225231
self.search_filter.clear();
226232
self.filter.take();
227233
}
228234
}
229235

230236
/// Create an app backed from a file path.
231-
pub async fn create_file_app(path: impl AsRef<Path>) -> VortexResult<AppState> {
237+
pub async fn create_file_app<'a>(path: impl AsRef<Path>) -> VortexResult<AppState<'a>> {
232238
let vxf = VortexOpenOptions::file().open(path).await?;
233239

234240
let cursor = LayoutCursor::new(vxf.footer().clone());
@@ -241,6 +247,8 @@ pub async fn create_file_app(path: impl AsRef<Path>) -> VortexResult<AppState> {
241247
filter: None,
242248
current_tab: Tab::default(),
243249
layouts_list_state: ListState::default().with_selected(Some(0)),
250+
segment_grid_state: SegmentGridState::default(),
251+
frame_size: Size::new(0, 0),
244252
})
245253
}
246254

vortex-tui/src/browse/mod.rs

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -48,42 +48,46 @@ fn handle_normal_mode(app: &mut AppState, event: Event) -> HandleResult {
4848
(KeyCode::Tab, _) => {
4949
// toggle between tabs
5050
app.current_tab = match app.current_tab {
51-
Tab::Layout => Tab::Encodings,
52-
Tab::Encodings => Tab::Layout,
51+
Tab::Layout => Tab::Segments,
52+
Tab::Segments => Tab::Layout,
5353
};
5454
}
5555
(KeyCode::Up | KeyCode::Char('k'), _)
5656
| (KeyCode::Char('p'), KeyModifiers::CONTROL) => {
5757
// We send the key-up to the list state if we're looking at
5858
// the Layouts tab.
59-
if app.current_tab == Tab::Layout {
60-
app.layouts_list_state.scroll_up_by(1);
59+
match app.current_tab {
60+
Tab::Layout => app.layouts_list_state.select_previous(),
61+
Tab::Segments => app.segment_grid_state.scroll_up(10),
6162
}
6263
}
6364
(KeyCode::Down | KeyCode::Char('j'), _)
64-
| (KeyCode::Char('n'), KeyModifiers::CONTROL) => {
65-
if app.current_tab == Tab::Layout {
66-
app.layouts_list_state.scroll_down_by(1);
67-
}
68-
}
65+
| (KeyCode::Char('n'), KeyModifiers::CONTROL) => match app.current_tab {
66+
Tab::Layout => app.layouts_list_state.select_next(),
67+
Tab::Segments => app.segment_grid_state.scroll_down(10),
68+
},
6969
(KeyCode::PageUp, _) | (KeyCode::Char('v'), KeyModifiers::ALT) => {
70-
if app.current_tab == Tab::Layout {
71-
app.layouts_list_state.scroll_up_by(10);
70+
match app.current_tab {
71+
Tab::Layout => app.layouts_list_state.scroll_up_by(10),
72+
Tab::Segments => app.segment_grid_state.scroll_up(100),
7273
}
7374
}
7475
(KeyCode::PageDown, _) | (KeyCode::Char('v'), KeyModifiers::CONTROL) => {
75-
if app.current_tab == Tab::Layout {
76-
app.layouts_list_state.scroll_down_by(10);
76+
match app.current_tab {
77+
Tab::Layout => app.layouts_list_state.scroll_down_by(10),
78+
Tab::Segments => app.segment_grid_state.scroll_down(100),
7779
}
7880
}
7981
(KeyCode::Home, _) | (KeyCode::Char('<'), KeyModifiers::ALT) => {
80-
if app.current_tab == Tab::Layout {
81-
app.layouts_list_state.select_first();
82+
match app.current_tab {
83+
Tab::Layout => app.layouts_list_state.select_first(),
84+
Tab::Segments => app.segment_grid_state.scroll_left(200),
8285
}
8386
}
8487
(KeyCode::End, _) | (KeyCode::Char('>'), KeyModifiers::ALT) => {
85-
if app.current_tab == Tab::Layout {
86-
app.layouts_list_state.select_last();
88+
match app.current_tab {
89+
Tab::Layout => app.layouts_list_state.select_last(),
90+
Tab::Segments => app.segment_grid_state.scroll_right(200),
8791
}
8892
}
8993
(KeyCode::Enter, _) => {
@@ -98,13 +102,21 @@ fn handle_normal_mode(app: &mut AppState, event: Event) -> HandleResult {
98102
}
99103
(KeyCode::Left | KeyCode::Char('h'), _)
100104
| (KeyCode::Char('b'), KeyModifiers::CONTROL) => {
101-
if app.current_tab == Tab::Layout {
102-
// Ascend back up to the Parent node
103-
app.cursor = app.cursor.parent();
104-
// Reset the list scroll state.
105-
app.layouts_list_state = ListState::default().with_selected(Some(0));
105+
match app.current_tab {
106+
Tab::Layout => {
107+
// Ascend back up to the Parent node
108+
app.cursor = app.cursor.parent();
109+
// Reset the list scroll state.
110+
app.layouts_list_state = ListState::default().with_selected(Some(0));
111+
}
112+
Tab::Segments => app.segment_grid_state.scroll_left(20),
106113
}
107114
}
115+
(KeyCode::Right | KeyCode::Char('l'), _)
116+
| (KeyCode::Char('b'), KeyModifiers::ALT) => match app.current_tab {
117+
Tab::Layout => {}
118+
Tab::Segments => app.segment_grid_state.scroll_right(20),
119+
},
108120

109121
(KeyCode::Char('/'), _) | (KeyCode::Char('s'), KeyModifiers::CONTROL) => {
110122
app.key_mode = KeyMode::Search;

vortex-tui/src/browse/ui/encodings.rs

Lines changed: 0 additions & 8 deletions
This file was deleted.
Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
mod encodings;
21
mod layouts;
2+
mod segments;
33

4-
use encodings::encodings_ui;
54
use layouts::render_layouts;
65
use ratatui::prelude::*;
76
use ratatui::widgets::{Block, BorderType, Borders, Tabs};
7+
pub use segments::SegmentGridState;
88

99
use super::app::{AppState, KeyMode, Tab};
10+
use crate::browse::ui::segments::segments_ui;
1011

1112
pub fn render_app(app: &mut AppState, frame: &mut Frame) {
1213
// Render the outer tab view, then render the inner frame view.
@@ -26,7 +27,7 @@ pub fn render_app(app: &mut AppState, frame: &mut Frame) {
2627
.borders(Borders::ALL)
2728
.border_type(BorderType::Rounded)
2829
.border_style(Style::default().magenta())
29-
.title_top("vx-browse")
30+
.title_top("Vortex Browser")
3031
.title_bottom(bottom_text)
3132
.title_alignment(Alignment::Center);
3233

@@ -47,12 +48,12 @@ pub fn render_app(app: &mut AppState, frame: &mut Frame) {
4748
// Display a tab indicator.
4849
let selected_tab = match app.current_tab {
4950
Tab::Layout => 0,
50-
Tab::Encodings => 1,
51+
Tab::Segments => 1,
5152
};
5253

5354
let tabs = Tabs::new([
5455
"File Layout",
55-
"Arrays",
56+
"Segments",
5657
// TODO(aduffy): add SQL query interface
5758
// "Query",
5859
])
@@ -67,8 +68,6 @@ pub fn render_app(app: &mut AppState, frame: &mut Frame) {
6768
Tab::Layout => {
6869
render_layouts(app, app_view, frame.buffer_mut());
6970
}
70-
Tab::Encodings => {
71-
frame.render_widget(encodings_ui(app), app_view);
72-
}
71+
Tab::Segments => segments_ui(app, app_view, frame.buffer_mut()),
7372
}
7473
}

0 commit comments

Comments
 (0)