Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(tui): popup to list keybinds #9545

Merged
merged 14 commits into from
Dec 11, 2024
17 changes: 16 additions & 1 deletion crates/turborepo-ui/src/tui/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use std::{
use ratatui::{
backend::{Backend, CrosstermBackend},
layout::{Constraint, Layout},
widgets::TableState,
widgets::{Clear, TableState},
Frame, Terminal,
};
use tokio::{
Expand All @@ -17,6 +17,8 @@ use tokio::{
};
use tracing::{debug, trace};

use crate::tui::popup::{popup, popup_area};

pub const FRAMERATE: Duration = Duration::from_millis(3);
const RESIZE_DEBOUNCE_DELAY: Duration = Duration::from_millis(10);

Expand Down Expand Up @@ -53,6 +55,7 @@ pub struct App<W> {
selected_task_index: usize,
is_task_selection_pinned: bool,
has_sidebar: bool,
showing_help_popup: bool,
done: bool,
}

Expand Down Expand Up @@ -97,6 +100,7 @@ impl<W> App<W> {
task_list_scroll: TableState::default().with_selected(selected_task_index),
selected_task_index,
has_sidebar: true,
showing_help_popup: false,
is_task_selection_pinned: has_user_interacted,
}
}
Expand All @@ -118,6 +122,7 @@ impl<W> App<W> {
Ok(InputOptions {
focus: &self.section_focus,
has_selection,
is_help_popup_open: self.showing_help_popup,
})
}

Expand Down Expand Up @@ -790,6 +795,9 @@ fn update(
Event::ToggleSidebar => {
app.has_sidebar = !app.has_sidebar;
}
Event::ToggleHelpPopup => {
app.showing_help_popup = !app.showing_help_popup;
}
Event::Input { bytes } => {
app.forward_input(&bytes)?;
}
Expand Down Expand Up @@ -862,6 +870,13 @@ fn view<W>(app: &mut App<W>, f: &mut Frame) {

f.render_stateful_widget(&table_to_render, table, &mut app.task_list_scroll);
f.render_widget(&pane_to_render, pane);

if app.showing_help_popup {
let area = popup_area(*f.buffer_mut().area());
let area = area.intersection(*f.buffer_mut().area());
f.render_widget(Clear, area); // Clears background underneath popup
f.render_widget(popup(area), area);
}
}

#[cfg(test)]
Expand Down
1 change: 1 addition & 0 deletions crates/turborepo-ui/src/tui/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ pub enum Event {
cols: u16,
},
ToggleSidebar,
ToggleHelpPopup,
SearchEnter,
SearchExit {
restore_scroll: bool,
Expand Down
4 changes: 4 additions & 0 deletions crates/turborepo-ui/src/tui/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use super::{
pub struct InputOptions<'a> {
pub focus: &'a LayoutSections,
pub has_selection: bool,
pub is_help_popup_open: bool,
}

pub fn start_crossterm_stream(tx: mpsc::Sender<crossterm::event::Event>) -> Option<JoinHandle<()>> {
Expand Down Expand Up @@ -80,6 +81,7 @@ fn translate_key_event(options: InputOptions, key_event: KeyEvent) -> Option<Eve
KeyCode::Char('/') if matches!(options.focus, LayoutSections::TaskList) => {
Some(Event::SearchEnter)
}
KeyCode::Esc if options.is_help_popup_open => Some(Event::ToggleHelpPopup),
KeyCode::Esc if matches!(options.focus, LayoutSections::Search { .. }) => {
Some(Event::SearchExit {
restore_scroll: true,
Expand Down Expand Up @@ -112,6 +114,7 @@ fn translate_key_event(options: InputOptions, key_event: KeyEvent) -> Option<Eve
KeyCode::Char('n') if key_event.modifiers == KeyModifiers::CONTROL => {
Some(Event::ScrollDown)
}
KeyCode::Char('m') => Some(Event::ToggleHelpPopup),
KeyCode::Up | KeyCode::Char('k') => Some(Event::Up),
KeyCode::Down | KeyCode::Char('j') => Some(Event::Down),
KeyCode::Enter | KeyCode::Char('i') => Some(Event::EnterInteractive),
Expand Down Expand Up @@ -443,6 +446,7 @@ mod test {
InputOptions {
focus: search(),
has_selection: false,
is_help_popup_open: false,
}
}

Expand Down
1 change: 1 addition & 0 deletions crates/turborepo-ui/src/tui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pub mod event;
mod handle;
mod input;
mod pane;
mod popup;
mod search;
mod size;
mod spinner;
Expand Down
80 changes: 80 additions & 0 deletions crates/turborepo-ui/src/tui/popup.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
use std::cmp::min;

use ratatui::{
layout::{Constraint, Flex, Layout, Rect},
text::Line,
widgets::{Block, List, ListItem, Padding},
};

const BIND_LIST: [&str; 11] = [
"m - Toggle this help popup",
"↑ or j - Select previous task",
"↓ or k - Select next task",
"h - Toggle task list",
"/ - Filter tasks to search term",
"ESC - Clear filter",
"i - Interact with task",
"Ctrl+z - Stop interacting with task",
"c - Copy logs selection (Only when logs are selected)",
"Ctrl+n - Scroll logs up",
"Ctrl+p - Scroll logs down",
];

pub fn popup_area(area: Rect) -> Rect {
let screen_width = area.width;
let screen_height = area.height;

let popup_width = BIND_LIST
.iter()
.map(|s| s.len().saturating_add(4))
.max()
.unwrap_or(0) as u16;
let popup_height = min((BIND_LIST.len().saturating_add(4)) as u16, screen_height);

let x = screen_width.saturating_sub(popup_width) / 2;
let y = screen_height.saturating_sub(popup_height) / 2;

let vertical = Layout::vertical([Constraint::Percentage(100)]).flex(Flex::Center);
let horizontal = Layout::horizontal([Constraint::Percentage(100)]).flex(Flex::Center);

let [vertical_area] = vertical.areas(Rect {
x,
y,
width: popup_width,
height: popup_height,
});

let [area] = horizontal.areas(vertical_area);

area
}

pub fn popup(area: Rect) -> List<'static> {
let available_height = area.height.saturating_sub(4) as usize;

let items: Vec<ListItem> = BIND_LIST
.iter()
.take(available_height)
.map(|item| ListItem::new(Line::from(*item)))
.collect();

let title_bottom = if available_height < BIND_LIST.len() {
let binds_not_visible = BIND_LIST.len().saturating_sub(available_height);

let pluralize = if binds_not_visible > 1 { "s" } else { "" };
let message = format!(
" {} more bind{}. Make your terminal taller. ",
binds_not_visible, pluralize
);
Line::from(message)
} else {
Line::from("")
};

let outer = Block::bordered()
.title(" Keybinds ")
.title_bottom(title_bottom.to_string())
.padding(Padding::uniform(1));

List::new(items).block(outer)
}
6 changes: 3 additions & 3 deletions crates/turborepo-ui/src/tui/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ pub struct TaskTable<'b> {
spinner: SpinnerState,
}

const TASK_NAVIGATE_INSTRUCTIONS: &str = "↑ ↓ to navigate";
const HIDE_INSTRUCTIONS: &str = "h to hide";
const TASK_NAVIGATE_INSTRUCTIONS: &str = "↑ ↓ - Select";
const MORE_BINDS_INSTRUCTIONS: &str = "m - More binds";

impl<'b> TaskTable<'b> {
/// Construct a new table with all of the planned tasks
Expand Down Expand Up @@ -122,7 +122,7 @@ impl<'a> StatefulWidget for &'a TaskTable<'a> {
)
.footer(
vec![Text::styled(
format!("{TASK_NAVIGATE_INSTRUCTIONS}\n{HIDE_INSTRUCTIONS}"),
format!("{TASK_NAVIGATE_INSTRUCTIONS}\n{MORE_BINDS_INSTRUCTIONS}"),
Style::default().add_modifier(Modifier::DIM),
)]
.into_iter()
Expand Down
Loading