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 54f942defe93 CAMEL-23976: TUI - render .md files with markdown viewer
in file browser
54f942defe93 is described below
commit 54f942defe93bb86f6be50dbd473341ffac41d25
Author: Claus Ibsen <[email protected]>
AuthorDate: Fri Jul 10 08:23:54 2026 +0200
CAMEL-23976: TUI - render .md files with markdown viewer in file browser
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Signed-off-by: Claus Ibsen <[email protected]>
---
.../dsl/jbang/core/commands/tui/SourceViewer.java | 104 +++++++++++++++++++++
1 file changed, 104 insertions(+)
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceViewer.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceViewer.java
index ae8629819280..020b02a75dd5 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceViewer.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceViewer.java
@@ -28,6 +28,7 @@ import java.util.function.IntConsumer;
import dev.tamboui.layout.Constraint;
import dev.tamboui.layout.Layout;
import dev.tamboui.layout.Rect;
+import dev.tamboui.markdown.MarkdownView;
import dev.tamboui.style.Overflow;
import dev.tamboui.style.Style;
import dev.tamboui.terminal.Frame;
@@ -83,6 +84,10 @@ class SourceViewer {
private MonitorContext currentCtx;
private String currentPid;
private Rect lastInnerArea;
+ private boolean isMarkdownFile;
+ private boolean markdownMode;
+ private String rawMarkdownContent;
+ private int markdownScroll;
private record CachedSource(
List<String> lines, List<JsonObject> codeData,
@@ -116,6 +121,10 @@ class SourceViewer {
currentRouteId = null;
currentCtx = null;
currentPid = null;
+ isMarkdownFile = false;
+ markdownMode = false;
+ rawMarkdownContent = null;
+ markdownScroll = 0;
}
void setOnLineSelected(IntConsumer callback) {
@@ -147,6 +156,32 @@ class SourceViewer {
onLineSelected = null;
return true;
}
+ if (isMarkdownFile) {
+ if (ke.isChar('M') || ke.isChar('m')) {
+ markdownMode = true;
+ return true;
+ }
+ if (ke.isChar('R') || ke.isChar('r')) {
+ markdownMode = false;
+ return true;
+ }
+ }
+ if (markdownMode) {
+ if (ke.isUp() || ke.isChar('k')) {
+ markdownScroll = Math.max(0, markdownScroll - 1);
+ } else if (ke.isDown() || ke.isChar('j')) {
+ markdownScroll++;
+ } else if (ke.isPageUp() || ke.isKey(KeyCode.PAGE_UP)) {
+ markdownScroll = Math.max(0, markdownScroll - 10);
+ } else if (ke.isPageDown() || ke.isKey(KeyCode.PAGE_DOWN)) {
+ markdownScroll += 10;
+ } else if (ke.isHome() || ke.isKey(KeyCode.HOME)) {
+ markdownScroll = 0;
+ } else if (ke.isEnd() || ke.isKey(KeyCode.END)) {
+ markdownScroll = Integer.MAX_VALUE;
+ }
+ return true;
+ }
if (currentRouteId != null) {
if (ke.isChar('Y') || ke.isChar('y')) {
switchFormat("yaml");
@@ -220,6 +255,17 @@ class SourceViewer {
if (!visible) {
return false;
}
+ if (markdownMode) {
+ if (me.kind() == MouseEventKind.SCROLL_UP) {
+ markdownScroll = Math.max(0, markdownScroll - 3);
+ return true;
+ }
+ if (me.kind() == MouseEventKind.SCROLL_DOWN) {
+ markdownScroll += 3;
+ return true;
+ }
+ return true;
+ }
if (me.kind() == MouseEventKind.SCROLL_UP) {
selectedLine = Math.max(0, selectedLine - 3);
return true;
@@ -249,6 +295,21 @@ class SourceViewer {
}
void render(Frame frame, Rect area) {
+ if (markdownMode && rawMarkdownContent != null) {
+ Block block = Block.builder()
+ .borderType(BorderType.ROUNDED).borders(Borders.ALL)
+ .title(buildTitle())
+ .build();
+ MarkdownView view = MarkdownView.builder()
+ .source(rawMarkdownContent)
+ .scroll(markdownScroll)
+ .block(block)
+ .styles(Theme.markdownStyles())
+ .build();
+ frame.renderWidget(view, area);
+ return;
+ }
+
Block block = Block.builder()
.borderType(BorderType.ROUNDED).borders(Borders.ALL)
.title(buildTitle())
@@ -325,6 +386,14 @@ class SourceViewer {
}
void renderFooter(List<Span> spans) {
+ if (markdownMode) {
+ TuiHelper.hint(spans, "Esc/c", "close");
+ TuiHelper.hint(spans, TuiIcons.HINT_SCROLL, "scroll");
+ TuiHelper.hint(spans, "M", "markdown");
+ TuiHelper.hint(spans, "R", "raw");
+ TuiHelper.hint(spans, "PgUp/PgDn", "page");
+ return;
+ }
search.renderFooterHints(spans);
if (search.isSearchInputActive()) {
return;
@@ -335,6 +404,10 @@ class SourceViewer {
TuiHelper.hint(spans, "Esc/c", "close");
}
TuiHelper.hint(spans, TuiIcons.HINT_SCROLL, "navigate");
+ if (isMarkdownFile) {
+ TuiHelper.hint(spans, "M", "markdown");
+ TuiHelper.hint(spans, "R", "raw");
+ }
if (currentRouteId != null) {
TuiHelper.hint(spans, "Y", "yaml");
TuiHelper.hint(spans, "J", "java");
@@ -361,6 +434,7 @@ class SourceViewer {
currentCtx = null;
currentPid = null;
String fileName = filePath.getFileName().toString();
+ boolean isMd = fileName.toLowerCase().endsWith(".md");
try {
List<String> rawLines = java.nio.file.Files.readAllLines(filePath,
java.nio.charset.StandardCharsets.UTF_8);
int lineNumWidth = String.valueOf(rawLines.size()).length();
@@ -384,11 +458,23 @@ class SourceViewer {
scrollX = 0;
pendingScroll = true;
visible = true;
+ isMarkdownFile = isMd;
+ if (isMd) {
+ rawMarkdownContent = String.join("\n", rawLines);
+ markdownMode = true;
+ markdownScroll = 0;
+ } else {
+ rawMarkdownContent = null;
+ markdownMode = false;
+ }
} catch (java.io.IOException e) {
title = fileName;
lines = List.of("(Failed to read file: " + e.getMessage() + ")");
codeData = Collections.emptyList();
visible = true;
+ isMarkdownFile = false;
+ markdownMode = false;
+ rawMarkdownContent = null;
}
}
@@ -598,6 +684,24 @@ class SourceViewer {
private Title buildTitle() {
String info = title != null ? title : "";
+ if (isMarkdownFile) {
+ List<Span> spans = new ArrayList<>();
+ spans.add(Span.raw(" Source [" + info + "] "));
+ String mdLabel = "Markdown*";
+ if (markdownMode) {
+ spans.add(Span.styled(mdLabel, Style.EMPTY.bold()));
+ } else {
+ spans.add(Span.styled(mdLabel, Style.EMPTY.dim()));
+ }
+ spans.add(Span.styled(" │ ", Style.EMPTY.dim()));
+ if (!markdownMode) {
+ spans.add(Span.styled("Raw", Style.EMPTY.bold()));
+ } else {
+ spans.add(Span.styled("Raw", Style.EMPTY.dim()));
+ }
+ spans.add(Span.raw(" "));
+ return Title.from(Line.from(spans));
+ }
if (currentRouteId == null) {
return Title.from(Line.from(List.of(Span.raw(" Source [" + info +
"] "))));
}