Skip to content

Commit 70345db

Browse files
authored
Improve notifications (#48)
- Align to the right - Show countdown block - Show notification when listening for messages - Do not panic if Connection event cannot be sent
1 parent 599023d commit 70345db

File tree

9 files changed

+107
-36
lines changed

9 files changed

+107
-36
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CHANGELOG
44
main
55
----
66

7+
- Improve notification display
78
- Do not accept connections while an existing session is running
89
- Filter properties with dot notation in context pane (`f`)
910
- Stack traversal - select stack and inspect stack frames in current mode.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ Debug-TUI
33

44
Interactive [Xdebug](https://xdebug.org) step-debugging client your terminal.
55

6-
![Image](https://github.com/user-attachments/assets/21627682-b2f1-4622-b67d-ff6cd32e4363)
6+
![Demo](https://github.com/user-attachments/assets/1a8a1d1b-d01b-4d71-9d35-c65e546e8c24)
77

88
- **Travel forwards**: step over, into and out.
99
- **Travel backwards**: it's not quite time travel, but you can revisit

src/app.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use anyhow::Result;
2222
use crossterm::event::KeyCode;
2323
use log::info;
2424
use log::warn;
25+
use log::error;
2526
use ratatui::layout::Rect;
2627
use ratatui::prelude::CrosstermBackend;
2728
use ratatui::style::Color;
@@ -190,6 +191,7 @@ impl ListenStatus {
190191
}
191192

192193
pub struct App {
194+
tick: u8,
193195
receiver: Receiver<AppEvent>,
194196
quit: bool,
195197
sender: Sender<AppEvent>,
@@ -226,6 +228,7 @@ impl App {
226228
pub fn new(config: Config, receiver: Receiver<AppEvent>, sender: Sender<AppEvent>) -> App {
227229
let client = Arc::new(Mutex::new(DbgpClient::new(None)));
228230
App {
231+
tick: 0,
229232
listening_status: ListenStatus::Listening,
230233
config,
231234
input_plurality: vec![],
@@ -281,7 +284,13 @@ impl App {
281284
loop {
282285
match listener.accept().await {
283286
Ok(s) => {
284-
sender.send(AppEvent::ClientConnected(s.0)).await.unwrap();
287+
match sender.send(AppEvent::ClientConnected(s.0)).await {
288+
Ok(_) => (),
289+
Err(e) => error!(
290+
"Could not send connection event: {}",
291+
e.to_string()
292+
),
293+
}
285294
}
286295
Err(_) => panic!("Could not connect"),
287296
}
@@ -321,7 +330,9 @@ impl App {
321330
event: AppEvent,
322331
) -> Result<()> {
323332
match event {
324-
AppEvent::Tick => (),
333+
AppEvent::Tick => {
334+
self.tick = self.tick.wrapping_add(1);
335+
},
325336
_ => info!("Handling event {:?}", event),
326337
};
327338
match event {
@@ -382,6 +393,7 @@ impl App {
382393
if self.listening_status != ListenStatus::Listening {
383394
self.notification = Notification::warning("refused incoming connection".to_string());
384395
} else {
396+
self.notification = Notification::info("connected".to_string());
385397
let filepath = {
386398
let mut client = self.client.lock().await;
387399
let response = client.deref_mut().connect(s).await?;

src/config.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,14 @@ impl Config {
3434
Config { listen , log_path: None}
3535
}
3636
}
37+
38+
#[cfg(test)]
39+
mod test {
40+
use crate::notification::Notification;
41+
42+
#[test]
43+
fn test_countdown_char() -> () {
44+
let notification = Notification::info("Hello".to_string());
45+
notification.countdown_char();
46+
}
47+
}

src/notification.rs

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ pub struct Notification {
1313
expires: SystemTime,
1414
}
1515
impl Notification {
16+
const DURATION: u64 = 5;
17+
const BLOCKS: [char; 8] = ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'];
18+
1619
pub(crate) fn none() -> Notification {
1720
Notification {
1821
message: "".to_string(),
@@ -31,6 +34,17 @@ impl Notification {
3134
}
3235
}
3336

37+
pub fn countdown_char(&self) -> char {
38+
match self.expires.duration_since(SystemTime::now()) {
39+
Ok(duration) => {
40+
let pct = duration.as_secs_f64() / Self::DURATION as f64;
41+
let block_offset = (8.0 * pct).ceil();
42+
Self::BLOCKS[block_offset as usize - 1]
43+
}
44+
Err(_) => ' ',
45+
}
46+
}
47+
3448
pub fn is_visible(&self) -> bool {
3549
SystemTime::now() < self.expires
3650
}
@@ -41,7 +55,7 @@ impl Notification {
4155
message,
4256
level: NotificationLevel::Info,
4357
expires: SystemTime::now()
44-
.checked_add(Duration::from_secs(5))
58+
.checked_add(Duration::from_secs(Self::DURATION))
4559
.unwrap(),
4660
}
4761
}
@@ -52,8 +66,34 @@ impl Notification {
5266
message,
5367
level: NotificationLevel::Warning,
5468
expires: SystemTime::now()
55-
.checked_add(Duration::from_secs(5))
69+
.checked_add(Duration::from_secs(Self::DURATION))
5670
.unwrap(),
5771
}
5872
}
5973
}
74+
75+
#[cfg(test)]
76+
mod test {
77+
use super::*;
78+
use pretty_assertions::assert_eq;
79+
80+
#[test]
81+
fn test_countdown_char() -> () {
82+
let notification = Notification::info("hello".to_string());
83+
assert_eq!('█', notification.countdown_char());
84+
85+
let notification = Notification {
86+
message: "hello".to_string(),
87+
level: NotificationLevel::Info,
88+
expires: SystemTime::now(),
89+
};
90+
assert_eq!(' ', notification.countdown_char());
91+
92+
let notification = Notification {
93+
message: "hello".to_string(),
94+
level: NotificationLevel::Info,
95+
expires: SystemTime::now() - Duration::from_secs(10),
96+
};
97+
assert_eq!(' ', notification.countdown_char());
98+
}
99+
}

src/theme.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ impl Theme {
4848
syntax_brace: Style::default().fg(Solarized::Base01.to_color()),
4949
notification_info: Style::default().fg(Solarized::Green.to_color()),
5050
notification_error: Style::default().fg(Solarized::Red.to_color()),
51-
notification_warning: Style::default().fg(Color::Yellow),
51+
notification_warning: Style::default().fg(Solarized::Yellow.to_color()),
5252
pane_border_active: Style::default().fg(Solarized::Base01.to_color()),
5353
pane_border_inactive: Style::default().fg(Solarized::Base02.to_color()),
5454
source_line: Style::default().fg(Solarized::Base1.to_color()),

src/view/context.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ mod test {
219219
prop1.name = "foo".to_string();
220220

221221
// segments are reversed
222-
let mut filter = &mut vec![
222+
let filter = &mut vec![
223223
"bar",
224224
"foo",
225225
];

src/view/help.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ impl View for HelpView {
1414
if app.listening_status == ListenStatus::Connected{
1515
Some(AppEvent::ChangeView(SelectedView::Session))
1616
} else {
17-
Some(AppEvent::ChangeView(SelectedView::Listen))
17+
Some(AppEvent::Listen)
1818
}
1919
},
2020
_ => None

src/view/layout.rs

Lines changed: 35 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ impl View for LayoutView {
3535

3636
f.render_widget(Block::default().style(app.theme().background), area);
3737
f.render_widget(status_widget(app), rows[0]);
38+
f.render_widget(notification_widget(app), rows[0]);
3839

3940
match app.view_current {
4041
SelectedView::Listen => ListenView::draw(app, f, rows[1]),
@@ -44,14 +45,34 @@ impl View for LayoutView {
4445
}
4546
}
4647

48+
fn notification_widget(app: &App) -> Paragraph<'_> {
49+
Paragraph::new(vec![Line::from(Span::styled(
50+
match app.notification.is_visible() {
51+
true => format!(
52+
"{} {}",
53+
app.notification.message.clone(),
54+
app.notification.countdown_char()
55+
),
56+
false => "".to_string(),
57+
},
58+
match app.notification.level {
59+
NotificationLevel::Error => app.theme().notification_error,
60+
NotificationLevel::Warning => app.theme().notification_warning,
61+
NotificationLevel::Info => app.theme().notification_info,
62+
NotificationLevel::None => Style::default(),
63+
},
64+
))
65+
.alignment(ratatui::layout::Alignment::Right)])
66+
}
67+
4768
fn status_widget(app: &App) -> Paragraph {
4869
Paragraph::new(vec![Line::from(vec![
4970
Span::styled(
5071
format!(
5172
" 󱘖 {} ",
5273
match app.listening_status {
5374
ListenStatus::Connected => "connected".to_string(),
54-
75+
5576
ListenStatus::Listening => app.config.listen.to_string(),
5677
ListenStatus::Refusing => "refusing".to_string(),
5778
},
@@ -76,39 +97,25 @@ fn status_widget(app: &App) -> Paragraph {
7697
true => format!("  {} / ∞", app.history.offset + 1),
7798
false => "  0 / 0".to_string(),
7899
},
79-
SessionViewMode::History => {
80-
match app.listening_status {
81-
ListenStatus::Connected => format!(
82-
"  {} / {} history [p] to go back [n] to go forwards [b] to return",
83-
app.history.offset + 1,
84-
app.history.len()
85-
),
86-
ListenStatus::Refusing => format!(
87-
"  {} / {} terminated [p] to go back [n] to go forwards [b] to listen",
88-
app.history.offset + 1,
89-
app.history.len()
90-
),
91-
ListenStatus::Listening => String::new(),
92-
}
93-
}
100+
SessionViewMode::History => match app.listening_status {
101+
ListenStatus::Connected => format!(
102+
"  {} / {} history [p] to go back [n] to go forwards [b] to return",
103+
app.history.offset + 1,
104+
app.history.len()
105+
),
106+
ListenStatus::Refusing => format!(
107+
"  {} / {} terminated [p] to go back [n] to go forwards [b] to listen",
108+
app.history.offset + 1,
109+
app.history.len()
110+
),
111+
ListenStatus::Listening => String::new(),
112+
},
94113
})
95114
.to_string(),
96115
match app.session_view.mode {
97116
SessionViewMode::Current => app.theme().widget_mode_debug,
98117
SessionViewMode::History => app.theme().widget_mode_history,
99118
},
100119
),
101-
Span::styled(
102-
match app.notification.is_visible() {
103-
true => format!(" {} ", app.notification.message.clone()),
104-
false => "".to_string(),
105-
},
106-
match app.notification.level {
107-
NotificationLevel::Error => app.theme().notification_error,
108-
NotificationLevel::Warning => app.theme().notification_warning,
109-
NotificationLevel::Info => app.theme().notification_info,
110-
NotificationLevel::None => Style::default(),
111-
},
112-
),
113120
])])
114121
}

0 commit comments

Comments
 (0)