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 42e0cc14a806 CAMEL-23857: TUI allow to choose runtime when running
from folder or examples
42e0cc14a806 is described below
commit 42e0cc14a80637c8d34c61f266014a6f24c2e039
Author: Claus Ibsen <[email protected]>
AuthorDate: Tue Jun 30 09:37:01 2026 +0200
CAMEL-23857: TUI allow to choose runtime when running from folder or
examples
TUI run options form now lets users choose runtime (Camel Main, Spring Boot,
Quarkus) when running from folder or examples. Export passes exportBaseDir
so
SB/Quarkus exports work from any directory. Added dev/prod profile toggle to
run options form with profile passed to exported application.properties.
Moved profile from MainConfigurationProperties to
DefaultConfigurationProperties
so all runtimes can use it. ProfileConfigurer.configureCommon now sets
tracingStandby for SB/Quarkus dev-mode tracing support.
Browse Files supports navigating subdirectories. Restart disabled for
SB/Quarkus
until reliable.
Closes #24329
Co-Authored-By: Claude <[email protected]>
---
.../main/camel-main-configuration-metadata.json | 2 +-
.../camel-main-configuration-metadata.json | 2 +-
.../camel/main/DefaultConfigurationProperties.java | 33 ++++++++
.../camel/main/MainConfigurationProperties.java | 34 --------
.../org/apache/camel/main/ProfileConfigurer.java | 17 ++--
.../dsl/jbang/core/commands/ExportBaseCommand.java | 5 ++
.../apache/camel/dsl/jbang/core/commands/Run.java | 7 ++
.../dsl/jbang/core/commands/tui/CamelMonitor.java | 16 +++-
.../dsl/jbang/core/commands/tui/FilesBrowser.java | 92 ++++++++++++++++------
.../jbang/core/commands/tui/RunOptionsForm.java | 85 +++++++++++++++++---
10 files changed, 210 insertions(+), 83 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json
index 6f476fc39be7..ebf86b1e192c 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json
@@ -105,7 +105,7 @@
{ "name": "camel.main.modeline", "required": false, "description":
"Whether to support JBang style \/\/DEPS to specify additional dependencies
when running Camel CLI", "sourceType":
"org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean",
"javaType": "boolean", "defaultValue": false, "secret": false },
{ "name": "camel.main.name", "required": false, "description": "Sets the
name of the CamelContext.", "sourceType":
"org.apache.camel.main.DefaultConfigurationProperties", "type": "string",
"javaType": "java.lang.String", "secret": false },
{ "name": "camel.main.producerTemplateCacheSize", "required": false,
"description": "Producer template endpoints cache size.", "sourceType":
"org.apache.camel.main.DefaultConfigurationProperties", "type": "integer",
"javaType": "int", "defaultValue": 1000, "secret": false },
- { "name": "camel.main.profile", "required": false, "description": "Camel
profile to use when running. The dev profile is for development, which enables
a set of additional developer focus functionality, tracing, debugging, and
gathering additional runtime statistics that are useful during development.
However, those additional features has a slight overhead cost, and are not
enabled for production profile. The default profile is prod.", "sourceType":
"org.apache.camel.main.MainConfig [...]
+ { "name": "camel.main.profile", "required": false, "description": "Camel
profile to use when running. The dev profile is for development, which enables
a set of additional developer focus functionality, tracing, debugging, and
gathering additional runtime statistics that are useful during development.
However, those additional features has a slight overhead cost, and are not
enabled for production profile. The default profile is prod.", "sourceType":
"org.apache.camel.main.DefaultCon [...]
{ "name": "camel.main.routeFilterExcludePattern", "required": false,
"description": "Used for filtering routes routes matching the given pattern,
which follows the following rules: - Match by route id - Match by route input
endpoint uri The matching is using exact match, by wildcard and regular
expression as documented by PatternHelper#matchPattern(String,String) . For
example to only include routes which starts with foo in their route id's, use:
include=foo* And to exclude route [...]
{ "name": "camel.main.routeFilterIncludePattern", "required": false,
"description": "Used for filtering routes matching the given pattern, which
follows the following rules: - Match by route id - Match by route input
endpoint uri The matching is using exact match, by wildcard and regular
expression as documented by PatternHelper#matchPattern(String,String) . For
example to only include routes which starts with foo in their route id's, use:
include=foo* And to exclude routes which [...]
{ "name": "camel.main.routesBuilderClasses", "required": false,
"description": "Sets classes names that implement RoutesBuilder .",
"sourceType": "org.apache.camel.main.MainConfigurationProperties", "type":
"string", "javaType": "java.lang.String", "secret": false },
diff --git
a/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
b/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
index 6f476fc39be7..ebf86b1e192c 100644
---
a/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
+++
b/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
@@ -105,7 +105,7 @@
{ "name": "camel.main.modeline", "required": false, "description":
"Whether to support JBang style \/\/DEPS to specify additional dependencies
when running Camel CLI", "sourceType":
"org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean",
"javaType": "boolean", "defaultValue": false, "secret": false },
{ "name": "camel.main.name", "required": false, "description": "Sets the
name of the CamelContext.", "sourceType":
"org.apache.camel.main.DefaultConfigurationProperties", "type": "string",
"javaType": "java.lang.String", "secret": false },
{ "name": "camel.main.producerTemplateCacheSize", "required": false,
"description": "Producer template endpoints cache size.", "sourceType":
"org.apache.camel.main.DefaultConfigurationProperties", "type": "integer",
"javaType": "int", "defaultValue": 1000, "secret": false },
- { "name": "camel.main.profile", "required": false, "description": "Camel
profile to use when running. The dev profile is for development, which enables
a set of additional developer focus functionality, tracing, debugging, and
gathering additional runtime statistics that are useful during development.
However, those additional features has a slight overhead cost, and are not
enabled for production profile. The default profile is prod.", "sourceType":
"org.apache.camel.main.MainConfig [...]
+ { "name": "camel.main.profile", "required": false, "description": "Camel
profile to use when running. The dev profile is for development, which enables
a set of additional developer focus functionality, tracing, debugging, and
gathering additional runtime statistics that are useful during development.
However, those additional features has a slight overhead cost, and are not
enabled for production profile. The default profile is prod.", "sourceType":
"org.apache.camel.main.DefaultCon [...]
{ "name": "camel.main.routeFilterExcludePattern", "required": false,
"description": "Used for filtering routes routes matching the given pattern,
which follows the following rules: - Match by route id - Match by route input
endpoint uri The matching is using exact match, by wildcard and regular
expression as documented by PatternHelper#matchPattern(String,String) . For
example to only include routes which starts with foo in their route id's, use:
include=foo* And to exclude route [...]
{ "name": "camel.main.routeFilterIncludePattern", "required": false,
"description": "Used for filtering routes matching the given pattern, which
follows the following rules: - Match by route id - Match by route input
endpoint uri The matching is using exact match, by wildcard and regular
expression as documented by PatternHelper#matchPattern(String,String) . For
example to only include routes which starts with foo in their route id's, use:
include=foo* And to exclude routes which [...]
{ "name": "camel.main.routesBuilderClasses", "required": false,
"description": "Sets classes names that implement RoutesBuilder .",
"sourceType": "org.apache.camel.main.MainConfigurationProperties", "type":
"string", "javaType": "java.lang.String", "secret": false },
diff --git
a/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationProperties.java
b/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationProperties.java
index 01e3034a48db..c2bba2d5a1c2 100644
---
a/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationProperties.java
+++
b/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationProperties.java
@@ -34,6 +34,8 @@ public abstract class DefaultConfigurationProperties<T> {
private String name;
private String description;
+ @Metadata(enums = "dev,test,prod")
+ private String profile;
@Metadata(defaultValue = "Default", enums =
"Verbose,Default,Brief,Oneline,Off")
private StartupSummaryLevel startupSummaryLevel;
private int durationMaxSeconds;
@@ -190,6 +192,23 @@ public abstract class DefaultConfigurationProperties<T> {
this.description = description;
}
+ public String getProfile() {
+ return profile;
+ }
+
+ /**
+ * Camel profile to use when running.
+ *
+ * The dev profile is for development, which enables a set of additional
developer focus functionality, tracing,
+ * debugging, and gathering additional runtime statistics that are useful
during development. However, those
+ * additional features has a slight overhead cost, and are not enabled for
production profile.
+ *
+ * The default profile is prod.
+ */
+ public void setProfile(String profile) {
+ this.profile = profile;
+ }
+
public StartupSummaryLevel getStartupSummaryLevel() {
return startupSummaryLevel;
}
@@ -1776,6 +1795,20 @@ public abstract class DefaultConfigurationProperties<T> {
return (T) this;
}
+ /**
+ * Camel profile to use when running.
+ *
+ * The dev profile is for development, which enables a set of additional
developer focus functionality, tracing,
+ * debugging, and gathering additional runtime statistics that are useful
during development. However, those
+ * additional features has a slight overhead cost, and are not enabled for
production profile.
+ *
+ * The default profile is prod.
+ */
+ public T withProfile(String profile) {
+ this.profile = profile;
+ return (T) this;
+ }
+
/**
* To specify for how long time in seconds to keep running the JVM before
automatic terminating the JVM. You can use
* this to run Camel for a short while.
diff --git
a/core/camel-main/src/main/java/org/apache/camel/main/MainConfigurationProperties.java
b/core/camel-main/src/main/java/org/apache/camel/main/MainConfigurationProperties.java
index 5a6da245a292..7502b3702ed4 100644
---
a/core/camel-main/src/main/java/org/apache/camel/main/MainConfigurationProperties.java
+++
b/core/camel-main/src/main/java/org/apache/camel/main/MainConfigurationProperties.java
@@ -26,7 +26,6 @@ import org.apache.camel.builder.LambdaRouteBuilder;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.spi.BootstrapCloseable;
import org.apache.camel.spi.Configurer;
-import org.apache.camel.spi.Metadata;
/**
* Global configuration for Camel Main to configure context name, stream
caching and other global configurations.
@@ -35,8 +34,6 @@ import org.apache.camel.spi.Metadata;
public class MainConfigurationProperties extends
DefaultConfigurationProperties<MainConfigurationProperties>
implements BootstrapCloseable {
- @Metadata(enums = "dev,test,prod")
- private String profile;
private boolean autoConfigurationEnabled = true;
private boolean autoConfigurationEnvironmentVariablesEnabled = true;
private boolean autoConfigurationSystemPropertiesEnabled = true;
@@ -506,23 +503,6 @@ public class MainConfigurationProperties extends
DefaultConfigurationProperties<
// getter and setters
// --------------------------------------------------------------
- public String getProfile() {
- return profile;
- }
-
- /**
- * Camel profile to use when running.
- *
- * The dev profile is for development, which enables a set of additional
developer focus functionality, tracing,
- * debugging, and gathering additional runtime statistics that are useful
during development. However, those
- * additional features has a slight overhead cost, and are not enabled for
production profile.
- *
- * The default profile is prod.
- */
- public void setProfile(String profile) {
- this.profile = profile;
- }
-
public boolean isAutoConfigurationEnabled() {
return autoConfigurationEnabled;
}
@@ -852,20 +832,6 @@ public class MainConfigurationProperties extends
DefaultConfigurationProperties<
// fluent builders
// --------------------------------------------------------------
- /**
- * Camel profile to use when running.
- *
- * The dev profile is for development, which enables a set of additional
developer focus functionality, tracing,
- * debugging, and gathering additional runtime statistics that are useful
during development. However, those
- * additional features has a slight overhead cost, and are not enabled for
production profile.
- *
- * The default profile is prod.
- */
- public MainConfigurationProperties withProfile(String profile) {
- this.profile = profile;
- return this;
- }
-
/**
* Whether auto configuration of components/dataformats/languages is
enabled or not. When enabled the configuration
* parameters are loaded from the properties component and configured as
defaults (similar to spring-boot
diff --git
a/core/camel-main/src/main/java/org/apache/camel/main/ProfileConfigurer.java
b/core/camel-main/src/main/java/org/apache/camel/main/ProfileConfigurer.java
index 89c361ca29d5..6abe909806b5 100644
--- a/core/camel-main/src/main/java/org/apache/camel/main/ProfileConfigurer.java
+++ b/core/camel-main/src/main/java/org/apache/camel/main/ProfileConfigurer.java
@@ -24,9 +24,10 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
- * Configure Camel Main with the chosen profile.
+ * Configure Camel with the chosen profile.
*
- * This is for Camel CLI and Standalone Camel, not Spring Boot or Quarkus; as
they have their own profile concept.
+ * The {@link #configureMain} method is for Camel Main (CLI and standalone).
The {@link #configureCommon} method is for
+ * all runtimes (standalone, Spring Boot, Quarkus).
*/
public class ProfileConfigurer {
@@ -48,19 +49,13 @@ public class ProfileConfigurer {
}
if ("dev".equals(profile)) {
- // make tracing at least standby so we can use it in dev-mode
+ // make tracer config at least standby so we can use it in dev-mode
boolean enabled = config.tracerConfig().isEnabled();
if (!enabled) {
config.tracerConfig().withStandby(true);
}
- if (!config.isTracing()) {
- config.setTracingStandby(true);
- }
// enable error registry to capture routing errors
config.errorRegistryConfig().withEnabled(true);
- }
-
- if ("dev".equals(profile)) {
// dev profile allows insecure:dev options by default since those
features
// (devConsole, upload, etc.) are expected in development.
// Users can still override this explicitly via
camel.security.insecureDevPolicy=warn/fail.
@@ -97,6 +92,10 @@ public class ProfileConfigurer {
}
if ("dev".equals(profile)) {
+ // make tracing at least standby so we can use it in dev-mode
+ if (!config.isTracing()) {
+ config.setTracingStandby(true);
+ }
// enable developer features as defaults — user properties can
override any of these
setIfNotConfigured(autoConfigured, "camel.main.devConsoleEnabled",
() -> config.setDevConsoleEnabled(true));
setIfNotConfigured(autoConfigured,
"camel.main.camelEventsTimestampEnabled",
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportBaseCommand.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportBaseCommand.java
index d6bf27e829e1..4465ccc09146 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportBaseCommand.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportBaseCommand.java
@@ -1011,6 +1011,11 @@ public abstract class ExportBaseCommand extends
CamelCommand {
customize.apply(profileProps);
}
+ // include camel profile if set (so exported runtimes like Spring Boot
and Quarkus know the profile)
+ if (this.profile != null &&
!profileProps.containsKey("camel.main.profile")) {
+ profileProps.put("camel.main.profile", this.profile);
+ }
+
StringBuilder content = new StringBuilder();
for (Map.Entry<Object, Object> entry : profileProps.entrySet()) {
String k = entry.getKey().toString();
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java
index efc31fca6eab..f6c852b81348 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java
@@ -1326,6 +1326,7 @@ public class Run extends CamelCommand {
// export to hidden folder
ExportQuarkus eq = new ExportQuarkus(getMain());
+ eq.exportBaseDir = this.exportBaseDir;
eq.javaLiveReload = this.dev;
eq.symbolicLink = this.dev;
eq.mavenWrapper = true;
@@ -1361,6 +1362,7 @@ public class Run extends CamelCommand {
eq.loggingLevel = "off";
eq.ignoreLoadingError = this.ignoreLoadingError;
eq.lazyBean = this.lazyBean;
+ eq.profile = this.profile;
eq.applicationProperties = this.property;
printer().println("Running using Quarkus (preparing and downloading
files)");
@@ -1431,6 +1433,7 @@ public class Run extends CamelCommand {
// export to hidden folder
ExportSpringBoot eq = new ExportSpringBoot(getMain());
+ eq.exportBaseDir = this.exportBaseDir;
// the code reload is not supported, since we use symlink, spring-boot
devtools doesn't support symlink
if (this.dev) {
printer().println("WARN: Code reload is not supported with Spring
Boot.");
@@ -1476,6 +1479,7 @@ public class Run extends CamelCommand {
eq.loggingLevel = "off";
eq.ignoreLoadingError = this.ignoreLoadingError;
eq.lazyBean = this.lazyBean;
+ eq.profile = this.profile;
eq.applicationProperties = this.property;
printer().println("Running using Spring Boot (preparing and
downloading files)");
@@ -1623,6 +1627,9 @@ public class Run extends CamelCommand {
kameletsVersion = answer.getProperty(KAMELETS_VERSION,
kameletsVersion);
springBootVersion = answer.getProperty(SPRING_BOOT_VERSION,
springBootVersion);
javaVersion = answer.getProperty(JAVA_VERSION, javaVersion);
+ if (quarkusPlatform == null) {
+ quarkusPlatform = new QuarkusPlatformMixin();
+ }
quarkusPlatform = QuarkusPlatformMixin.of(answer, quarkusPlatform);
gav = answer.getProperty(GAV, gav);
stub = answer.getProperty(STUB, stub);
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java
index 1d38c39ac4c1..6550316f5b4d 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java
@@ -1195,6 +1195,20 @@ public class CamelMonitor extends CamelCommand {
return;
}
+ String platform = info.platform;
+ boolean isSpringBoot = "Spring Boot".equals(platform);
+ boolean isQuarkus = "Quarkus".equals(platform);
+
+ // TODO: restart for Spring Boot and Quarkus is not yet reliable
+ if (isSpringBoot || isQuarkus) {
+ setNotification("Restart not supported for " + platform, true);
+ return;
+ }
+
+ restartCamelMainProcess(ph, info);
+ }
+
+ private void restartCamelMainProcess(ProcessHandle ph, IntegrationInfo
info) {
// capture command line before stopping
Optional<String> cmdOpt = ph.info().command();
Optional<String[]> argsOpt = ph.info().arguments();
@@ -1260,7 +1274,7 @@ public class CamelMonitor extends CamelCommand {
private void setNotification(String message, boolean error) {
monitorNotification = message;
monitorNotificationError = error;
- monitorNotificationExpiry = System.currentTimeMillis() + 5000;
+ monitorNotificationExpiry = System.currentTimeMillis() + (error ?
15000 : 5000);
}
static List<String> parseCommandLine(String commandLine) {
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FilesBrowser.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FilesBrowser.java
index b6089cf01d64..e6cc0837027f 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FilesBrowser.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/FilesBrowser.java
@@ -45,7 +45,7 @@ import dev.tamboui.widgets.list.ScrollMode;
class FilesBrowser {
- record FileEntry(String emoji, String name, long size, String path) {
+ record FileEntry(String emoji, String name, long size, String path,
boolean directory) {
}
private static final String[] CAMEL_YAML_MARKERS = {
@@ -65,6 +65,8 @@ class FilesBrowser {
private boolean visible;
private String title;
+ private Path rootDir;
+ private Path currentDir;
private final ListState listState = new ListState();
private List<FileEntry> entries = Collections.emptyList();
private final SourceViewer sourceViewer = new SourceViewer();
@@ -83,6 +85,8 @@ class FilesBrowser {
void reset() {
visible = false;
+ rootDir = null;
+ currentDir = null;
entries = Collections.emptyList();
sourceViewer.reset();
}
@@ -92,33 +96,56 @@ class FilesBrowser {
if (dir == null || !Files.isDirectory(dir)) {
return;
}
- List<FileEntry> found = new ArrayList<>();
+ rootDir = dir;
+ currentDir = dir;
+ title = info.name != null ? info.name : "?";
+ sourceViewer.reset();
+ if (!loadDirectory(dir)) {
+ return;
+ }
+ visible = true;
+ }
+
+ private boolean loadDirectory(Path dir) {
+ List<FileEntry> dirs = new ArrayList<>();
+ List<FileEntry> files = new ArrayList<>();
try (var stream = Files.list(dir)) {
- stream.filter(Files::isRegularFile)
- .limit(99)
+ stream.limit(200)
.forEach(p -> {
String name = p.getFileName().toString();
- String emoji = fileEmoji(p);
- long size = 0;
- try {
- size = Files.size(p);
- } catch (IOException e) {
- // ignore
+ if (Files.isDirectory(p)) {
+ dirs.add(new FileEntry("📁", name, -1,
p.toString(), true));
+ } else if (Files.isRegularFile(p)) {
+ String emoji = fileEmoji(p);
+ long size = 0;
+ try {
+ size = Files.size(p);
+ } catch (IOException e) {
+ // ignore
+ }
+ files.add(new FileEntry(emoji, name, size,
p.toString(), false));
}
- found.add(new FileEntry(emoji, name, size,
p.toString()));
});
} catch (IOException e) {
- return;
+ return false;
+ }
+ dirs.sort(Comparator.comparing(FileEntry::name,
String.CASE_INSENSITIVE_ORDER));
+ files.sort(Comparator.comparing(FileEntry::name,
String.CASE_INSENSITIVE_ORDER));
+
+ List<FileEntry> found = new ArrayList<>();
+ if (!dir.equals(rootDir)) {
+ found.add(new FileEntry("📁", "..", -1, dir.getParent().toString(),
true));
}
+ found.addAll(dirs);
+ found.addAll(files);
+
if (found.isEmpty()) {
- return;
+ return false;
}
- found.sort(Comparator.comparing(FileEntry::name,
String.CASE_INSENSITIVE_ORDER));
entries = found;
- title = info.name != null ? info.name : "?";
listState.select(0);
- visible = true;
- sourceViewer.reset();
+ currentDir = dir;
+ return true;
}
boolean handleKeyEvent(KeyEvent ke) {
@@ -148,7 +175,11 @@ class FilesBrowser {
Integer sel = listState.selected();
if (sel != null && sel < entries.size()) {
FileEntry entry = entries.get(sel);
- sourceViewer.loadFile(Path.of(entry.path()));
+ if (entry.directory()) {
+ loadDirectory(Path.of(entry.path()));
+ } else {
+ sourceViewer.loadFile(Path.of(entry.path()));
+ }
}
return true;
}
@@ -168,7 +199,9 @@ class FilesBrowser {
}
int nameWidth = entries.stream().mapToInt(e ->
e.name().length()).max().orElse(10);
- int sizeWidth = entries.stream().mapToInt(e ->
formatFileSize(e.size()).length()).max().orElse(4);
+ int sizeWidth = entries.stream()
+ .filter(e -> !e.directory())
+ .mapToInt(e ->
formatFileSize(e.size()).length()).max().orElse(4);
int itemWidth = 4 + nameWidth + 2 + sizeWidth + 2;
int popupW = Math.min(area.width() - 4, Math.max(30, itemWidth + 4));
int popupH = Math.min(area.height() - 4, entries.size() + 2);
@@ -182,10 +215,21 @@ class FilesBrowser {
ListItem[] items = new ListItem[entries.size()];
for (int i = 0; i < entries.size(); i++) {
FileEntry entry = entries.get(i);
- String sizeStr = formatFileSize(entry.size());
- String label = String.format(" %s %-" + nameWidth + "s %s",
entry.emoji(), entry.name(), sizeStr);
- items[i] = ListItem.from(label);
+ if (entry.directory()) {
+ String label = String.format(" %s %-" + nameWidth + "s",
entry.emoji(), entry.name());
+ items[i] = ListItem.from(label);
+ } else {
+ String sizeStr = formatFileSize(entry.size());
+ String label = String.format(" %s %-" + nameWidth + "s %s",
entry.emoji(), entry.name(), sizeStr);
+ items[i] = ListItem.from(label);
+ }
+ }
+
+ String popupTitle = " Files: " + title;
+ if (rootDir != null && currentDir != null &&
!currentDir.equals(rootDir)) {
+ popupTitle += "/" + rootDir.relativize(currentDir);
}
+ popupTitle += " ";
ListWidget list = ListWidget.builder()
.items(items)
@@ -195,7 +239,7 @@ class FilesBrowser {
.block(Block.builder()
.borderType(BorderType.ROUNDED).borders(Borders.ALL)
.title(Title.from(Line
- .from(Span.styled(" Files: " + title + " ",
Style.EMPTY.fg(Color.YELLOW).bold()))))
+ .from(Span.styled(popupTitle,
Style.EMPTY.fg(Color.YELLOW).bold()))))
.build())
.build();
frame.renderStatefulWidget(list, popup, listState);
@@ -205,7 +249,7 @@ class FilesBrowser {
if (sourceViewer.isVisible()) {
sourceViewer.renderFooter(spans);
} else {
- MonitorContext.hint(spans, "Up/Down", "navigate");
+ MonitorContext.hint(spans, "↑↓", "navigate");
MonitorContext.hint(spans, "Enter", "open");
MonitorContext.hint(spans, "Esc", "close");
}
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RunOptionsForm.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RunOptionsForm.java
index 64c776b53636..b9cfdbb1aac2 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RunOptionsForm.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RunOptionsForm.java
@@ -44,15 +44,17 @@ class RunOptionsForm {
// Row indices for page 0
private static final int ROW_NAME = 0;
- private static final int ROW_PORT = 1;
- private static final int ROW_MAX = 2;
- private static final int ROW_CONSOLE = 3;
- private static final int ROW_DEV = 4;
- private static final int ROW_OBSERVE = 5;
- private static final int ROW_TRACE = 6;
- private static final int ROW_STUB = 7;
- private static final int ROW_OTEL_AGENT = 8;
- private static final int ROW_COUNT = 9;
+ private static final int ROW_RUNTIME = 1;
+ private static final int ROW_PROFILE = 2;
+ private static final int ROW_PORT = 3;
+ private static final int ROW_MAX = 4;
+ private static final int ROW_CONSOLE = 5;
+ private static final int ROW_DEV = 6;
+ private static final int ROW_OBSERVE = 7;
+ private static final int ROW_TRACE = 8;
+ private static final int ROW_STUB = 9;
+ private static final int ROW_OTEL_AGENT = 10;
+ private static final int ROW_COUNT = 11;
private boolean visible;
private int page;
@@ -60,12 +62,17 @@ class RunOptionsForm {
private static final String[] MAX_MODES = { "Max seconds:", "Max
messages:", "Max idle secs:" };
private static final String[] MAX_FLAGS = { "--max-seconds=",
"--max-messages=", "--max-idle-seconds=" };
+ private static final String[] RUNTIME_LABELS = { "Camel Main", "Spring
Boot", "Quarkus" };
+ private static final String[] RUNTIME_VALUES = { "camel-main",
"spring-boot", "quarkus" };
+ private static final String[] PROFILE_LABELS = { "dev", "prod" };
// Text fields
private TextInputState nameInput;
private TextInputState portInput;
private TextInputState maxInput;
private int maxMode;
+ private int runtimeMode;
+ private int profileMode;
// Checkboxes
private boolean devMode;
@@ -96,6 +103,8 @@ class RunOptionsForm {
portInput = new TextInputState("");
maxInput = new TextInputState("");
maxMode = 0;
+ runtimeMode = 0;
+ profileMode = 0;
devMode = dev;
observe = false;
backlogTrace = false;
@@ -161,7 +170,9 @@ class RunOptionsForm {
} else {
hint(spans, "Tab", "next");
}
- if (selectedRow >= ROW_CONSOLE) {
+ if (selectedRow == ROW_RUNTIME || selectedRow == ROW_PROFILE ||
selectedRow == ROW_MAX) {
+ hint(spans, "Space", "cycle");
+ } else if (selectedRow >= ROW_CONSOLE) {
hint(spans, "Space", "toggle");
}
if (hasProperties()) {
@@ -184,6 +195,10 @@ class RunOptionsForm {
if (!name.isEmpty()) {
args.add("--name=" + name);
}
+ if (runtimeMode > 0) {
+ args.add("--runtime=" + RUNTIME_VALUES[runtimeMode]);
+ }
+ args.add("--profile=" + PROFILE_LABELS[profileMode]);
String port = portInput.text().trim();
if (!port.isEmpty()) {
args.add("--port=" + port);
@@ -277,6 +292,26 @@ class RunOptionsForm {
return true;
}
+ // Runtime row: Space or Left/Right cycles
+ if (selectedRow == ROW_RUNTIME) {
+ if (ke.isChar(' ') || ke.isRight()) {
+ runtimeMode = (runtimeMode + 1) % RUNTIME_LABELS.length;
+ return true;
+ }
+ if (ke.isLeft()) {
+ runtimeMode = (runtimeMode - 1 + RUNTIME_LABELS.length) %
RUNTIME_LABELS.length;
+ return true;
+ }
+ }
+
+ // Profile row: Space or Left/Right cycles
+ if (selectedRow == ROW_PROFILE) {
+ if (ke.isChar(' ') || ke.isRight() || ke.isLeft()) {
+ profileMode = (profileMode + 1) % PROFILE_LABELS.length;
+ return true;
+ }
+ }
+
// Max row: Space cycles mode
if (ke.isChar(' ') && selectedRow == ROW_MAX) {
maxMode = (maxMode + 1) % MAX_MODES.length;
@@ -296,8 +331,8 @@ class RunOptionsForm {
return true;
}
- // Text field rows: delegate to active input
- if (selectedRow <= ROW_MAX) {
+ // Text field rows: delegate to active input (skip runtime row — it
uses cycling)
+ if (selectedRow <= ROW_MAX && selectedRow != ROW_RUNTIME &&
selectedRow != ROW_PROFILE) {
TextInputState active = activeInput();
if (active != null) {
handleTextInput(ke, active, selectedRow == ROW_PORT ||
selectedRow == ROW_MAX);
@@ -408,7 +443,7 @@ class RunOptionsForm {
private void renderOptionsPage(Frame frame, Rect area) {
int popupW = Math.min(56, area.width() - 4);
- int popupH = 13;
+ int popupH = 15;
int x = area.left() + Math.max(0, (area.width() - popupW) / 2);
int y = area.top() + Math.max(0, (area.height() - popupH) / 4);
Rect popup = new Rect(x, y, Math.min(popupW, area.width()),
Math.min(popupH, area.height()));
@@ -438,6 +473,14 @@ class RunOptionsForm {
renderTextInput(frame, innerX + labelW, rowY, fieldW, nameInput,
selectedRow == ROW_NAME);
rowY++;
+ renderLabel(frame, innerX, rowY, labelW, "Runtime:", selectedRow ==
ROW_RUNTIME);
+ renderCycler(frame, innerX + labelW, rowY, fieldW, RUNTIME_LABELS,
runtimeMode, selectedRow == ROW_RUNTIME);
+ rowY++;
+
+ renderLabel(frame, innerX, rowY, labelW, "Profile:", selectedRow ==
ROW_PROFILE);
+ renderCycler(frame, innerX + labelW, rowY, fieldW, PROFILE_LABELS,
profileMode, selectedRow == ROW_PROFILE);
+ rowY++;
+
renderLabel(frame, innerX, rowY, labelW, "Port:", selectedRow ==
ROW_PORT);
renderTextInput(frame, innerX + labelW, rowY, fieldW, portInput,
selectedRow == ROW_PORT);
rowY++;
@@ -644,6 +687,22 @@ class RunOptionsForm {
}
}
+ private void renderCycler(Frame frame, int x, int y, int w, String[]
labels, int active, boolean selected) {
+ List<Span> spans = new ArrayList<>();
+ for (int i = 0; i < labels.length; i++) {
+ if (i > 0) {
+ spans.add(Span.styled(" ", Style.EMPTY));
+ }
+ if (i == active) {
+ spans.add(Span.styled("[" + labels[i] + "]", selected ?
Style.EMPTY.bold() : Style.EMPTY));
+ } else {
+ spans.add(Span.styled(" " + labels[i] + " ",
Style.EMPTY.dim()));
+ }
+ }
+ Rect area = new Rect(x, y, w, 1);
+ frame.renderWidget(Paragraph.from(Line.from(spans)), area);
+ }
+
private void renderCheckbox(Frame frame, int x, int y, int w, String
label, boolean checked, boolean selected) {
String box = checked ? "[x]" : "[ ]";
Style style = selected ? Style.EMPTY.bold().reversed() : Style.EMPTY;