This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch camel-jbang-diagram-zoom
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 386ac67879fd22ea9b600685703c783c65fadda7
Author: Claus Ibsen <[email protected]>
AuthorDate: Sun Oct 25 14:20:06 2026 +0100

    chore: camel-jbang - route diagram: Esc back from a linked route selects 
the step the link was followed from
    
    Esc from a route reached by Enter on a step that calls it went back to
    the from of the calling route, at the top. It now selects the step the
    link was followed from again, on its row of the screen.
    
    Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../core/commands/tui/AbstractDiagramTab.java      | 24 ++++++++++++++
 .../jbang/core/commands/tui/DiagramSupport.java    |  3 ++
 .../core/commands/tui/AbstractDiagramTabTest.java  | 37 ++++++++++++++++++++++
 3 files changed, 64 insertions(+)

diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AbstractDiagramTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AbstractDiagramTab.java
index b409939287a8..5f20173f244c 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AbstractDiagramTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/AbstractDiagramTab.java
@@ -18,6 +18,8 @@ package org.apache.camel.dsl.jbang.core.commands.tui;
 
 import java.util.ArrayDeque;
 import java.util.Deque;
+import java.util.HashMap;
+import java.util.Map;
 
 import dev.tamboui.tui.event.KeyCode;
 import dev.tamboui.tui.event.KeyEvent;
@@ -53,6 +55,16 @@ abstract class AbstractDiagramTab extends AbstractTab {
     protected String drillDownRouteId;
     /** The routes followed from a node that calls another route, for Esc to 
walk back. */
     protected final Deque<String> routeNavigationStack = new ArrayDeque<>();
+    /**
+     * The step each route on the trail was left from (the one calling the 
next route), for Esc to select it again on
+     * its row of the screen. Following a link sets the entry of the route it 
leaves, so an old entry is never used.
+     */
+    protected final Map<String, LinkOrigin> linkOrigins = new HashMap<>();
+
+    /** Where a route was left by following a link: the step the link is on, 
and its row on the screen. */
+    record LinkOrigin(String nodeId, int screenRow) {
+    }
+
     /** Whether the detail panel of the selected node is shown, beside the 
route diagram. */
     protected boolean detailMode;
 
@@ -251,9 +263,15 @@ abstract class AbstractDiagramTab extends AbstractTab {
         }
         if (!topologyMode) {
             if (!routeNavigationStack.isEmpty()) {
+                var left = diagram.getRouteLayout(drillDownRouteId);
                 drillDownRouteId = routeNavigationStack.pop();
                 diagram.selectFromNode(drillDownRouteId);
                 diagram.resetScroll();
+                LinkOrigin origin = linkOrigins.remove(drillDownRouteId);
+                if (origin != null) {
+                    // back on the step the link was followed from, where it 
was on the screen
+                    diagram.zoom.anchor(left, origin.nodeId(), 
origin.screenRow());
+                }
                 return true;
             }
             leaveRoute();
@@ -367,6 +385,12 @@ abstract class AbstractDiagramTab extends AbstractTab {
             routeNavigationStack.pop();
         } else {
             routeNavigationStack.push(drillDownRouteId);
+            var from = diagram.getSelectedEipNodeBox();
+            if (from != null && from.nodeId() != null) {
+                linkOrigins.put(drillDownRouteId, new 
LinkOrigin(from.nodeId(), from.startRow() - diagram.getScrollY()));
+            } else {
+                linkOrigins.remove(drillDownRouteId);
+            }
         }
         drillDownRouteId = linkedRouteId;
         diagram.selectFromNode(linkedRouteId);
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramSupport.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramSupport.java
index dd06303792d1..f04310b81e56 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramSupport.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramSupport.java
@@ -1597,6 +1597,9 @@ class DiagramSupport {
         eipNodeBoxes = new ArrayList<>(finalWidget.getNodeBoxes());
         if (selectedEipNodeIndex < 0 && !eipNodeBoxes.isEmpty()) {
             selectedEipNodeIndex = 0;
+        } else if (restore != null) {
+            // a step on a branch to the right is scrolled to as well
+            scrollToSelectedEipNode();
         }
 
         vScrollState.contentLength(totalRows);
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/AbstractDiagramTabTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/AbstractDiagramTabTest.java
index 1bfe1f67065b..2dd43afa024c 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/AbstractDiagramTabTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/AbstractDiagramTabTest.java
@@ -22,6 +22,10 @@ import java.util.function.Function;
 import java.util.stream.Stream;
 
 import dev.tamboui.tui.event.KeyEvent;
+import org.apache.camel.diagram.RouteDiagramLayoutEngine;
+import org.apache.camel.diagram.RouteDiagramLayoutEngine.LayoutRoute;
+import org.apache.camel.diagram.RouteDiagramLayoutEngine.NodeInfo;
+import org.apache.camel.diagram.RouteDiagramLayoutEngine.RouteInfo;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.MethodSource;
 
@@ -81,6 +85,39 @@ class AbstractDiagramTabTest {
         assertThat(tab.detailMode).isFalse();
     }
 
+    @ParameterizedTest
+    @MethodSource("tabs")
+    void escSelectsTheStepTheLinkWasFollowedFrom(Function<MonitorContext, 
AbstractDiagramTab> factory) {
+        AbstractDiagramTab tab = inRoute(factory, true);
+        tab.linkOrigins.put("r0", new AbstractDiagramTab.LinkOrigin("to2", 7));
+
+        assertThat(tab.handleEscape()).isTrue();
+        assertThat(tab.drillDownRouteId).isEqualTo("r0");
+
+        // the diagram of r0, drawn next, selects the step that called r1, on 
its row of the screen
+        RouteInfo r0 = new RouteInfo();
+        r0.routeId = "r0";
+        r0.nodes.add(node("route", "r0", 0));
+        r0.nodes.add(node("from", "from1", 1));
+        r0.nodes.add(node("to", "to1", 2));
+        r0.nodes.add(node("to", "to2", 2));
+        LayoutRoute layout = new RouteDiagramLayoutEngine().layoutRoute(r0, 0);
+        RouteDiagramZoom.Restore restore = ctx.routeZoom.restore(layout);
+        assertThat(restore).isNotNull();
+        assertThat(restore.index()).isEqualTo(2);
+        assertThat(restore.screenRow()).isEqualTo(7);
+        assertThat(tab.linkOrigins).doesNotContainKey("r0");
+    }
+
+    private static NodeInfo node(String type, String id, int level) {
+        NodeInfo n = new NodeInfo();
+        n.type = type;
+        n.id = id;
+        n.level = level;
+        n.code = type + "[" + id + "]";
+        return n;
+    }
+
     @ParameterizedTest
     @MethodSource("tabs")
     void dTogglesTheDetailPanelOnlyWithNodesToShow(Function<MonitorContext, 
AbstractDiagramTab> factory) {

Reply via email to