Skip to content

Commit 4c13f8e

Browse files
authored
feat: Added scrollable capabilities during the Shell-Cell session preparation step (#89)
1 parent 447b5f6 commit 4c13f8e

File tree

2 files changed

+40
-32
lines changed

2 files changed

+40
-32
lines changed

src/cli/run/app/mod.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use ratatui::{
1212
};
1313
use terminput::Encoding;
1414
use terminput_crossterm::to_terminput;
15+
use tui_scrollview::ScrollViewState;
1516

1617
use crate::{
1718
buildkit::BuildKitD,
@@ -98,6 +99,16 @@ impl App {
9899
{
99100
// Exit on Ctrl-C or Ctrl-D for other states
100101
self = App::Exit;
102+
} else if let Self::Preparing(ref mut state) = self {
103+
match key.code {
104+
KeyCode::Down | KeyCode::Char('j') => {
105+
state.scroll_down();
106+
},
107+
KeyCode::Up | KeyCode::Char('k') => {
108+
state.scroll_up();
109+
},
110+
_ => {},
111+
}
101112
}
102113
}
103114

@@ -181,6 +192,7 @@ impl App {
181192
rx,
182193
logs_rx,
183194
logs: Vec::new(),
195+
scroll_view_state: ScrollViewState::new(),
184196
})
185197
}
186198

@@ -201,6 +213,7 @@ pub struct PreparingState {
201213
rx: Receiver<color_eyre::Result<(Pty, SCell)>>,
202214
logs_rx: Receiver<(String, LogType)>,
203215
logs: Vec<(String, LogType)>,
216+
scroll_view_state: ScrollViewState,
204217
}
205218

206219
#[derive(Debug, Clone, Copy)]
@@ -216,12 +229,21 @@ impl PreparingState {
216229
match self.logs_rx.recv_timeout(MIN_FPS) {
217230
Ok(log) => {
218231
self.logs.push(log);
232+
self.scroll_view_state.scroll_to_bottom();
219233
false
220234
},
221235
Err(RecvTimeoutError::Timeout) => false,
222236
Err(RecvTimeoutError::Disconnected) => true,
223237
}
224238
}
239+
240+
fn scroll_up(&mut self) {
241+
self.scroll_view_state.scroll_up();
242+
}
243+
244+
fn scroll_down(&mut self) {
245+
self.scroll_view_state.scroll_down();
246+
}
225247
}
226248

227249
pub struct RunningPtyState {

src/cli/run/app/ui.rs

Lines changed: 18 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
use itertools::Itertools;
22
use ratatui::{
3-
layout::{Alignment, Constraint, Layout},
3+
layout::{Alignment, Constraint, Layout, Rect, Size},
44
style::{Modifier, Style},
55
text::{Line, Span},
6-
widgets::{Block, Borders, List, ListItem, Paragraph, Widget},
6+
widgets::{Block, Borders, List, ListItem, Paragraph, StatefulWidget, Widget},
77
};
8+
use tui_scrollview::{ScrollView, ScrollbarVisibility};
89

910
use crate::{
1011
cli::run::app::{App, LogType},
1112
pty::Pty,
1213
};
1314

1415
impl Widget for &mut App {
16+
#[allow(clippy::indexing_slicing)]
1517
fn render(
1618
self,
1719
area: ratatui::prelude::Rect,
@@ -28,7 +30,7 @@ impl Widget for &mut App {
2830
.title("'Shell-Cell'")
2931
.title_bottom("Ctrl-C or Ctrl-D: exit");
3032
let inner = block.inner(area);
31-
Widget::render(block, area, buf);
33+
block.render(area, buf);
3234

3335
let area_width = inner.width as usize;
3436

@@ -55,9 +57,8 @@ impl Widget for &mut App {
5557
})
5658
.collect::<Vec<_>>();
5759
let logs_len = logs.len();
58-
// Calculate how many log items can fit in the available height
59-
let available_height = inner.height as usize;
60-
let skip_amount = logs_len.saturating_sub(available_height);
60+
let logs_height = u16::try_from(logs_len).unwrap_or(u16::MAX);
61+
let skip_amount = logs_len.saturating_sub(logs_height.into());
6162

6263
let logs = logs
6364
.iter()
@@ -84,18 +85,23 @@ impl Widget for &mut App {
8485
})
8586
.skip(skip_amount);
8687

87-
Widget::render(List::new(logs), inner, buf);
88+
let mut scroll_view = ScrollView::new(Size::new(inner.width, logs_height))
89+
.vertical_scrollbar_visibility(ScrollbarVisibility::Automatic)
90+
.horizontal_scrollbar_visibility(ScrollbarVisibility::Never);
91+
92+
scroll_view.render_widget(List::new(logs), Rect::new(0, 0, inner.width, logs_height));
93+
scroll_view.render(inner, buf, &mut state.scroll_view_state);
8894
} else if let App::RunningPty(state) = self {
8995
let block = block
9096
.title(format!("'Shell-Cell' {}", state.scell_name))
9197
.title_bottom("Ctrl-D: exit");
9298
let inner = block.inner(area);
93-
Widget::render(block, area, buf);
94-
Widget::render(&mut state.pty, inner, buf);
99+
block.render(area, buf);
100+
state.pty.render(inner, buf);
95101
} else if let App::Finished = self {
96102
let block = block.title("'Shell-Cell'");
97103
let inner = block.inner(area);
98-
Widget::render(block, area, buf);
104+
block.render(area, buf);
99105

100106
// Create a centered area using Layout
101107
let vertical_layout = Layout::vertical([
@@ -117,8 +123,7 @@ impl Widget for &mut App {
117123
];
118124

119125
let paragraph = Paragraph::new(text).alignment(Alignment::Center);
120-
#[allow(clippy::indexing_slicing)]
121-
Widget::render(paragraph, vertical_layout[1], buf);
126+
paragraph.render(vertical_layout[1], buf);
122127
}
123128
}
124129
}
@@ -133,25 +138,6 @@ impl Widget for &mut Pty {
133138
{
134139
// set the proper size for the terminal screen
135140
self.set_size(area.height, area.width);
136-
Widget::render(
137-
tui_term::widget::PseudoTerminal::new(self.screen()),
138-
area,
139-
buf,
140-
);
141+
tui_term::widget::PseudoTerminal::new(self.screen()).render(area, buf);
141142
}
142143
}
143-
144-
fn logs_list_iter<'a>(messages: &'a [&str]) -> impl Iterator<Item = ListItem<'a>> {
145-
messages.iter().enumerate().map(|(i, msg)| {
146-
let is_last = i == messages.len().saturating_sub(1) && i != 0;
147-
148-
let style = Style::default().add_modifier(Modifier::BOLD);
149-
150-
let line = if is_last {
151-
Line::from(vec![Span::styled(format!("{msg} ..."), style.yellow())])
152-
} else {
153-
Line::from(vec![Span::styled(format!("✓ {msg}"), style.green())])
154-
};
155-
ListItem::new(line)
156-
})
157-
}

0 commit comments

Comments
 (0)