Skip to content

Commit f736351

Browse files
committed
feat: add pipe widget
1 parent 60a5055 commit f736351

File tree

8 files changed

+138
-7
lines changed

8 files changed

+138
-7
lines changed

.zellij.kdl

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ layout {
88

99
tab name="edit" focus=true {
1010
pane {
11-
command "fish"
12-
args "-c" "TERM=wezterm direnv exec . nvim"
11+
command "direnv"
12+
args "exec" "." "hx"
1313
}
1414
}
1515

src/bin/zjstatus.rs

+3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use zjstatus::{
1212
datetime::DateTimeWidget,
1313
mode::ModeWidget,
1414
notification::NotificationWidget,
15+
pipe::PipeWidget,
1516
session::SessionWidget,
1617
swap_layout::SwapLayoutWidget,
1718
tabs::TabsWidget,
@@ -90,6 +91,7 @@ impl ZellijPlugin for State {
9091
self.state = ZellijState {
9192
cols: 0,
9293
command_results: BTreeMap::new(),
94+
pipe_results: BTreeMap::new(),
9395
mode: ModeInfo::default(),
9496
panes: PaneManifest::default(),
9597
plugin_uuid: uid.to_string(),
@@ -307,6 +309,7 @@ fn register_widgets(configuration: &BTreeMap<String, String>) -> BTreeMap<String
307309
"datetime".to_owned(),
308310
Arc::new(DateTimeWidget::new(configuration)),
309311
);
312+
widget_map.insert("pipe".to_owned(), Arc::new(PipeWidget::new(configuration)));
310313
widget_map.insert(
311314
"swap_layout".to_owned(),
312315
Arc::new(SwapLayoutWidget::new(configuration)),

src/config.rs

+6
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use chrono::{DateTime, Local};
1515
pub struct ZellijState {
1616
pub cols: usize,
1717
pub command_results: BTreeMap<String, CommandResult>,
18+
pub pipe_results: BTreeMap<String, String>,
1819
pub mode: ModeInfo,
1920
pub panes: PaneManifest,
2021
pub plugin_uuid: String,
@@ -63,6 +64,7 @@ pub fn event_mask_from_widget_name(name: &str) -> u8 {
6364
"session" => UpdateEventMask::Mode as u8,
6465
"swap_layout" => UpdateEventMask::Tab as u8,
6566
"tabs" => UpdateEventMask::Tab as u8,
67+
"pipe" => UpdateEventMask::Always as u8,
6668
_ => UpdateEventMask::None as u8,
6769
}
6870
}
@@ -278,6 +280,10 @@ impl ModuleConfig {
278280
widget_key_name = "command";
279281
}
280282

283+
if widget_key.starts_with("pipe_") {
284+
widget_key_name = "pipe";
285+
}
286+
281287
if !tokens.contains(&widget_key_name.to_owned()) {
282288
continue;
283289
}

src/pipe.rs

+16
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,28 @@ fn process_line(state: &mut ZellijState, line: &str) -> bool {
6767

6868
should_render = true;
6969
}
70+
"pipe" => {
71+
if parts.len() < 4 {
72+
return false;
73+
}
74+
75+
pipe(state, parts[2], parts[3]);
76+
77+
should_render = true;
78+
}
7079
_ => {}
7180
}
7281

7382
should_render
7483
}
7584

85+
fn pipe(state: &mut ZellijState, name: &str, content: &str) {
86+
tracing::debug!("saving pipe result {name} {content}");
87+
state
88+
.pipe_results
89+
.insert(name.to_owned(), content.to_owned());
90+
}
91+
7692
fn notify(state: &mut ZellijState, message: &str) {
7793
state.incoming_notification = Some(notification::Message {
7894
body: message.to_string(),

src/render.rs

+9-4
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,10 @@ impl FormattedPart {
205205
widget_key_name = "command";
206206
}
207207

208+
if widget_key.starts_with("pipe_") {
209+
widget_key_name = "pipe";
210+
}
211+
208212
let widget_mask = event_mask_from_widget_name(widget_key_name);
209213
let skip_widget_cache = widget_mask & UpdateEventMask::Always as u8 != 0;
210214
if !skip_widget_cache && widget_mask & state.cache_mask == 0 {
@@ -278,6 +282,10 @@ fn cache_mask_from_content(content: &str) -> u8 {
278282
widget_key_name = "command";
279283
}
280284

285+
if widget_key.starts_with("pipe_") {
286+
widget_key_name = "pipe";
287+
}
288+
281289
output |= event_mask_from_widget_name(widget_key_name);
282290
}
283291
output
@@ -304,10 +312,7 @@ fn parse_color(color: &str, config: &BTreeMap<String, String>) -> Option<Color>
304312
if color.starts_with('$') {
305313
let alias_name = color.strip_prefix('$').unwrap();
306314

307-
color = match config.get(&format!("color_{alias_name}")) {
308-
Some(color_str) => color_str,
309-
None => return None,
310-
};
315+
color = config.get(&format!("color_{alias_name}"))?;
311316
}
312317

313318
if color.starts_with('#') {

src/widgets/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ pub mod command;
22
pub mod datetime;
33
pub mod mode;
44
pub mod notification;
5+
pub mod pipe;
56
pub mod session;
67
pub mod swap_layout;
78
pub mod tabs;

src/widgets/pipe.rs

+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
use lazy_static::lazy_static;
2+
use regex::Regex;
3+
use std::collections::BTreeMap;
4+
5+
use crate::render::FormattedPart;
6+
7+
use super::widget::Widget;
8+
9+
lazy_static! {
10+
static ref PIPE_REGEX: Regex = Regex::new("_[a-zA-Z0-9]+$").unwrap();
11+
}
12+
13+
pub struct PipeWidget {
14+
config: BTreeMap<String, PipeConfig>,
15+
}
16+
17+
#[derive(Clone)]
18+
struct PipeConfig {
19+
format: Vec<FormattedPart>,
20+
}
21+
22+
impl PipeWidget {
23+
pub fn new(config: &BTreeMap<String, String>) -> Self {
24+
Self {
25+
config: parse_config(config),
26+
}
27+
}
28+
}
29+
30+
impl Widget for PipeWidget {
31+
fn process(&self, name: &str, state: &crate::config::ZellijState) -> String {
32+
let pipe_config = match self.config.get(name) {
33+
Some(pc) => pc,
34+
None => {
35+
tracing::debug!("pipe no name {name}");
36+
return "".to_owned();
37+
}
38+
};
39+
40+
let pipe_result = match state.pipe_results.get(name) {
41+
Some(pr) => pr,
42+
None => {
43+
tracing::debug!("pipe no content {name}");
44+
return "".to_owned();
45+
}
46+
};
47+
48+
pipe_config
49+
.format
50+
.iter()
51+
.map(|f| {
52+
let mut content = f.content.clone();
53+
54+
if content.contains("{output}") {
55+
content = content.replace(
56+
"{output}",
57+
pipe_result.strip_suffix('\n').unwrap_or(pipe_result),
58+
)
59+
}
60+
61+
(f, content)
62+
})
63+
.fold("".to_owned(), |acc, (f, content)| {
64+
format!("{acc}{}", f.format_string(&content))
65+
})
66+
}
67+
68+
fn process_click(&self, _name: &str, _state: &crate::config::ZellijState, _pos: usize) {}
69+
}
70+
71+
fn parse_config(zj_conf: &BTreeMap<String, String>) -> BTreeMap<String, PipeConfig> {
72+
let mut keys: Vec<String> = zj_conf
73+
.keys()
74+
.filter(|k| k.starts_with("pipe_"))
75+
.cloned()
76+
.collect();
77+
keys.sort();
78+
79+
let mut config: BTreeMap<String, PipeConfig> = BTreeMap::new();
80+
81+
for key in keys {
82+
let pipe_name = PIPE_REGEX.replace(&key, "").to_string();
83+
let mut pipe_conf = PipeConfig { format: vec![] };
84+
85+
if let Some(existing_conf) = config.get(pipe_name.as_str()) {
86+
pipe_conf = existing_conf.clone();
87+
}
88+
89+
if key.ends_with("format") {
90+
pipe_conf.format =
91+
FormattedPart::multiple_from_format_string(zj_conf.get(&key).unwrap(), zj_conf);
92+
}
93+
94+
config.insert(pipe_name, pipe_conf);
95+
}
96+
config
97+
}

tests/zjstatus/layout.kdl

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
layout {
22
pane split_direction="vertical" {
33
pane borderless=true
4+
pane command="nvim"
45
}
56

67
pane size=2 borderless=true {
@@ -10,7 +11,7 @@ layout {
1011
color_bg "#181825"
1112

1213
format_left "{mode}#[fg=$blue,bg=$bg,bold] {session}"
13-
format_center "{tabs} {command_3}"
14+
format_center "{tabs} {command_3} {pipe_1}"
1415
format_right "{notifications}{datetime}"
1516
format_space "#[bg=$bg]"
1617
format_precedence "lrc"
@@ -20,6 +21,8 @@ layout {
2021
notification_format_no_notifications "#[fg=$blue,bg=$bg,dim]  "
2122
notification_show_interval "10"
2223

24+
pipe_1_format "#[fg=red] {output}"
25+
2326
border_enabled "true"
2427
border_char "─"
2528
border_format "#[fg=#6C7086]{char}"

0 commit comments

Comments
 (0)