Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
# Changelog

## Unreleased
## 0.7.0

- The help fits short terminals: no banner, two columns when one is too tall, and it scrolls
only when neither fits. Shift+drag text selection is listed under NODES.
- `m` turns the mouse off and on, so a plain drag selects text; the zoomed pane has no side
borders, so a selection there is just the log text.

## 0.6.0

Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "jamboree"
version = "0.6.0"
version = "0.7.0"
edition = "2024"
license = "MIT OR Apache-2.0"
description = "Launch a local network of any JAM client, watch every node's logs side by side, and break it on purpose"
Expand Down
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ Every run is saved to `logs/<timestamp>/node_<i>.log`, with the command line in
- `c` clears the search and the filter.
- `Enter` zooms into the focused pane, `Esc` goes back, `1`-`6` hide or show node 0 to 5.
- `--search 'error,warning'` sets the terms at start, also with `--logs`.
- To copy log lines: `Enter` to zoom the pane, scroll to them, `m` to turn the mouse off, then
drag to select and copy as usual. The zoomed pane has no side borders, so the selection is
just the log text. `m` again turns the mouse back on.

## Read saved logs

Expand Down Expand Up @@ -304,10 +307,12 @@ jamboree --nodes 6 --move-ports --listen-port 41000 \
| `c` | Clear the search and the filter |
| `s` `i` `p` `r` | Stop, isolate, split, restart |
| `x` | Shut down all nodes (TERM, then KILL after 3 s); their logs stay |
| `m` | Mouse off, so a plain drag selects text / on again |
| `?` | Help (two columns on a short terminal, `↑` `↓` scroll it when even that does not fit) |
| `q` | Quit (stops all nodes first) |

The mouse is on, so to select text hold Shift while dragging (Option in iTerm2).
While the mouse is on, Shift+drag also selects text (Option+drag in iTerm2), but in the grid it
takes the borders and neighbouring panes too; zoom first.

### Config file

Expand Down
4 changes: 4 additions & 0 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ pub struct AppState {
pub panes: RefCell<Vec<(usize, Rect)>>,
/// Node and time of the last click, to tell a double click
pub last_click: Option<(usize, Instant)>,
/// Mouse capture off (`m`), so a plain drag selects text in the terminal
pub mouse_off: bool,
}

impl AppState {
Expand Down Expand Up @@ -116,6 +118,7 @@ impl AppState {
last_key: None,
panes: RefCell::new(Vec::new()),
last_click: None,
mouse_off: false,
}
}

Expand All @@ -139,6 +142,7 @@ impl AppState {
last_key: None,
panes: RefCell::new(Vec::new()),
last_click: None,
mouse_off: false,
}
}

Expand Down
26 changes: 25 additions & 1 deletion src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ use crate::AppState;
use crate::ViewMode;
use crate::net::Net;
use crate::node::{self, Node};
use crossterm::event::{KeyCode, KeyModifiers, MouseButton, MouseEvent, MouseEventKind};
use crossterm::event::{
DisableMouseCapture, EnableMouseCapture, KeyCode, KeyModifiers, MouseButton, MouseEvent,
MouseEventKind,
};
use crossterm::execute;
use std::io::stdout;
use ratatui::layout::Position;
use std::time::{Duration, Instant};

Expand Down Expand Up @@ -246,6 +251,16 @@ pub fn handle_key_event(
Action::Continue
}

// Mouse off: a plain drag selects text in the terminal; on again for click and wheel
KeyCode::Char('m') => {
app.mouse_off = !app.mouse_off;
let _ = match app.mouse_off {
true => execute!(stdout(), DisableMouseCapture),
false => execute!(stdout(), EnableMouseCapture),
};
Action::Continue
}

// Quit
KeyCode::Char('q') => Action::Quit,

Expand Down Expand Up @@ -696,6 +711,15 @@ mod tests {
assert!(matches!(press(&mut ui, &["Esc"]), Action::ChangeView(ViewMode::Grid)));
}

#[test]
fn m_turns_the_mouse_off_and_on_again() {
let mut ui = panes(1);
press(&mut ui, &["m"]);
assert!(ui.0.mouse_off);
press(&mut ui, &["m"]);
assert!(!ui.0.mouse_off);
}

#[test]
fn help_scrolls_with_the_arrows_and_any_other_key_only_closes_it() {
let mut ui = panes(1);
Expand Down
7 changes: 4 additions & 3 deletions src/ui/focused.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,9 @@ pub fn render(f: &mut Frame, node: &Node, app: &AppState, net: Option<&Net>) {
node.resize_pty_if_needed(new_size);

// Fast tail extraction with wrap calculation for focused view
let visible_lines = area.height.saturating_sub(2) as usize; // borders
let available_width = area.width.saturating_sub(2).max(1) as usize;
// Only a top border (the title), so a terminal selection takes no border characters
let visible_lines = area.height.saturating_sub(1) as usize;
let available_width = area.width.max(1) as usize;

let raw_text = node.get_colored_tail(5_000_000);

Expand Down Expand Up @@ -102,7 +103,7 @@ pub fn render(f: &mut Frame, node: &Node, app: &AppState, net: Option<&Net>) {
super::grid::fault_tags(net, node),
super::grid::frozen_tag(app, node)
))
.borders(Borders::ALL)
.borders(Borders::TOP)
.border_style(Style::default().fg(Color::Green)),
)
.wrap(Wrap { trim: false });
Expand Down
3 changes: 2 additions & 1 deletion src/ui/help.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ const SECTIONS: [(&str, &[(&str, &str)]); 4] = [
("NODES", &[
("← → h l Tab", "Focus previous / next node"),
("click", "Focus a node; double-click to zoom"),
("Shift+drag", "Select text (Option+drag in iTerm2)"),
("m", "Mouse off: drag selects text (m again: on)"),
("Shift+drag", "Select with the mouse on (iTerm2: Option)"),
("Enter z", "Zoom into the focused node"),
("Esc", "{esc}"),
("1-6", "Hide or show node 0 to 5"),
Expand Down
15 changes: 13 additions & 2 deletions src/ui/menus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const MATCH_HINT: &str = " [n/N] Match [c] Clear ";
const MATCH_HINT_ESC: &str = " [n/N] Match [c/Esc] Clear ";
const PAUSED_INDICATOR: &str = " [ALL FROZEN: Space resumes]";
const GREP_INDICATOR: &str = " [FILTER ON]";
const MOUSE_OFF_INDICATOR: &str = " [MOUSE OFF: drag selects, m restores]";

/// The base menu, with Ctrl+G offered as "Show all" while the filter is on
fn base(menu: &str, app: &AppState) -> String {
Expand All @@ -41,7 +42,12 @@ pub fn grid_menu(app: &AppState, has_tags: bool) -> String {
_ => "",
};

let grep_status = if app.grep_mode { GREP_INDICATOR } else { "" };
let grep_status = match (app.mouse_off, app.grep_mode) {
(true, true) => format!("{MOUSE_OFF_INDICATOR}{GREP_INDICATOR}"),
(true, false) => MOUSE_OFF_INDICATOR.to_string(),
(false, true) => GREP_INDICATOR.to_string(),
(false, false) => String::new(),
};

let match_hint = match has_tags {
true if app.esc_clears_search => MATCH_HINT_ESC,
Expand Down Expand Up @@ -72,7 +78,12 @@ pub fn focused_menu(app: &AppState, has_tags: bool) -> String {
_ => "",
};

let grep_status = if app.grep_mode { GREP_INDICATOR } else { "" };
let grep_status = match (app.mouse_off, app.grep_mode) {
(true, true) => format!("{MOUSE_OFF_INDICATOR}{GREP_INDICATOR}"),
(true, false) => MOUSE_OFF_INDICATOR.to_string(),
(false, true) => GREP_INDICATOR.to_string(),
(false, false) => String::new(),
};

let match_hint = match has_tags {
true if app.esc_clears_search => MATCH_HINT_ESC,
Expand Down
Loading