This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 6cf3e73dfc20 CAMEL-26864: camel-jbang - the Log says paused or live as
the Timeline does, and a message without a body reads (no body) (#29657)
6cf3e73dfc20 is described below
commit 6cf3e73dfc2048b1d2891d6b25de9f9bb52be20c
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu Nov 12 07:38:18 2026 +0100
CAMEL-26864: camel-jbang - the Log says paused or live as the Timeline
does, and a message without a body reads (no body) (#29657)
- Log: a green ● live or a reversed ‖ PAUSED badge in the title, a warning
border while paused, and Space pauses and resumes as on the Timeline (f and End
still work)
- Source, Routes, Diagram and Inspect show the body type "null" as (no
body), and the kind null as no body
Signed-off-by: Claus Ibsen <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
---
.../dsl/jbang/core/commands/tui/BodyTypes.java | 19 +++++++++-----
.../dsl/jbang/core/commands/tui/HistoryTab.java | 2 +-
.../camel/dsl/jbang/core/commands/tui/LogTab.java | 26 ++++++++++++++-----
.../src/main/resources/tui/help/log.md | 2 +-
.../core/commands/tui/BodyTypesViewsTest.java | 18 ++++++++++---
.../jbang/core/commands/tui/LogTabRenderTest.java | 30 ++++++++++++++++++++++
6 files changed, 79 insertions(+), 18 deletions(-)
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/BodyTypes.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/BodyTypes.java
index 47034b59dbdd..bb9cb78482b3 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/BodyTypes.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/BodyTypes.java
@@ -32,6 +32,12 @@ public final class BodyTypes {
/** The mark of a step reached with bodies of more than one kind. */
public static final String MARK = TuiIcons.WARN;
+ /**
+ * A message without a body, which the app gives as the type "null"
(MessageHelper): the word null alone reads as a
+ * class name.
+ */
+ public static final String NO_BODY = "(no body)";
+
private BodyTypes() {
}
@@ -40,9 +46,9 @@ public final class BodyTypes {
return kinds != null && kinds.size() > 1;
}
- /** The simple class name of a type: LinkedHashMap, byte[]. */
+ /** The simple class name of a type: LinkedHashMap, byte[], or (no body).
*/
public static String simpleName(String type) {
- return type.substring(type.lastIndexOf('.') + 1);
+ return "null".equals(type) ? NO_BODY :
type.substring(type.lastIndexOf('.') + 1);
}
/** The types by their simple class names: String | LinkedHashMap. */
@@ -54,12 +60,13 @@ public final class BodyTypes {
return sj.toString();
}
- /** The kinds in words: payload and data, or null, payload and data. */
+ /** The kinds in words: payload and data, or no body, payload and data. */
public static String kinds(List<String> kinds) {
- if (kinds.size() < 2) {
- return String.join("", kinds);
+ List<String> words = kinds.stream().map(k -> "null".equals(k) ? "no
body" : k).toList();
+ if (words.size() < 2) {
+ return String.join("", words);
}
- return String.join(", ", kinds.subList(0, kinds.size() - 1)) + " and "
+ kinds.get(kinds.size() - 1);
+ return String.join(", ", words.subList(0, words.size() - 1)) + " and "
+ words.get(words.size() - 1);
}
/**
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HistoryTab.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HistoryTab.java
index d4d8355be238..f67b230380c2 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HistoryTab.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HistoryTab.java
@@ -2152,7 +2152,7 @@ class HistoryTab extends AbstractTab {
lines.add(Line.from(Span.raw(" " + stripControlChars(bl))));
}
} else {
- lines.add(Line.from(Span.styled(" Body is null", headerStyle)));
+ lines.add(Line.from(Span.styled(" Body: ", headerStyle),
Span.styled(BodyTypes.NO_BODY, Style.EMPTY.dim())));
}
lines.add(Line.from(Span.raw("")));
}
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LogTab.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LogTab.java
index 5b77c1478fed..fb01ffa8c93c 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LogTab.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LogTab.java
@@ -209,7 +209,8 @@ class LogTab extends AbstractTab {
logLevelListState.select(2);
return true;
}
- if (ke.isCharIgnoreCase('f')) {
+ if (ke.isChar(' ') || ke.isCharIgnoreCase('f')) {
+ // Space pauses and resumes, as on the Timeline
followMode = !followMode;
return true;
}
@@ -355,18 +356,17 @@ class LogTab extends AbstractTab {
} else {
titleSpans.add(Span.raw(" Log "));
}
- if (hasNew) {
- // follow mode is off (a scroll or Home/PgUp switches it off) and
lines arrived below the view
- titleSpans.add(Span.styled("(paused, End follows)",
Theme.warning()));
- titleSpans.add(Span.raw(" "));
- }
+ titleSpans.add(followBadge(hasNew));
+ titleSpans.add(Span.raw(" "));
if (ctx.logPinned && ctx.logPinPercent > 0 && ctx.logPinVisible) {
titleSpans.add(Span.styled(" Ctrl+L ", Theme.hintKey()));
titleSpans.add(Span.raw(" pin (" + ctx.logPinPercent + "%) "));
}
titleLine = Line.from(titleSpans);
+ // paused, the border is in the warning colour, so a log that stays
put does not pass for one that follows
Block block = Block.builder()
.borderType(BorderType.ROUNDED).borders(Borders.ALL)
+ .borderStyle(followMode ? Style.EMPTY : Theme.warning())
.title(Title.from(titleLine))
.build();
frame.renderWidget(block, area);
@@ -485,6 +485,18 @@ class LogTab extends AbstractTab {
}
}
+ /**
+ * The badge of the title, the same as on the Timeline: a green {@code ●
live} while the log follows the newest
+ * lines, a reversed {@code ‖ PAUSED} when it stays put (Space, f, a
scroll, Home or PgUp), which says so when there
+ * are lines below the view.
+ */
+ Span followBadge(boolean hasNew) {
+ if (followMode) {
+ return Span.styled("● live", Theme.success().bold());
+ }
+ return Span.styled(" ‖ PAUSED" + (hasNew ? ", more below " : " "),
Theme.warning().bold().reversed());
+ }
+
@Override
public void renderFooter(List<Span> spans) {
search.renderFooterHints(spans);
@@ -513,7 +525,7 @@ class LogTab extends AbstractTab {
if (!ctx.isInfraSelected()) {
hint(spans, "l", "level");
}
- hint(spans, "f", "follow" + (followMode ? " [on]" : " [off]"));
+ hint(spans, "Space", followMode ? "pause" : "resume");
if (ctx.logPinned) {
hint(spans, "Ctrl+L", "pin (" + ctx.logPinPercent + "%)");
} else {
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/log.md
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/log.md
index ea077b993d5a..973071a63f48 100644
--- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/log.md
+++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/log.md
@@ -102,7 +102,7 @@ newest first; the same error repeated is listed once with
its count.
- `h` — highlight a word
- `l` — change log level
- `Shift+F8` — fix with AI: the ERROR on the screen (a list to pick from when
there are several)
-- `f` — toggle follow mode (`End` turns it on). Scrolling with the mouse
wheel, `Up`, `PgUp` or `Home` turns it off; the title then shows `(paused, End
follows)` when new lines arrive below the view
+- `Space` — pause or resume, as on the Timeline (`f` does the same, and `End`
resumes). Scrolling with the mouse wheel, `Up`, `PgUp` or `Home` pauses too.
The title says `● live` while the log follows the newest lines, and a reversed
`‖ PAUSED` in a warning border while it stays put, with `more below` when there
are lines below the view
- `w` — toggle word wrap
- `c` — toggle the compact view (on by default): the time, level, logger and
message of each line, without the date, pid and thread
- `Ctrl+L` — pin/cycle/unpin log panel (works from any tab)
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/BodyTypesViewsTest.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/BodyTypesViewsTest.java
index a27775637353..633408affbd9 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/BodyTypesViewsTest.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/BodyTypesViewsTest.java
@@ -21,6 +21,7 @@ import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import dev.tamboui.text.Line;
+import dev.tamboui.text.Span;
import org.apache.camel.diagram.RouteDiagramLayoutEngine;
import org.apache.camel.util.json.JsonObject;
import org.apache.camel.util.json.Jsoner;
@@ -113,11 +114,22 @@ class BodyTypesViewsTest {
assertThat(BodyTypes.label(List.of("java.lang.String", "byte[]"),
List.of("payload")))
.isEqualTo("String | byte[]");
assertThat(BodyTypes.label(List.of("null", "java.util.LinkedHashMap"),
List.of("null", "data")))
- .isEqualTo(TuiIcons.WARN + " null | LinkedHashMap (null and
data)");
+ .isEqualTo(TuiIcons.WARN + " (no body) | LinkedHashMap (no
body and data)");
assertThat(BodyTypes.label(List.of(), List.of())).isEmpty();
assertThat(BodyTypes.shortLabel(List.of("null",
"java.util.LinkedHashMap"), List.of("null", "data")))
- .isEqualTo(TuiIcons.WARN + " null | LinkedHashMap");
- assertThat(BodyTypes.kinds(List.of("null", "payload",
"data"))).isEqualTo("null, payload and data");
+ .isEqualTo(TuiIcons.WARN + " (no body) | LinkedHashMap");
+ assertThat(BodyTypes.kinds(List.of("null", "payload",
"data"))).isEqualTo("no body, payload and data");
+ // CAMEL-26864: a step reached without a body, as the setBody after a
timer, says so, not "null"
+ assertThat(BodyTypes.label(List.of("null"),
List.of("null"))).isEqualTo("(no body)");
+ assertThat(BodyTypes.fit(List.of("null"), List.of("null"),
20)).isEqualTo("(no body)");
+ }
+
+ @Test
+ void inspectSaysAMessageHasNoBody() {
+ List<Line> lines = new ArrayList<>();
+ HistoryTab.addBodyLines(lines, null, "null", -1, false);
+ String text =
lines.get(0).spans().stream().map(Span::content).reduce("", String::concat);
+ assertThat(text).isEqualTo(" Body: (no body)");
}
@Test
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/LogTabRenderTest.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/LogTabRenderTest.java
index a871c6e9c700..84f1cd914575 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/LogTabRenderTest.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/LogTabRenderTest.java
@@ -110,6 +110,36 @@ class LogTabRenderTest {
assertEquals(LogTab.View.COMPACT, tab.view());
}
+ @Test
+ void spacePausesAndResumesWithTheBadgeOfTheTimeline() {
+ List<LogEntry> entries = new ArrayList<>();
+ for (int i = 0; i < 40; i++) {
+ entries.add(LogTab.parseLogLine("2026-11-11 10:00:" +
String.format("%02d", i)
+ + ".000 INFO 42 --- [main] route1
: Line " + i));
+ }
+ LogTab tab = new LogTab(ctx);
+ tab.setEntriesForTesting(entries);
+
+ String live = TuiTestHelper.renderToString(tab, 140, 12);
+ assertTrue(live.contains("● live"), live);
+ assertTrue(footer(tab).matches(".*Space +pause.*"), footer(tab));
+
+ // CAMEL-26864: Space pauses, as on the Timeline; the badge says so,
not only the footer
+ assertTrue(tab.handleKeyEvent(KeyEvent.ofChar(' ')));
+ String paused = TuiTestHelper.renderToString(tab, 140, 12);
+ assertTrue(paused.contains("‖ PAUSED") && !paused.contains("● live"),
paused);
+ assertTrue(footer(tab).matches(".*Space +resume.*"), footer(tab));
+
+ tab.pageUp();
+ String above = TuiTestHelper.renderToString(tab, 140, 12);
+ assertTrue(above.contains("‖ PAUSED, more below"), above);
+
+ // End (and f) still resume
+ tab.handleKeyEvent(KeyEvent.ofKey(KeyCode.END));
+ String resumed = TuiTestHelper.renderToString(tab, 140, 12);
+ assertTrue(resumed.contains("● live") && resumed.contains("Line 39"),
resumed);
+ }
+
private static String footer(LogTab tab) {
List<Span> spans = new ArrayList<>();
tab.renderFooter(spans);