@delorenj/claude-notifications 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DO.md +5 -0
- package/FIXES-APPLIED.md +195 -0
- package/INTEGRATION.md +445 -0
- package/LAYOUT-INTEGRATION.md +191 -0
- package/QUICK-REFERENCE.md +195 -0
- package/README.md +145 -14
- package/TASK.md +15 -0
- package/ZELLIJ-NOTIFY.md +523 -0
- package/_bmad-output/implementation-artifacts/spec-install-multi-cli-hooks.md +241 -0
- package/bin/claude-notifications.js +417 -312
- package/bin/claude-notify.js +47 -1
- package/bin/zellij-notify.js +346 -0
- package/bun.lock +35 -0
- package/diagnose-zellij.sh +105 -0
- package/examples/settings-with-zellij.json +18 -0
- package/examples/settings-zellij-only.json +18 -0
- package/examples/zellij-notify-examples.sh +143 -0
- package/lib/adapters/_stub.js +35 -0
- package/lib/adapters/auggie.js +10 -0
- package/lib/adapters/claude-code.js +181 -0
- package/lib/adapters/codex.js +10 -0
- package/lib/adapters/copilot.js +10 -0
- package/lib/adapters/gemini.js +10 -0
- package/lib/adapters/index.js +240 -0
- package/lib/adapters/kimi.js +10 -0
- package/lib/adapters/opencode.js +14 -0
- package/lib/adapters/vibe.js +10 -0
- package/lib/config.js +44 -8
- package/lib/tui.js +115 -0
- package/lib/zellij.js +248 -0
- package/package.json +6 -4
- package/postinstall.js +28 -25
- package/preuninstall.js +18 -9
- package/test/adapters/claude-code.test.js +144 -0
- package/test/adapters/patches.test.js +81 -0
- package/test/adapters/registry.test.js +89 -0
- package/test/adapters/stubs.test.js +46 -0
- package/test/cli-json.test.js +79 -0
- package/test/helpers/fake-fs.js +59 -0
- package/test-integration.sh +113 -0
- package/test-notification-plugin.kdl +34 -0
- package/test-updated-layout.sh +75 -0
- package/test-zellij-cli.sh +72 -0
- package/zellij-plugin/.cargo/config.toml +5 -0
- package/zellij-plugin/.github/workflows/ci.yml +97 -0
- package/zellij-plugin/Cargo.lock +3558 -0
- package/zellij-plugin/Cargo.toml +40 -0
- package/zellij-plugin/README.md +290 -0
- package/zellij-plugin/build.sh +179 -0
- package/zellij-plugin/configs/examples/accessibility.kdl +31 -0
- package/zellij-plugin/configs/examples/catppuccin.kdl +32 -0
- package/zellij-plugin/configs/examples/default.kdl +34 -0
- package/zellij-plugin/configs/examples/minimal.kdl +22 -0
- package/zellij-plugin/docs/CONFIGURATION.md +191 -0
- package/zellij-plugin/docs/INTEGRATION.md +333 -0
- package/zellij-plugin/src/animation.rs +451 -0
- package/zellij-plugin/src/colors.rs +407 -0
- package/zellij-plugin/src/config.rs +664 -0
- package/zellij-plugin/src/event_bridge.rs +339 -0
- package/zellij-plugin/src/main.rs +420 -0
- package/zellij-plugin/src/notification.rs +466 -0
- package/zellij-plugin/src/queue.rs +399 -0
- package/zellij-plugin/src/renderer.rs +477 -0
- package/zellij-plugin/src/state.rs +338 -0
- package/zellij-plugin/src/tests.rs +413 -0
- package/.claude/checkpoints/1756392335.json +0 -1
- package/.claude/checkpoints/1756392341.json +0 -1
- package/.claude/checkpoints/1756392347.json +0 -1
- package/.claude/checkpoints/1756392376.json +0 -1
- package/.claude/checkpoints/1756392377.json +0 -1
- package/.claude/checkpoints/1756392386.json +0 -1
- package/.claude/checkpoints/1756392387.json +0 -1
- package/.claude/checkpoints/1756392398.json +0 -1
- package/.claude/checkpoints/1756392400.json +0 -1
- package/.claude/checkpoints/1756392427.json +0 -1
- package/.claude/checkpoints/1756392428.json +0 -1
- package/.claude/checkpoints/1756392486.json +0 -1
- package/.claude/checkpoints/1756392488.json +0 -1
- package/.claude/checkpoints/1756392558.json +0 -1
- package/.claude/checkpoints/1756392559.json +0 -1
- package/.claude/checkpoints/summary-session-20250828-105040.md +0 -57
- package/.claude/checkpoints/task-1756392207.json +0 -1
- package/.claude/checkpoints/task-1756392742.json +0 -1
- package/.claude/commands/analysis/COMMAND_COMPLIANCE_REPORT.md +0 -54
- package/.claude/commands/analysis/README.md +0 -9
- package/.claude/commands/analysis/bottleneck-detect.md +0 -162
- package/.claude/commands/analysis/performance-bottlenecks.md +0 -59
- package/.claude/commands/analysis/performance-report.md +0 -25
- package/.claude/commands/analysis/token-efficiency.md +0 -45
- package/.claude/commands/analysis/token-usage.md +0 -25
- package/.claude/commands/automation/README.md +0 -9
- package/.claude/commands/automation/auto-agent.md +0 -122
- package/.claude/commands/automation/self-healing.md +0 -106
- package/.claude/commands/automation/session-memory.md +0 -90
- package/.claude/commands/automation/smart-agents.md +0 -73
- package/.claude/commands/automation/smart-spawn.md +0 -25
- package/.claude/commands/automation/workflow-select.md +0 -25
- package/.claude/commands/coordination/README.md +0 -9
- package/.claude/commands/coordination/agent-spawn.md +0 -25
- package/.claude/commands/coordination/init.md +0 -44
- package/.claude/commands/coordination/orchestrate.md +0 -43
- package/.claude/commands/coordination/spawn.md +0 -45
- package/.claude/commands/coordination/swarm-init.md +0 -85
- package/.claude/commands/coordination/task-orchestrate.md +0 -25
- package/.claude/commands/github/README.md +0 -11
- package/.claude/commands/github/code-review-swarm.md +0 -514
- package/.claude/commands/github/code-review.md +0 -25
- package/.claude/commands/github/github-modes.md +0 -147
- package/.claude/commands/github/github-swarm.md +0 -121
- package/.claude/commands/github/issue-tracker.md +0 -292
- package/.claude/commands/github/issue-triage.md +0 -25
- package/.claude/commands/github/multi-repo-swarm.md +0 -519
- package/.claude/commands/github/pr-enhance.md +0 -26
- package/.claude/commands/github/pr-manager.md +0 -170
- package/.claude/commands/github/project-board-sync.md +0 -471
- package/.claude/commands/github/release-manager.md +0 -338
- package/.claude/commands/github/release-swarm.md +0 -544
- package/.claude/commands/github/repo-analyze.md +0 -25
- package/.claude/commands/github/repo-architect.md +0 -367
- package/.claude/commands/github/swarm-issue.md +0 -482
- package/.claude/commands/github/swarm-pr.md +0 -285
- package/.claude/commands/github/sync-coordinator.md +0 -301
- package/.claude/commands/github/workflow-automation.md +0 -442
- package/.claude/commands/hooks/README.md +0 -11
- package/.claude/commands/hooks/overview.md +0 -58
- package/.claude/commands/hooks/post-edit.md +0 -117
- package/.claude/commands/hooks/post-task.md +0 -112
- package/.claude/commands/hooks/pre-edit.md +0 -113
- package/.claude/commands/hooks/pre-task.md +0 -111
- package/.claude/commands/hooks/session-end.md +0 -118
- package/.claude/commands/hooks/setup.md +0 -103
- package/.claude/commands/memory/README.md +0 -9
- package/.claude/commands/memory/memory-persist.md +0 -25
- package/.claude/commands/memory/memory-search.md +0 -25
- package/.claude/commands/memory/memory-usage.md +0 -25
- package/.claude/commands/memory/neural.md +0 -47
- package/.claude/commands/memory/usage.md +0 -46
- package/.claude/commands/monitoring/README.md +0 -9
- package/.claude/commands/monitoring/agent-metrics.md +0 -25
- package/.claude/commands/monitoring/agents.md +0 -44
- package/.claude/commands/monitoring/real-time-view.md +0 -25
- package/.claude/commands/monitoring/status.md +0 -46
- package/.claude/commands/monitoring/swarm-monitor.md +0 -25
- package/.claude/commands/optimization/README.md +0 -9
- package/.claude/commands/optimization/auto-topology.md +0 -62
- package/.claude/commands/optimization/cache-manage.md +0 -25
- package/.claude/commands/optimization/parallel-execute.md +0 -25
- package/.claude/commands/optimization/parallel-execution.md +0 -50
- package/.claude/commands/optimization/topology-optimize.md +0 -25
- package/.claude/commands/pair/README.md +0 -261
- package/.claude/commands/pair/commands.md +0 -546
- package/.claude/commands/pair/config.md +0 -510
- package/.claude/commands/pair/examples.md +0 -512
- package/.claude/commands/pair/modes.md +0 -348
- package/.claude/commands/pair/session.md +0 -407
- package/.claude/commands/pair/start.md +0 -209
- package/.claude/commands/sparc/analyzer.md +0 -52
- package/.claude/commands/sparc/architect.md +0 -53
- package/.claude/commands/sparc/batch-executor.md +0 -54
- package/.claude/commands/sparc/coder.md +0 -54
- package/.claude/commands/sparc/debugger.md +0 -54
- package/.claude/commands/sparc/designer.md +0 -53
- package/.claude/commands/sparc/documenter.md +0 -54
- package/.claude/commands/sparc/innovator.md +0 -54
- package/.claude/commands/sparc/memory-manager.md +0 -54
- package/.claude/commands/sparc/optimizer.md +0 -54
- package/.claude/commands/sparc/orchestrator.md +0 -132
- package/.claude/commands/sparc/researcher.md +0 -54
- package/.claude/commands/sparc/reviewer.md +0 -54
- package/.claude/commands/sparc/sparc-modes.md +0 -174
- package/.claude/commands/sparc/swarm-coordinator.md +0 -54
- package/.claude/commands/sparc/tdd.md +0 -54
- package/.claude/commands/sparc/tester.md +0 -54
- package/.claude/commands/sparc/workflow-manager.md +0 -54
- package/.claude/commands/stream-chain/pipeline.md +0 -121
- package/.claude/commands/stream-chain/run.md +0 -70
- package/.claude/commands/swarm/analysis.md +0 -95
- package/.claude/commands/swarm/development.md +0 -96
- package/.claude/commands/swarm/examples.md +0 -168
- package/.claude/commands/swarm/maintenance.md +0 -102
- package/.claude/commands/swarm/optimization.md +0 -117
- package/.claude/commands/swarm/research.md +0 -136
- package/.claude/commands/swarm/testing.md +0 -131
- package/.claude/commands/training/README.md +0 -9
- package/.claude/commands/training/model-update.md +0 -25
- package/.claude/commands/training/neural-patterns.md +0 -74
- package/.claude/commands/training/neural-train.md +0 -25
- package/.claude/commands/training/pattern-learn.md +0 -25
- package/.claude/commands/training/specialization.md +0 -63
- package/.claude/commands/truth/start.md +0 -143
- package/.claude/commands/verify/check.md +0 -50
- package/.claude/commands/verify/start.md +0 -128
- package/.claude/commands/workflows/README.md +0 -9
- package/.claude/commands/workflows/development.md +0 -78
- package/.claude/commands/workflows/research.md +0 -63
- package/.claude/commands/workflows/workflow-create.md +0 -25
- package/.claude/commands/workflows/workflow-execute.md +0 -25
- package/.claude/commands/workflows/workflow-export.md +0 -25
- package/.claude/config.json +0 -36
- package/.claude/settings.json +0 -162
- package/.claude-flow/metrics/agent-metrics.json +0 -1
- package/.claude-flow/metrics/performance.json +0 -9
- package/.claude-flow/metrics/system-metrics.json +0 -230
- package/.claude-flow/metrics/task-metrics.json +0 -10
- package/FIXES.md +0 -75
- package/test-results.md +0 -163
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
//! Renderer module for Zellij Visual Notifications
|
|
2
|
+
//!
|
|
3
|
+
//! Handles rendering of status bar widgets, pane borders, and badges.
|
|
4
|
+
|
|
5
|
+
use std::collections::BTreeMap;
|
|
6
|
+
use crate::animation::AnimationEngine;
|
|
7
|
+
use crate::colors::ColorManager;
|
|
8
|
+
use crate::config::Config;
|
|
9
|
+
use crate::notification::NotificationType;
|
|
10
|
+
use crate::queue::NotificationQueue;
|
|
11
|
+
use crate::state::VisualState;
|
|
12
|
+
|
|
13
|
+
/// Renderer for visual elements
|
|
14
|
+
#[derive(Debug, Clone)]
|
|
15
|
+
pub struct Renderer {
|
|
16
|
+
/// Show status bar widget
|
|
17
|
+
show_status_bar: bool,
|
|
18
|
+
/// Show border colors
|
|
19
|
+
show_border_colors: bool,
|
|
20
|
+
/// Show tab badges
|
|
21
|
+
show_tab_badges: bool,
|
|
22
|
+
/// Use unicode icons
|
|
23
|
+
use_unicode: bool,
|
|
24
|
+
/// Accessibility mode (patterns instead of colors only)
|
|
25
|
+
use_patterns: bool,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
impl Default for Renderer {
|
|
29
|
+
fn default() -> Self {
|
|
30
|
+
Self {
|
|
31
|
+
show_status_bar: true,
|
|
32
|
+
show_border_colors: true,
|
|
33
|
+
show_tab_badges: true,
|
|
34
|
+
use_unicode: true,
|
|
35
|
+
use_patterns: true,
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
impl Renderer {
|
|
41
|
+
/// Create a new renderer with configuration
|
|
42
|
+
pub fn new(config: &Config) -> Self {
|
|
43
|
+
Self {
|
|
44
|
+
show_status_bar: config.show_status_bar,
|
|
45
|
+
show_border_colors: config.show_border_colors,
|
|
46
|
+
show_tab_badges: config.show_tab_badges,
|
|
47
|
+
use_unicode: true,
|
|
48
|
+
use_patterns: config.accessibility.use_patterns,
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/// Render the status bar widget
|
|
53
|
+
pub fn render_status_bar(
|
|
54
|
+
&self,
|
|
55
|
+
rows: usize,
|
|
56
|
+
cols: usize,
|
|
57
|
+
pane_states: &BTreeMap<u32, VisualState>,
|
|
58
|
+
queue: &NotificationQueue,
|
|
59
|
+
color_manager: &ColorManager,
|
|
60
|
+
animation_engine: &AnimationEngine,
|
|
61
|
+
tick: u64,
|
|
62
|
+
) {
|
|
63
|
+
if !self.show_status_bar || cols < 10 {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Count active notifications
|
|
68
|
+
let active_count = pane_states.values().filter(|s| s.has_notification()).count();
|
|
69
|
+
let queue_count = queue.len();
|
|
70
|
+
|
|
71
|
+
// Build status bar content
|
|
72
|
+
let content = self.build_status_content(
|
|
73
|
+
active_count,
|
|
74
|
+
queue_count,
|
|
75
|
+
pane_states,
|
|
76
|
+
color_manager,
|
|
77
|
+
animation_engine,
|
|
78
|
+
tick,
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
// Print the status bar (Zellij will capture this)
|
|
82
|
+
print!("{}", content);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/// Build the status bar content string
|
|
86
|
+
fn build_status_content(
|
|
87
|
+
&self,
|
|
88
|
+
active_count: usize,
|
|
89
|
+
queue_count: usize,
|
|
90
|
+
pane_states: &BTreeMap<u32, VisualState>,
|
|
91
|
+
color_manager: &ColorManager,
|
|
92
|
+
animation_engine: &AnimationEngine,
|
|
93
|
+
tick: u64,
|
|
94
|
+
) -> String {
|
|
95
|
+
let mut output = String::new();
|
|
96
|
+
|
|
97
|
+
// Plugin name/icon
|
|
98
|
+
let icon = if self.use_unicode { "\u{1F514}" } else { "[N]" }; // Bell icon
|
|
99
|
+
output.push_str(&format!("{} ", icon));
|
|
100
|
+
|
|
101
|
+
// Show notification counts
|
|
102
|
+
if active_count == 0 && queue_count == 0 {
|
|
103
|
+
output.push_str(&format!("{}No notifications{}",
|
|
104
|
+
color_manager.fg_escape(&color_manager.get_dimmed_color()),
|
|
105
|
+
color_manager.reset_escape()
|
|
106
|
+
));
|
|
107
|
+
} else {
|
|
108
|
+
// Show active notification indicators
|
|
109
|
+
for (pane_id, state) in pane_states.iter() {
|
|
110
|
+
if let Some(ref notif_type) = state.notification_type {
|
|
111
|
+
if !state.acknowledged {
|
|
112
|
+
let color = color_manager.get_notification_color(notif_type)
|
|
113
|
+
.unwrap_or_else(|| color_manager.get_foreground_color());
|
|
114
|
+
|
|
115
|
+
let brightness = animation_engine.get_brightness(state, tick);
|
|
116
|
+
let adjusted_color = color_manager.apply_brightness(&color, brightness);
|
|
117
|
+
|
|
118
|
+
let icon = self.get_notification_icon(notif_type);
|
|
119
|
+
let pattern = if self.use_patterns {
|
|
120
|
+
self.get_pattern_suffix(notif_type)
|
|
121
|
+
} else {
|
|
122
|
+
""
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
output.push_str(&format!("{}[{}{}:{}{}]{} ",
|
|
126
|
+
color_manager.fg_escape(&adjusted_color),
|
|
127
|
+
icon,
|
|
128
|
+
pattern,
|
|
129
|
+
pane_id,
|
|
130
|
+
if state.is_animating { "*" } else { "" },
|
|
131
|
+
color_manager.reset_escape()
|
|
132
|
+
));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Show queue count if any
|
|
138
|
+
if queue_count > 0 {
|
|
139
|
+
output.push_str(&format!("(+{} queued)", queue_count));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
output
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/// Get the icon for a notification type
|
|
147
|
+
fn get_notification_icon(&self, notification_type: &NotificationType) -> &'static str {
|
|
148
|
+
if self.use_unicode {
|
|
149
|
+
match notification_type {
|
|
150
|
+
NotificationType::Success => "\u{2714}", // Check mark
|
|
151
|
+
NotificationType::Error => "\u{2718}", // X mark
|
|
152
|
+
NotificationType::Warning => "\u{26A0}", // Warning triangle
|
|
153
|
+
NotificationType::Info => "\u{2139}", // Info symbol
|
|
154
|
+
NotificationType::Progress => "\u{21BB}", // Rotating arrow
|
|
155
|
+
NotificationType::Attention => "\u{2757}", // Exclamation mark
|
|
156
|
+
}
|
|
157
|
+
} else {
|
|
158
|
+
match notification_type {
|
|
159
|
+
NotificationType::Success => "+",
|
|
160
|
+
NotificationType::Error => "X",
|
|
161
|
+
NotificationType::Warning => "!",
|
|
162
|
+
NotificationType::Info => "i",
|
|
163
|
+
NotificationType::Progress => "~",
|
|
164
|
+
NotificationType::Attention => "!",
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/// Get pattern suffix for accessibility (distinguishes by shape, not just color)
|
|
170
|
+
fn get_pattern_suffix(&self, notification_type: &NotificationType) -> &'static str {
|
|
171
|
+
match notification_type {
|
|
172
|
+
NotificationType::Success => "=", // Double line
|
|
173
|
+
NotificationType::Error => "##", // Hash/blocked
|
|
174
|
+
NotificationType::Warning => "~~", // Wavy
|
|
175
|
+
NotificationType::Info => "..", // Dots
|
|
176
|
+
NotificationType::Progress => "->", // Arrow
|
|
177
|
+
NotificationType::Attention => "!!", // Double exclaim
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/// Render a pane badge (for tab bar)
|
|
182
|
+
pub fn render_pane_badge(
|
|
183
|
+
&self,
|
|
184
|
+
state: &VisualState,
|
|
185
|
+
color_manager: &ColorManager,
|
|
186
|
+
) -> Option<String> {
|
|
187
|
+
if !self.show_tab_badges {
|
|
188
|
+
return None;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if let Some(ref notif_type) = state.notification_type {
|
|
192
|
+
if !state.acknowledged {
|
|
193
|
+
let icon = self.get_notification_icon(notif_type);
|
|
194
|
+
let color = color_manager.get_notification_color(notif_type)?;
|
|
195
|
+
|
|
196
|
+
return Some(format!("{}{}{}",
|
|
197
|
+
color_manager.fg_escape(&color),
|
|
198
|
+
icon,
|
|
199
|
+
color_manager.reset_escape()
|
|
200
|
+
));
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
None
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/// Get border style for a pane
|
|
208
|
+
pub fn get_border_style(
|
|
209
|
+
&self,
|
|
210
|
+
state: &VisualState,
|
|
211
|
+
color_manager: &ColorManager,
|
|
212
|
+
animation_engine: &AnimationEngine,
|
|
213
|
+
tick: u64,
|
|
214
|
+
) -> Option<BorderStyle> {
|
|
215
|
+
if !self.show_border_colors {
|
|
216
|
+
return None;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if let Some(ref notif_type) = state.notification_type {
|
|
220
|
+
if !state.acknowledged {
|
|
221
|
+
let base_color = color_manager.get_notification_color(notif_type)?;
|
|
222
|
+
|
|
223
|
+
// Apply animation brightness
|
|
224
|
+
let brightness = animation_engine.get_brightness(state, tick);
|
|
225
|
+
let color = color_manager.apply_brightness(&base_color, brightness);
|
|
226
|
+
|
|
227
|
+
return Some(BorderStyle {
|
|
228
|
+
color,
|
|
229
|
+
style: if state.is_animating {
|
|
230
|
+
BorderLineStyle::Double
|
|
231
|
+
} else {
|
|
232
|
+
BorderLineStyle::Single
|
|
233
|
+
},
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
None
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/// Format notification for tooltip/popup
|
|
242
|
+
pub fn format_notification_tooltip(
|
|
243
|
+
&self,
|
|
244
|
+
state: &VisualState,
|
|
245
|
+
_color_manager: &ColorManager,
|
|
246
|
+
) -> Option<String> {
|
|
247
|
+
if let Some(ref message) = state.notification_message {
|
|
248
|
+
let icon = state.notification_type.as_ref()
|
|
249
|
+
.map(|t| self.get_notification_icon(t))
|
|
250
|
+
.unwrap_or("");
|
|
251
|
+
|
|
252
|
+
Some(format!("{} {}", icon, message))
|
|
253
|
+
} else {
|
|
254
|
+
None
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/// Create a summary line for multiple notifications
|
|
259
|
+
pub fn render_summary(
|
|
260
|
+
&self,
|
|
261
|
+
pane_states: &BTreeMap<u32, VisualState>,
|
|
262
|
+
color_manager: &ColorManager,
|
|
263
|
+
) -> String {
|
|
264
|
+
let mut success = 0;
|
|
265
|
+
let mut error = 0;
|
|
266
|
+
let mut warning = 0;
|
|
267
|
+
let mut info = 0;
|
|
268
|
+
let mut attention = 0;
|
|
269
|
+
|
|
270
|
+
for state in pane_states.values() {
|
|
271
|
+
if let Some(ref notif_type) = state.notification_type {
|
|
272
|
+
if !state.acknowledged {
|
|
273
|
+
match notif_type {
|
|
274
|
+
NotificationType::Success => success += 1,
|
|
275
|
+
NotificationType::Error => error += 1,
|
|
276
|
+
NotificationType::Warning => warning += 1,
|
|
277
|
+
NotificationType::Info => info += 1,
|
|
278
|
+
NotificationType::Attention => attention += 1,
|
|
279
|
+
NotificationType::Progress => {}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
let mut parts = Vec::new();
|
|
286
|
+
|
|
287
|
+
if success > 0 {
|
|
288
|
+
let color = color_manager.get_notification_color(&NotificationType::Success)
|
|
289
|
+
.unwrap_or_default();
|
|
290
|
+
parts.push(format!("{}{}{}{}",
|
|
291
|
+
color_manager.fg_escape(&color),
|
|
292
|
+
self.get_notification_icon(&NotificationType::Success),
|
|
293
|
+
success,
|
|
294
|
+
color_manager.reset_escape()
|
|
295
|
+
));
|
|
296
|
+
}
|
|
297
|
+
if error > 0 {
|
|
298
|
+
let color = color_manager.get_notification_color(&NotificationType::Error)
|
|
299
|
+
.unwrap_or_default();
|
|
300
|
+
parts.push(format!("{}{}{}{}",
|
|
301
|
+
color_manager.fg_escape(&color),
|
|
302
|
+
self.get_notification_icon(&NotificationType::Error),
|
|
303
|
+
error,
|
|
304
|
+
color_manager.reset_escape()
|
|
305
|
+
));
|
|
306
|
+
}
|
|
307
|
+
if warning > 0 {
|
|
308
|
+
let color = color_manager.get_notification_color(&NotificationType::Warning)
|
|
309
|
+
.unwrap_or_default();
|
|
310
|
+
parts.push(format!("{}{}{}{}",
|
|
311
|
+
color_manager.fg_escape(&color),
|
|
312
|
+
self.get_notification_icon(&NotificationType::Warning),
|
|
313
|
+
warning,
|
|
314
|
+
color_manager.reset_escape()
|
|
315
|
+
));
|
|
316
|
+
}
|
|
317
|
+
if attention > 0 {
|
|
318
|
+
let color = color_manager.get_notification_color(&NotificationType::Attention)
|
|
319
|
+
.unwrap_or_default();
|
|
320
|
+
parts.push(format!("{}{}{}{}",
|
|
321
|
+
color_manager.fg_escape(&color),
|
|
322
|
+
self.get_notification_icon(&NotificationType::Attention),
|
|
323
|
+
attention,
|
|
324
|
+
color_manager.reset_escape()
|
|
325
|
+
));
|
|
326
|
+
}
|
|
327
|
+
if info > 0 {
|
|
328
|
+
let color = color_manager.get_notification_color(&NotificationType::Info)
|
|
329
|
+
.unwrap_or_default();
|
|
330
|
+
parts.push(format!("{}{}{}{}",
|
|
331
|
+
color_manager.fg_escape(&color),
|
|
332
|
+
self.get_notification_icon(&NotificationType::Info),
|
|
333
|
+
info,
|
|
334
|
+
color_manager.reset_escape()
|
|
335
|
+
));
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if parts.is_empty() {
|
|
339
|
+
"No notifications".to_string()
|
|
340
|
+
} else {
|
|
341
|
+
parts.join(" ")
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/// Border style for pane borders
|
|
347
|
+
#[derive(Debug, Clone)]
|
|
348
|
+
pub struct BorderStyle {
|
|
349
|
+
/// Border color (hex)
|
|
350
|
+
pub color: String,
|
|
351
|
+
/// Line style
|
|
352
|
+
pub style: BorderLineStyle,
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/// Border line styles
|
|
356
|
+
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
357
|
+
pub enum BorderLineStyle {
|
|
358
|
+
/// Single line border
|
|
359
|
+
Single,
|
|
360
|
+
/// Double line border
|
|
361
|
+
Double,
|
|
362
|
+
/// Dashed border
|
|
363
|
+
Dashed,
|
|
364
|
+
/// Dotted border
|
|
365
|
+
Dotted,
|
|
366
|
+
/// Bold/thick border
|
|
367
|
+
Bold,
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
impl BorderLineStyle {
|
|
371
|
+
/// Get the box-drawing characters for this style
|
|
372
|
+
pub fn chars(&self) -> BorderChars {
|
|
373
|
+
match self {
|
|
374
|
+
BorderLineStyle::Single => BorderChars {
|
|
375
|
+
horizontal: '\u{2500}',
|
|
376
|
+
vertical: '\u{2502}',
|
|
377
|
+
top_left: '\u{250C}',
|
|
378
|
+
top_right: '\u{2510}',
|
|
379
|
+
bottom_left: '\u{2514}',
|
|
380
|
+
bottom_right: '\u{2518}',
|
|
381
|
+
},
|
|
382
|
+
BorderLineStyle::Double => BorderChars {
|
|
383
|
+
horizontal: '\u{2550}',
|
|
384
|
+
vertical: '\u{2551}',
|
|
385
|
+
top_left: '\u{2554}',
|
|
386
|
+
top_right: '\u{2557}',
|
|
387
|
+
bottom_left: '\u{255A}',
|
|
388
|
+
bottom_right: '\u{255D}',
|
|
389
|
+
},
|
|
390
|
+
BorderLineStyle::Dashed => BorderChars {
|
|
391
|
+
horizontal: '\u{2504}',
|
|
392
|
+
vertical: '\u{2506}',
|
|
393
|
+
top_left: '\u{250C}',
|
|
394
|
+
top_right: '\u{2510}',
|
|
395
|
+
bottom_left: '\u{2514}',
|
|
396
|
+
bottom_right: '\u{2518}',
|
|
397
|
+
},
|
|
398
|
+
BorderLineStyle::Dotted => BorderChars {
|
|
399
|
+
horizontal: '\u{2508}',
|
|
400
|
+
vertical: '\u{250A}',
|
|
401
|
+
top_left: '\u{250C}',
|
|
402
|
+
top_right: '\u{2510}',
|
|
403
|
+
bottom_left: '\u{2514}',
|
|
404
|
+
bottom_right: '\u{2518}',
|
|
405
|
+
},
|
|
406
|
+
BorderLineStyle::Bold => BorderChars {
|
|
407
|
+
horizontal: '\u{2501}',
|
|
408
|
+
vertical: '\u{2503}',
|
|
409
|
+
top_left: '\u{250F}',
|
|
410
|
+
top_right: '\u{2513}',
|
|
411
|
+
bottom_left: '\u{2517}',
|
|
412
|
+
bottom_right: '\u{251B}',
|
|
413
|
+
},
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/// Box-drawing characters for borders
|
|
419
|
+
#[derive(Debug, Clone, Copy)]
|
|
420
|
+
pub struct BorderChars {
|
|
421
|
+
pub horizontal: char,
|
|
422
|
+
pub vertical: char,
|
|
423
|
+
pub top_left: char,
|
|
424
|
+
pub top_right: char,
|
|
425
|
+
pub bottom_left: char,
|
|
426
|
+
pub bottom_right: char,
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
#[cfg(test)]
|
|
430
|
+
mod tests {
|
|
431
|
+
use super::*;
|
|
432
|
+
|
|
433
|
+
#[test]
|
|
434
|
+
fn test_renderer_creation() {
|
|
435
|
+
let config = Config::default();
|
|
436
|
+
let renderer = Renderer::new(&config);
|
|
437
|
+
assert!(renderer.show_status_bar);
|
|
438
|
+
assert!(renderer.show_border_colors);
|
|
439
|
+
assert!(renderer.show_tab_badges);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
#[test]
|
|
443
|
+
fn test_notification_icons() {
|
|
444
|
+
let renderer = Renderer::default();
|
|
445
|
+
|
|
446
|
+
let success_icon = renderer.get_notification_icon(&NotificationType::Success);
|
|
447
|
+
let error_icon = renderer.get_notification_icon(&NotificationType::Error);
|
|
448
|
+
|
|
449
|
+
assert!(!success_icon.is_empty());
|
|
450
|
+
assert!(!error_icon.is_empty());
|
|
451
|
+
assert_ne!(success_icon, error_icon);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
#[test]
|
|
455
|
+
fn test_border_line_styles() {
|
|
456
|
+
let single = BorderLineStyle::Single;
|
|
457
|
+
let double = BorderLineStyle::Double;
|
|
458
|
+
|
|
459
|
+
let single_chars = single.chars();
|
|
460
|
+
let double_chars = double.chars();
|
|
461
|
+
|
|
462
|
+
assert_ne!(single_chars.horizontal, double_chars.horizontal);
|
|
463
|
+
assert_ne!(single_chars.vertical, double_chars.vertical);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
#[test]
|
|
467
|
+
fn test_pattern_suffix() {
|
|
468
|
+
let renderer = Renderer::default();
|
|
469
|
+
|
|
470
|
+
let success_pattern = renderer.get_pattern_suffix(&NotificationType::Success);
|
|
471
|
+
let error_pattern = renderer.get_pattern_suffix(&NotificationType::Error);
|
|
472
|
+
|
|
473
|
+
assert!(!success_pattern.is_empty());
|
|
474
|
+
assert!(!error_pattern.is_empty());
|
|
475
|
+
assert_ne!(success_pattern, error_pattern);
|
|
476
|
+
}
|
|
477
|
+
}
|