summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMichael <31220038+MichaelAug@users.noreply.github.com>2024-03-24 19:57:59 +0000
committerGitHub <noreply@github.com>2024-03-24 12:57:59 -0700
commit5131aba138e300ba8d7f3836d67507801d1902d8 (patch)
tree0f3012c6c1bc284042850b10e56e4312bfd6836f
parent540a95c160a12b7ebef31b26ddea6a7a94ef996a (diff)
Make MsgPopup scrollable (#2120)
-rw-r--r--CHANGELOG.md1
-rw-r--r--src/popups/msg.rs120
2 files changed, 92 insertions, 29 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6c2a64b6..e2d50e5b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
* re-enable clippy `missing_const_for_fn` linter warning and added const to functions where applicable ([#2116](https://github.com/extrawurst/gitui/issues/2116))
+* Make info and error message popups scrollable [[@MichaelAug](https://github.com/MichaelAug)] ([#1138](https://github.com/extrawurst/gitui/issues/1138))
## [0.25.1] - 2024-02-23
diff --git a/src/popups/msg.rs b/src/popups/msg.rs
index 6da957ce..759c3b26 100644
--- a/src/popups/msg.rs
+++ b/src/popups/msg.rs
@@ -1,13 +1,16 @@
use crate::components::{
visibility_blocking, CommandBlocking, CommandInfo, Component,
- DrawableComponent, EventState,
+ DrawableComponent, EventState, ScrollType, VerticalScroll,
};
+use crate::strings::order;
use crate::{
app::Environment,
keys::{key_match, SharedKeyConfig},
strings, ui,
};
+use anyhow::Result;
use crossterm::event::Event;
+use ratatui::text::Line;
use ratatui::{
layout::{Alignment, Rect},
text::Span,
@@ -22,9 +25,12 @@ pub struct MsgPopup {
visible: bool,
theme: SharedTheme,
key_config: SharedKeyConfig,
+ scroll: VerticalScroll,
}
-use anyhow::Result;
+const POPUP_HEIGHT: u16 = 25;
+const BORDER_WIDTH: u16 = 2;
+const MINIMUM_WIDTH: u16 = 60;
impl DrawableComponent for MsgPopup {
fn draw(&self, f: &mut Frame, _rect: Rect) -> Result<()> {
@@ -32,29 +38,55 @@ impl DrawableComponent for MsgPopup {
return Ok(());
}
+ let max_width = f.size().width.max(MINIMUM_WIDTH);
+
// determine the maximum width of text block
- let lens = self
+ let width = self
.msg
- .split('\n')
+ .lines()
.map(str::len)
- .collect::<Vec<usize>>();
- let mut max = lens.iter().max().expect("max") + 2;
- if max > std::u16::MAX as usize {
- max = std::u16::MAX as usize;
- }
- let mut width = u16::try_from(max)
- .expect("can't fail due to check above");
- // dont overflow screen, and dont get too narrow
- if width > f.size().width {
- width = f.size().width;
- } else if width < 60 {
- width = 60;
- }
+ .max()
+ .unwrap_or(0)
+ .saturating_add(BORDER_WIDTH.into())
+ .clamp(MINIMUM_WIDTH.into(), max_width.into())
+ .try_into()
+ .expect("can't fail because we're clamping to u16 value");
+
+ let area =
+ ui::centered_rect_absolute(width, POPUP_HEIGHT, f.size());
+
+ // Wrap lines and break words if there is not enough space
+ let wrapped_msg = bwrap::wrap_maybrk!(
+ &self.msg,
+ area.width.saturating_sub(BORDER_WIDTH).into()
+ );
+
+ let msg_lines: Vec<String> =
+ wrapped_msg.lines().map(String::from).collect();
+ let line_num = msg_lines.len();
+
+ let height = POPUP_HEIGHT
+ .saturating_sub(BORDER_WIDTH)
+ .min(f.size().height.saturating_sub(BORDER_WIDTH));
+
+ let top =
+ self.scroll.update_no_selection(line_num, height.into());
+
+ let scrolled_lines = msg_lines
+ .iter()
+ .skip(top)
+ .take(height.into())
+ .map(|line| {
+ Line::from(vec![Span::styled(
+ line.clone(),
+ self.theme.text(true, false),
+ )])
+ })
+ .collect::<Vec<Line>>();
- let area = ui::centered_rect_absolute(width, 25, f.size());
f.render_widget(Clear, area);
f.render_widget(
- Paragraph::new(self.msg.clone())
+ Paragraph::new(scrolled_lines)
.block(
Block::default()
.title(Span::styled(
@@ -69,6 +101,8 @@ impl DrawableComponent for MsgPopup {
area,
);
+ self.scroll.draw(f, area, &self.theme);
+
Ok(())
}
}
@@ -84,6 +118,16 @@ impl Component for MsgPopup {
true,
self.visible,
));
+ out.push(
+ CommandInfo::new(
+ strings::commands::navigate_commit_message(
+ &self.key_config,
+ ),
+ true,
+ self.visible,
+ )
+ .order(order::NAV),
+ );
visibility_blocking(self)
}
@@ -93,6 +137,14 @@ impl Component for MsgPopup {
if let Event::Key(e) = ev {
if key_match(e, self.key_config.keys.enter) {
self.hide();
+ } else if key_match(
+ e,
+ self.key_config.keys.popup_down,
+ ) {
+ self.scroll.move_top(ScrollType::Down);
+ } else if key_match(e, self.key_config.keys.popup_up)
+ {
+ self.scroll.move_top(ScrollType::Up);
}
}
Ok(EventState::Consumed)
@@ -124,24 +176,34 @@ impl MsgPopup {
visible: false,
theme: env.theme.clone(),
key_config: env.key_config.clone(),
+ scroll: VerticalScroll::new(),
}
}
- ///
- pub fn show_error(&mut self, msg: &str) -> Result<()> {
- self.title = strings::msg_title_error(&self.key_config);
+ fn set_new_msg(
+ &mut self,
+ msg: &str,
+ title: String,
+ ) -> Result<()> {
+ self.title = title;
self.msg = msg.to_string();
- self.show()?;
+ self.scroll.reset();
+ self.show()
+ }
- Ok(())
+ ///
+ pub fn show_error(&mut self, msg: &str) -> Result<()> {
+ self.set_new_msg(
+ msg,
+ strings::msg_title_error(&self.key_config),
+ )
}
///
pub fn show_info(&mut self, msg: &str) -> Result<()> {
- self.title = strings::msg_title_info(&self.key_config);
- self.msg = msg.to_string();
- self.show()?;
-
- Ok(())
+ self.set_new_msg(
+ msg,
+ strings::msg_title_info(&self.key_config),
+ )
}
}