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 51a65ad835f1 CAMEL-23637: camel-jbang - TUI add Run Infra Service
action to F2 menu (#23607)
51a65ad835f1 is described below
commit 51a65ad835f17e42833eb8142db52c269f940db5
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu May 28 20:42:05 2026 +0200
CAMEL-23637: camel-jbang - TUI add Run Infra Service action to F2 menu
(#23607)
Co-authored-by: Claude <[email protected]>
---
.../dsl/jbang/core/commands/tui/ActionsPopup.java | 435 ++++++++++++++++++++-
1 file changed, 418 insertions(+), 17 deletions(-)
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java
index e569c49861fc..e448c3cc73f5 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java
@@ -17,11 +17,15 @@
package org.apache.camel.dsl.jbang.core.commands.tui;
import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Iterator;
+import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.concurrent.ScheduledExecutorService;
import java.util.function.Supplier;
@@ -41,15 +45,21 @@ import dev.tamboui.widgets.Clear;
import dev.tamboui.widgets.block.Block;
import dev.tamboui.widgets.block.BorderType;
import dev.tamboui.widgets.block.Title;
+import dev.tamboui.widgets.input.TextInput;
+import dev.tamboui.widgets.input.TextInputState;
import dev.tamboui.widgets.list.ListItem;
import dev.tamboui.widgets.list.ListState;
import dev.tamboui.widgets.list.ListWidget;
import dev.tamboui.widgets.list.ScrollMode;
import dev.tamboui.widgets.paragraph.Paragraph;
+import org.apache.camel.catalog.CamelCatalog;
+import org.apache.camel.catalog.DefaultCamelCatalog;
import org.apache.camel.dsl.jbang.core.common.ExampleHelper;
import org.apache.camel.dsl.jbang.core.common.LauncherHelper;
import org.apache.camel.dsl.jbang.core.common.PathUtils;
+import org.apache.camel.util.json.JsonArray;
import org.apache.camel.util.json.JsonObject;
+import org.apache.camel.util.json.Jsoner;
import static org.apache.camel.dsl.jbang.core.commands.tui.MonitorContext.hint;
import static
org.apache.camel.dsl.jbang.core.commands.tui.MonitorContext.hintLast;
@@ -59,27 +69,29 @@ class ActionsPopup {
// Group 1: User Actions
private static final int ACTION_SEND_MESSAGE = 0;
private static final int ACTION_RUN_EXAMPLE = 1;
- private static final int ACTION_SHOW_DOCS = 2;
+ private static final int ACTION_RUN_INFRA = 2;
+ private static final int ACTION_SHOW_DOCS = 3;
// Group 2: Diagnostics
- private static final int ACTION_DOCTOR = 3;
- private static final int ACTION_CLASSPATH = 4;
- private static final int ACTION_RESET_STATS = 5;
- private static final int ACTION_STOP_ALL = 6;
+ private static final int ACTION_DOCTOR = 4;
+ private static final int ACTION_CLASSPATH = 5;
+ private static final int ACTION_RESET_STATS = 6;
+ private static final int ACTION_STOP_ALL = 7;
// Group 3: Recording & Presentation
- private static final int ACTION_SCREENSHOT = 7;
- private static final int ACTION_TAPE_RECORDING = 8;
- private static final int ACTION_TAPE_INSTRUCTIONS = 9;
- private static final int ACTION_CAPTION = 10;
- private static final int ACTION_SHOW_KEYSTROKES = 11;
+ private static final int ACTION_SCREENSHOT = 8;
+ private static final int ACTION_TAPE_RECORDING = 9;
+ private static final int ACTION_TAPE_INSTRUCTIONS = 10;
+ private static final int ACTION_CAPTION = 11;
+ private static final int ACTION_SHOW_KEYSTROKES = 12;
// Group 4: MCP
- private static final int ACTION_MCP_INFO = 12;
- private static final int ACTION_MCP_LOG = 13;
+ private static final int ACTION_MCP_INFO = 13;
+ private static final int ACTION_MCP_LOG = 14;
- private static final int[] GROUP_SIZES = { 3, 4, 5 };
+ private static final int[] GROUP_SIZES = { 4, 4, 5 };
private static final int MCP_GROUP_SIZE = 2;
private final Supplier<Set<String>> runningNames;
private final Supplier<List<IntegrationInfo>> integrations;
+ private final Supplier<List<InfraInfo>> infraServices;
private final Runnable screenshotAction;
private final Runnable toggleKeystrokes;
private final Supplier<Boolean> keystrokesEnabled;
@@ -112,6 +124,14 @@ class ActionsPopup {
private String docTitle;
private int docScroll;
+ private boolean showInfraBrowser;
+ private final ListState infraBrowserState = new ListState();
+ private List<InfraServiceEntry> infraCatalog;
+ private boolean showInfraPortDialog;
+ private InfraServiceEntry selectedInfraService;
+ private int infraImplIndex;
+ private TextInputState infraPortState;
+
private final McpLogPopup mcpLogPopup = new McpLogPopup();
private final DoctorPopup doctorPopup = new DoctorPopup();
@@ -134,6 +154,7 @@ class ActionsPopup {
Runnable toggleTapeRecording, Supplier<Boolean>
tapeRecordingActive) {
this.runningNames = runningNames;
this.integrations = integrations;
+ this.infraServices = infraServices;
this.captionOverlay = captionOverlay;
this.screenshotAction = screenshotAction;
this.toggleKeystrokes = toggleKeystrokes;
@@ -170,11 +191,11 @@ class ActionsPopup {
private int visualActionCount() {
if (mcpEnabled) {
- // 3 + 4 + 5 + 2 actions = 14, plus 3 dividers = 17
- return 14 + 3;
+ // 4 + 4 + 5 + 2 actions = 15, plus 3 dividers = 18
+ return 15 + 3;
} else {
- // 3 + 4 + 5 actions = 12, plus 2 dividers = 14
- return 12 + 2;
+ // 4 + 4 + 5 actions = 13, plus 2 dividers = 15
+ return 13 + 2;
}
}
@@ -227,11 +248,17 @@ class ActionsPopup {
boolean isVisible() {
return showActionsMenu || showExampleBrowser ||
runOptionsForm.isVisible() || showDocPicker || showDocViewer
+ || showInfraBrowser || showInfraPortDialog
|| mcpLogPopup.isVisible() || doctorPopup.isVisible() ||
classpathPopup.isVisible()
|| sendMessagePopup.isVisible() || stopAllPopup.isVisible() ||
captionOverlay.isInlineMode();
}
SelectionContext getSelectionContext() {
+ if (showInfraBrowser && infraCatalog != null) {
+ List<String> items = infraCatalog.stream().map(e ->
e.alias).collect(Collectors.toList());
+ Integer sel = infraBrowserState.selected();
+ return new SelectionContext("list", items, sel != null ? sel : -1,
infraCatalog.size(), "Infra Services");
+ }
if (showExampleBrowser && exampleCatalog != null) {
List<String> items = new ArrayList<>();
String currentLevel = null;
@@ -268,6 +295,7 @@ class ActionsPopup {
// Group 1: User Actions
labels.add("Send Message");
labels.add("Run an example...");
+ labels.add("Run Infra Service...");
labels.add("Show Documentation");
labels.add("───");
// Group 2: Diagnostics
@@ -302,6 +330,8 @@ class ActionsPopup {
runOptionsForm.close();
showDocPicker = false;
showDocViewer = false;
+ showInfraBrowser = false;
+ showInfraPortDialog = false;
mcpLogPopup.close();
doctorPopup.close();
classpathPopup.close();
@@ -367,6 +397,44 @@ class ActionsPopup {
}
return true;
}
+ if (showInfraPortDialog) {
+ if (ke.isCancel()) {
+ showInfraPortDialog = false;
+ showInfraBrowser = true;
+ } else if (ke.isConfirm()) {
+ launchInfraService();
+ } else if (selectedInfraService != null &&
selectedInfraService.implementations.size() > 1) {
+ if (ke.isLeft()) {
+ infraImplIndex = (infraImplIndex - 1 +
selectedInfraService.implementations.size())
+ %
selectedInfraService.implementations.size();
+ return true;
+ } else if (ke.isRight()) {
+ infraImplIndex = (infraImplIndex + 1) %
selectedInfraService.implementations.size();
+ return true;
+ }
+ }
+ handlePortInput(ke);
+ return true;
+ }
+ if (showInfraBrowser) {
+ if (ke.isCancel()) {
+ showInfraBrowser = false;
+ showActionsMenu = true;
+ } else if (ke.isUp()) {
+ navigateInfraBrowser(-1);
+ } else if (ke.isDown()) {
+ navigateInfraBrowser(1);
+ } else if (ke.isPageUp() || ke.isKey(KeyCode.PAGE_UP)) {
+ navigateInfraBrowser(-10);
+ } else if (ke.isPageDown() || ke.isKey(KeyCode.PAGE_DOWN)) {
+ navigateInfraBrowser(10);
+ } else if (ke.isConfirm()) {
+ selectInfraService();
+ } else if (ke.code() == KeyCode.CHAR) {
+ jumpToInfraService(ke.character());
+ }
+ return true;
+ }
if (runOptionsForm.isVisible()) {
if (ke.isCancel()) {
runOptionsForm.close();
@@ -445,6 +513,8 @@ class ActionsPopup {
} else if (action == ACTION_CLASSPATH) {
showActionsMenu = false;
openClasspath();
+ } else if (action == ACTION_RUN_INFRA) {
+ openInfraBrowser();
} else if (action == ACTION_MCP_INFO) {
showActionsMenu = false;
openMcpInfo();
@@ -478,6 +548,12 @@ class ActionsPopup {
if (showActionsMenu) {
renderActionsMenu(frame, area);
}
+ if (showInfraBrowser) {
+ renderInfraBrowser(frame, area);
+ }
+ if (showInfraPortDialog) {
+ renderInfraPortDialog(frame, area);
+ }
if (showExampleBrowser) {
renderExampleBrowser(frame, area);
}
@@ -546,6 +622,17 @@ class ActionsPopup {
runOptionsForm.renderFooter(spans);
return;
}
+ if (showInfraPortDialog) {
+ hint(spans, "Enter", "run");
+ hintLast(spans, "Esc", "back");
+ return;
+ }
+ if (showInfraBrowser) {
+ hint(spans, "↑↓", "navigate");
+ hint(spans, "Enter", "select");
+ hintLast(spans, "Esc", "back");
+ return;
+ }
if (showExampleBrowser) {
hint(spans, "↑↓", "navigate");
hint(spans, "Enter", "run");
@@ -595,6 +682,7 @@ class ActionsPopup {
// Group 1: User Actions
items.add(ListItem.from(" 📩 Send Message"));
items.add(ListItem.from(" 🐪 Run an example..."));
+ items.add(ListItem.from(" 🔧 Run Infra Service..."));
items.add(ListItem.from(" 📖 Show Documentation"));
items.add(ListItem.from(divider).style(Style.EMPTY.dim()));
// Group 2: Diagnostics
@@ -1225,6 +1313,316 @@ class ActionsPopup {
return null;
}
+ // ---- Infra Browser ----
+
+ private void openInfraBrowser() {
+ showActionsMenu = false;
+ if (infraCatalog == null) {
+ infraCatalog = loadInfraCatalog();
+ }
+ if (infraCatalog.isEmpty()) {
+ setNotification("No infra services found", true);
+ return;
+ }
+ showInfraBrowser = true;
+ infraBrowserState.select(0);
+ // skip to first non-running service
+ if (!infraCatalog.isEmpty() && infraCatalog.get(0).running) {
+ navigateInfraBrowser(1);
+ }
+ }
+
+ private List<InfraServiceEntry> loadInfraCatalog() {
+ try {
+ CamelCatalog catalog = new DefaultCamelCatalog();
+ try (InputStream is = catalog.loadResource("test-infra",
"metadata.json")) {
+ if (is == null) {
+ return List.of();
+ }
+ String json = new String(is.readAllBytes(),
StandardCharsets.UTF_8);
+ JsonArray arr = (JsonArray) Jsoner.deserialize(json);
+ Map<String, InfraServiceEntry> byAlias = new LinkedHashMap<>();
+ for (Object obj : arr) {
+ JsonObject svc = (JsonObject) obj;
+ String desc = (String) svc.getOrDefault("description", "");
+ JsonArray aliases = (JsonArray) svc.get("alias");
+ JsonArray impls = (JsonArray)
svc.get("aliasImplementation");
+ if (aliases != null) {
+ for (Object a : aliases) {
+ String alias = a.toString();
+ InfraServiceEntry entry = byAlias.get(alias);
+ if (entry == null) {
+ entry = new InfraServiceEntry(alias, desc, new
ArrayList<>(), false);
+ byAlias.put(alias, entry);
+ }
+ if (impls != null) {
+ for (Object impl : impls) {
+ String implStr = impl.toString();
+ if
(!entry.implementations.contains(implStr)) {
+ entry.implementations.add(implStr);
+ }
+ }
+ }
+ }
+ }
+ }
+ // Mark running services
+ Set<String> runningAliases = infraServices.get().stream()
+ .filter(i -> i.alive)
+ .map(i -> i.alias)
+ .collect(Collectors.toSet());
+ List<InfraServiceEntry> result = new ArrayList<>();
+ for (InfraServiceEntry entry : byAlias.values()) {
+ boolean running = runningAliases.contains(entry.alias);
+ result.add(new InfraServiceEntry(entry.alias,
entry.description, entry.implementations, running));
+ }
+ result.sort((a, b) -> a.alias.compareToIgnoreCase(b.alias));
+ return result;
+ }
+ } catch (Exception e) {
+ return List.of();
+ }
+ }
+
+ private void refreshInfraRunningState() {
+ if (infraCatalog == null) {
+ return;
+ }
+ Set<String> runningAliases = infraServices.get().stream()
+ .filter(i -> i.alive)
+ .map(i -> i.alias)
+ .collect(Collectors.toSet());
+ List<InfraServiceEntry> refreshed = new ArrayList<>();
+ for (InfraServiceEntry entry : infraCatalog) {
+ boolean running = runningAliases.contains(entry.alias);
+ refreshed.add(new InfraServiceEntry(entry.alias,
entry.description, entry.implementations, running));
+ }
+ infraCatalog = refreshed;
+ }
+
+ private void navigateInfraBrowser(int direction) {
+ if (infraCatalog == null || infraCatalog.isEmpty()) {
+ return;
+ }
+ int total = infraCatalog.size();
+ Integer current = infraBrowserState.selected();
+ int next = (current != null ? current : 0) + direction;
+ next = Math.max(0, Math.min(next, total - 1));
+ // skip running services
+ while (next >= 0 && next < total && infraCatalog.get(next).running) {
+ next += direction;
+ }
+ next = Math.max(0, Math.min(next, total - 1));
+ if (!infraCatalog.get(next).running) {
+ infraBrowserState.select(next);
+ }
+ }
+
+ private void selectInfraService() {
+ Integer sel = infraBrowserState.selected();
+ if (sel == null || sel >= infraCatalog.size()) {
+ return;
+ }
+ InfraServiceEntry entry = infraCatalog.get(sel);
+ if (entry.running) {
+ return;
+ }
+ selectedInfraService = entry;
+ infraImplIndex = 0;
+ infraPortState = new TextInputState("");
+ showInfraBrowser = false;
+ showInfraPortDialog = true;
+ }
+
+ private void jumpToInfraService(char ch) {
+ if (infraCatalog == null) {
+ return;
+ }
+ char lower = Character.toLowerCase(ch);
+ for (int i = 0; i < infraCatalog.size(); i++) {
+ if
(infraCatalog.get(i).alias.toLowerCase().startsWith(String.valueOf(lower))) {
+ infraBrowserState.select(i);
+ infraBrowserState.setOffset(Math.max(0, i - 2));
+ return;
+ }
+ }
+ }
+
+ private void renderInfraBrowser(Frame frame, Rect area) {
+ if (infraCatalog == null || infraCatalog.isEmpty()) {
+ return;
+ }
+ refreshInfraRunningState();
+ int popupW = Math.min(100, area.width() - 4);
+ int popupH = Math.min(infraCatalog.size() + 2, Math.min(22,
area.height() - 6));
+ int x = area.left() + Math.max(0, (area.width() - popupW) / 2);
+ int y = area.top() + Math.max(0, (area.height() - popupH) / 2);
+ Rect popup = new Rect(x, y, Math.min(popupW, area.width()),
Math.min(popupH, area.height()));
+
+ frame.renderWidget(Clear.INSTANCE, popup);
+
+ int nameCol = 22;
+ List<ListItem> items = new ArrayList<>();
+ for (InfraServiceEntry entry : infraCatalog) {
+ String padded = String.format("%-" + nameCol + "s",
TuiHelper.truncate(entry.alias, nameCol));
+ String prefix = " 🔧 " + padded + " ";
+ if (entry.running) {
+ items.add(ListItem.from(prefix +
"(running)").style(Style.EMPTY.dim()));
+ } else {
+ String implStr = entry.implementations.isEmpty() ? "" :
String.join(", ", entry.implementations);
+ String desc = entry.description;
+ if (!implStr.isEmpty()) {
+ desc = desc + " [" + implStr + "]";
+ }
+ int descW = Math.max(10, popupW - prefix.length() - 2);
+ if (desc.length() <= descW) {
+ items.add(ListItem.from(prefix + desc));
+ } else {
+ String indent = " ".repeat(prefix.length());
+ List<Line> lines = new ArrayList<>();
+ List<String> wrapped = wrapWords(desc, descW);
+ lines.add(Line.from(prefix + wrapped.get(0)));
+ for (int w = 1; w < wrapped.size(); w++) {
+ lines.add(Line.from(indent + wrapped.get(w)));
+ }
+
items.add(ListItem.from(Text.from(lines.toArray(Line[]::new))));
+ }
+ }
+ }
+
+ long available = infraCatalog.stream().filter(e -> !e.running).count();
+ ListWidget list = ListWidget.builder()
+ .items(items.toArray(ListItem[]::new))
+ .highlightStyle(Style.EMPTY.fg(Color.WHITE).bold().onBlue())
+ .highlightSymbol("")
+ .scrollMode(ScrollMode.AUTO_SCROLL)
+ .block(Block.builder()
+ .borderType(BorderType.ROUNDED)
+ .title(" Run Infra Service (" + available + "/" +
infraCatalog.size() + ") ")
+ .titleBottom(Title.from(Line.from(
+ Span.styled(" Enter",
MonitorContext.HINT_KEY_STYLE), Span.raw(" select │"),
+ Span.styled(" ↑↓",
MonitorContext.HINT_KEY_STYLE), Span.raw(" navigate │"),
+ Span.styled(" Esc",
MonitorContext.HINT_KEY_STYLE), Span.raw(" back "))))
+ .build())
+ .build();
+ frame.renderStatefulWidget(list, popup, infraBrowserState);
+ }
+
+ // ---- Infra Port Dialog ----
+
+ private void renderInfraPortDialog(Frame frame, Rect area) {
+ if (selectedInfraService == null) {
+ return;
+ }
+ boolean hasMultiImpl = selectedInfraService.implementations.size() > 1;
+ int popupW = 42;
+ int popupH = hasMultiImpl ? 8 : 6;
+ int x = area.left() + Math.max(0, (area.width() - popupW) / 2);
+ int y = area.top() + Math.max(0, (area.height() - popupH) / 2);
+ Rect popup = new Rect(x, y, Math.min(popupW, area.width()),
Math.min(popupH, area.height()));
+
+ frame.renderWidget(Clear.INSTANCE, popup);
+
+ Block block = Block.builder()
+ .borderType(BorderType.ROUNDED)
+ .title(" Run " + selectedInfraService.alias + " ")
+ .titleBottom(Title.from(Line.from(
+ Span.styled(" Enter", MonitorContext.HINT_KEY_STYLE),
Span.raw(" run │"),
+ Span.styled(" Esc", MonitorContext.HINT_KEY_STYLE),
Span.raw(" back "))))
+ .build();
+ frame.renderWidget(block, popup);
+ Rect inner = block.inner(popup);
+
+ int labelW = 8;
+ int fieldW = inner.width() - labelW;
+ int row = inner.top();
+ int ix = inner.left();
+
+ // Implementation selector (if multiple)
+ if (hasMultiImpl) {
+ row++;
+ Rect labelArea = new Rect(ix, row, labelW, 1);
+ frame.renderWidget(Paragraph.from(Line.from(Span.styled("Impl:",
Style.EMPTY.bold()))), labelArea);
+ String impl =
selectedInfraService.implementations.get(infraImplIndex);
+ Rect implArea = new Rect(ix + labelW, row, fieldW, 1);
+ frame.renderWidget(Paragraph.from(Line.from(
+ Span.styled("◀ ", MonitorContext.HINT_KEY_STYLE),
+ Span.raw(impl),
+ Span.styled(" ▶", MonitorContext.HINT_KEY_STYLE))),
implArea);
+ row++;
+ }
+
+ // Port input
+ row++;
+ Rect labelArea = new Rect(ix, row, labelW, 1);
+ frame.renderWidget(Paragraph.from(Line.from(Span.styled("Port:",
Style.EMPTY.bold()))), labelArea);
+ Rect portArea = new Rect(ix + labelW, row, fieldW, 1);
+ TextInput textInput = TextInput.builder()
+ .cursorStyle(Style.EMPTY.reversed())
+ .placeholder("default")
+ .build();
+ frame.renderStatefulWidget(textInput, portArea, infraPortState);
+ }
+
+ private void handlePortInput(KeyEvent ke) {
+ if (infraPortState == null) {
+ return;
+ }
+ if (ke.isDeleteBackward()) {
+ infraPortState.deleteBackward();
+ } else if (ke.isDeleteForward()) {
+ infraPortState.deleteForward();
+ } else if (ke.isLeft()) {
+ infraPortState.moveCursorLeft();
+ } else if (ke.isRight()) {
+ infraPortState.moveCursorRight();
+ } else if (ke.isHome()) {
+ infraPortState.moveCursorToStart();
+ } else if (ke.isEnd()) {
+ infraPortState.moveCursorToEnd();
+ } else if (ke.code() == KeyCode.CHAR &&
Character.isDigit(ke.character())) {
+ infraPortState.insert(ke.character());
+ }
+ }
+
+ private void launchInfraService() {
+ if (selectedInfraService == null) {
+ return;
+ }
+ String alias = selectedInfraService.alias;
+ String impl = null;
+ if (!selectedInfraService.implementations.isEmpty()) {
+ impl = selectedInfraService.implementations.get(infraImplIndex);
+ }
+ String portStr = infraPortState != null ? infraPortState.text().trim()
: "";
+ showInfraPortDialog = false;
+ try {
+ List<String> cmd = new
ArrayList<>(LauncherHelper.getCamelCommand());
+ cmd.add("infra");
+ cmd.add("run");
+ cmd.add(alias);
+ if (impl != null) {
+ cmd.add(impl);
+ }
+ cmd.add("--background");
+ if (!portStr.isEmpty()) {
+ cmd.add("--port=" + portStr);
+ }
+ Path outputFile = Files.createTempFile("camel-infra-", ".log");
+ outputFile.toFile().deleteOnExit();
+ ProcessBuilder pb = new ProcessBuilder(cmd);
+ pb.redirectErrorStream(true);
+ pb.redirectOutput(outputFile.toFile());
+ Process process = pb.start();
+ pendingLaunches.add(new PendingLaunch(alias, process, outputFile,
System.currentTimeMillis()));
+ setNotification("Starting infra: " + alias, false);
+ // force reload next time browser opens
+ infraCatalog = null;
+ } catch (Exception e) {
+ setNotification("Failed to start infra: " + alias + " - " +
e.getMessage(), true);
+ }
+ }
+
// ---- Process Launch & Monitoring ----
private void launchSelectedExample() {
@@ -1332,6 +1730,9 @@ class ActionsPopup {
return lines;
}
+ record InfraServiceEntry(String alias, String description, List<String>
implementations, boolean running) {
+ }
+
private record PendingLaunch(String name, Process process, Path
outputFile, long startTime) {
}
}