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 7bafca9f9ef6 CAMEL-24260: TUI quick docs for Spring Boot configuration
metadata
7bafca9f9ef6 is described below
commit 7bafca9f9ef625cb688807198ff2bdd58deda93a
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Jul 27 12:50:15 2026 +0200
CAMEL-24260: TUI quick docs for Spring Boot configuration metadata
CAMEL-24260: TUI gutter warning triangle for deprecated properties
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Signed-off-by: Claus Ibsen <[email protected]>
---
.../camel/cli/connector/LocalCliConnector.java | 19 ++++
.../jbang/core/commands/tui/ConfigurationTab.java | 31 +++++-
.../dsl/jbang/core/commands/tui/DiagramTab.java | 4 +-
.../dsl/jbang/core/commands/tui/RoutesTab.java | 42 ++++---
.../dsl/jbang/core/commands/tui/SourceTab.java | 95 ++++++++++++++--
.../dsl/jbang/core/commands/tui/SourceViewer.java | 61 ++++++++--
.../commands/tui/SpringBootMetadataHelper.java | 124 +++++++++++++++++++++
7 files changed, 337 insertions(+), 39 deletions(-)
diff --git
a/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java
b/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java
index ad33fe6a3e93..9dd068726e57 100644
---
a/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java
+++
b/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java
@@ -388,6 +388,8 @@ public class LocalCliConnector extends ServiceSupport
implements CliConnector, C
doActionJfrMemoryLeakTask(root);
} else if ("cli-debug".equals(action)) {
doActionCliDebug(root);
+ } else if ("spring-boot-configuration".equals(action)) {
+ doActionSpringBootConfigurationTask(root);
}
} catch (Exception e) {
LOG.warn("Error executing action: {} due to: {}. This exception is
ignored.", action != null ? action : af,
@@ -886,6 +888,23 @@ public class LocalCliConnector extends ServiceSupport
implements CliConnector, C
}
}
+ private void doActionSpringBootConfigurationTask(JsonObject root) throws
IOException {
+ DevConsole dc =
camelContext.getCamelContextExtension().getContextPlugin(DevConsoleRegistry.class)
+ .resolveById("spring-boot-configuration");
+ if (dc != null) {
+ Map<String, Object> params = new HashMap<>();
+ String filter = root.getString("filter");
+ if (filter != null) {
+ params.put("filter", filter);
+ }
+ JsonObject json = (JsonObject) dc.call(DevConsole.MediaType.JSON,
params);
+ LOG.trace("Updating output file: {}", outputFile);
+ IOHelper.writeText(json.toJson(), outputFile);
+ } else {
+ IOHelper.writeText("{}", outputFile);
+ }
+ }
+
private void doActionHeapDumpTask(JsonObject root) throws IOException {
DevConsole dc =
camelContext.getCamelContextExtension().getContextPlugin(DevConsoleRegistry.class)
.resolveById("heap-dump");
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ConfigurationTab.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ConfigurationTab.java
index f0d9634a8539..4e4e1066b76c 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ConfigurationTab.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ConfigurationTab.java
@@ -61,6 +61,10 @@ class ConfigurationTab extends AbstractTableTab {
private Map<String, BaseOptionModel> mainOptionsMap;
private final Map<String, Map<String, BaseOptionModel>>
componentOptionsCache = new HashMap<>();
+ // Spring Boot configuration metadata cache (lazy-loaded on-demand via IPC)
+ private Map<String, JsonObject> springBootMetadataCache;
+ private boolean springBootMetadataLoaded;
+
ConfigurationTab(MonitorContext ctx) {
super(ctx, "key", "value", "source");
}
@@ -318,13 +322,36 @@ class ConfigurationTab extends AbstractTableTab {
String optionName = rest.substring(dot + 1);
Map<String, BaseOptionModel> compOptions =
getComponentOptions(componentName);
if (compOptions != null) {
- return compOptions.get(optionName);
+ BaseOptionModel opt = compOptions.get(optionName);
+ if (opt != null) {
+ return opt;
+ }
}
}
}
+ // fallback to Spring Boot configuration metadata
+ ensureSpringBootMetadataCache();
+ if (springBootMetadataCache != null) {
+ JsonObject sbProp = springBootMetadataCache.get(key);
+ if (sbProp != null) {
+ return SpringBootMetadataHelper.toOptionModel(sbProp);
+ }
+ }
return null;
}
+ private void ensureSpringBootMetadataCache() {
+ if (springBootMetadataLoaded) {
+ return;
+ }
+ springBootMetadataLoaded = true;
+ IntegrationInfo info = ctx.findSelectedIntegration();
+ if (info == null || !"Spring Boot".equals(info.platform)) {
+ return;
+ }
+ springBootMetadataCache = SpringBootMetadataHelper.fetchMetadata(ctx,
info.pid);
+ }
+
private void initCatalog() {
IntegrationInfo info = ctx.findSelectedIntegration();
String version = info != null ? info.camelVersion : null;
@@ -342,6 +369,8 @@ class ConfigurationTab extends AbstractTableTab {
catalogVersion = version;
mainOptionsMap = new HashMap<>();
componentOptionsCache.clear();
+ springBootMetadataCache = null;
+ springBootMetadataLoaded = false;
MainModel mainModel = catalog.mainModel();
if (mainModel != null) {
for (MainModel.MainOptionModel opt : mainModel.getOptions()) {
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramTab.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramTab.java
index 7c7ac2116bac..6160d6260185 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramTab.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramTab.java
@@ -1420,7 +1420,7 @@ class DiagramTab extends AbstractTab {
// ---- Quick doc (i toggle in source viewer) ----
- private Map<Integer, List<String>> provideAllQuickDocs(List<JsonObject>
cd) {
+ private Map<Integer, List<SourceViewer.DocEntry>>
provideAllQuickDocs(List<JsonObject> cd) {
if (cachedRouteDetail == null || cd.isEmpty()) {
return Map.of();
}
@@ -1433,7 +1433,7 @@ class DiagramTab extends AbstractTab {
IntegrationInfo info = ctx.findSelectedIntegration();
CamelCatalog catalog = info != null ? getCatalog(info) : null;
- Map<Integer, List<String>> result = new LinkedHashMap<>();
+ Map<Integer, List<SourceViewer.DocEntry>> result = new
LinkedHashMap<>();
for (JsonObject proc : processors) {
Integer line = proc.getInteger("line");
if (line == null || line <= 0) {
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RoutesTab.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RoutesTab.java
index 6590530ad796..a8142f5fe957 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RoutesTab.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RoutesTab.java
@@ -2058,7 +2058,7 @@ class RoutesTab extends AbstractTab {
// ---- Quick doc (q toggle in source viewer) ----
- private Map<Integer, List<String>> provideAllQuickDocs(List<JsonObject>
cd) {
+ private Map<Integer, List<SourceViewer.DocEntry>>
provideAllQuickDocs(List<JsonObject> cd) {
if (cachedRouteDetail == null || cd.isEmpty()) {
return Map.of();
}
@@ -2071,7 +2071,7 @@ class RoutesTab extends AbstractTab {
IntegrationInfo info = ctx.findSelectedIntegration();
CamelCatalog catalog = info != null ? getCatalog(info) : null;
- Map<Integer, List<String>> result = new LinkedHashMap<>();
+ Map<Integer, List<SourceViewer.DocEntry>> result = new
LinkedHashMap<>();
for (JsonObject proc : processors) {
Integer line = proc.getInteger("line");
if (line == null || line <= 0) {
@@ -2111,7 +2111,7 @@ class RoutesTab extends AbstractTab {
}
static void buildEndpointInlineDoc(
- Map<Integer, List<String>> result, List<JsonObject> cd,
+ Map<Integer, List<SourceViewer.DocEntry>> result, List<JsonObject>
cd,
CamelCatalog catalog, String endpointUri, int eipIdx) {
String component = endpointUri.contains(":") ?
endpointUri.substring(0, endpointUri.indexOf(':')) : endpointUri;
@@ -2131,8 +2131,8 @@ class RoutesTab extends AbstractTab {
}
int beforeSize = result.size();
- List<String> titleLines = new ArrayList<>();
- titleLines.add(compTitle + " — " + desc);
+ List<SourceViewer.DocEntry> titleLines = new ArrayList<>();
+ titleLines.add(SourceViewer.DocEntry.of(compTitle + " — " + desc));
result.put(eipIdx, titleLines);
inlineParameterDocs(result, cd, eipIdx, optionDocs);
@@ -2145,7 +2145,7 @@ class RoutesTab extends AbstractTab {
}
private static void clusterEndpointOptions(
- List<String> docLines, CamelCatalog catalog,
+ List<SourceViewer.DocEntry> docLines, CamelCatalog catalog,
String endpointUri, Map<String, BaseOptionModel> optionDocs) {
try {
Map<String, String> props =
catalog.endpointProperties(endpointUri);
@@ -2155,7 +2155,10 @@ class RoutesTab extends AbstractTab {
if (optModel != null) {
String optDoc = formatOptionDoc(optModel);
if (optDoc != null) {
- docLines.add(entry.getKey() + ": " +
entry.getValue() + " — " + optDoc);
+ String text = entry.getKey() + ": " +
entry.getValue() + " — " + optDoc;
+ docLines.add(optModel.isDeprecated()
+ ? SourceViewer.DocEntry.deprecated(text)
+ : SourceViewer.DocEntry.of(text));
}
}
}
@@ -2166,7 +2169,7 @@ class RoutesTab extends AbstractTab {
}
static void buildEipInlineDoc(
- Map<Integer, List<String>> result, List<JsonObject> cd,
+ Map<Integer, List<SourceViewer.DocEntry>> result, List<JsonObject>
cd,
CamelCatalog catalog, String type, JsonObject opts, int eipIdx) {
EipModel model = catalog != null ? catalog.eipModel(type) : null;
@@ -2182,7 +2185,7 @@ class RoutesTab extends AbstractTab {
}
}
- List<String> titleLines = new ArrayList<>();
+ List<SourceViewer.DocEntry> titleLines = new ArrayList<>();
if (model != null && model.getTitle() != null) {
String eipTitle = model.getTitle();
if (compModel != null && compModel.getTitle() != null) {
@@ -2194,9 +2197,9 @@ class RoutesTab extends AbstractTab {
} else {
desc = model.getDescription() != null ?
truncateText(model.getDescription(), 80) : "";
}
- titleLines.add(eipTitle + " — " + desc);
+ titleLines.add(SourceViewer.DocEntry.of(eipTitle + " — " + desc));
} else {
- titleLines.add(type);
+ titleLines.add(SourceViewer.DocEntry.of(type));
}
int beforeSize = result.size();
@@ -2228,7 +2231,7 @@ class RoutesTab extends AbstractTab {
}
private static void clusterEipOptions(
- List<String> docLines, JsonObject opts,
+ List<SourceViewer.DocEntry> docLines, JsonObject opts,
Map<String, BaseOptionModel> optionDocs, boolean skipUri) {
for (Map.Entry<String, Object> entry : opts.entrySet()) {
if (skipUri && "uri".equals(entry.getKey())) {
@@ -2238,7 +2241,10 @@ class RoutesTab extends AbstractTab {
if (optModel != null) {
String optDoc = formatOptionDoc(optModel);
if (optDoc != null) {
- docLines.add(entry.getKey() + ": " + entry.getValue() + "
— " + optDoc);
+ String text = entry.getKey() + ": " + entry.getValue() + "
— " + optDoc;
+ docLines.add(optModel.isDeprecated()
+ ? SourceViewer.DocEntry.deprecated(text)
+ : SourceViewer.DocEntry.of(text));
}
}
}
@@ -2261,7 +2267,7 @@ class RoutesTab extends AbstractTab {
}
static void inlineParameterDocs(
- Map<Integer, List<String>> result, List<JsonObject> cd,
+ Map<Integer, List<SourceViewer.DocEntry>> result, List<JsonObject>
cd,
int eipIdx, Map<String, BaseOptionModel> optionDocs) {
if (optionDocs.isEmpty()) {
@@ -2302,7 +2308,9 @@ class RoutesTab extends AbstractTab {
String docLine = formatOptionDoc(doc);
if (docLine != null) {
int docIdx = lastContinuationLine(cd, i,
indent);
- result.put(docIdx, List.of(docLine));
+ result.put(docIdx, List.of(doc.isDeprecated()
+ ?
SourceViewer.DocEntry.deprecated(docLine)
+ : SourceViewer.DocEntry.of(docLine)));
}
}
}
@@ -2336,7 +2344,9 @@ class RoutesTab extends AbstractTab {
String docLine = formatOptionDoc(doc);
if (docLine != null) {
int docIdx = lastContinuationLine(cd, i, indent);
- result.put(docIdx, List.of(docLine));
+ result.put(docIdx, List.of(doc.isDeprecated()
+ ? SourceViewer.DocEntry.deprecated(docLine)
+ : SourceViewer.DocEntry.of(docLine)));
}
}
}
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceTab.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceTab.java
index bafa66b66f53..601c2ab12859 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceTab.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceTab.java
@@ -26,9 +26,11 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -92,6 +94,10 @@ class SourceTab extends AbstractTab {
private final Map<String, Map<String, BaseOptionModel>>
languageOptionsCache = new HashMap<>();
private final Map<String, Map<String, BaseOptionModel>>
dataformatOptionsCache = new HashMap<>();
+ // Spring Boot configuration metadata cache (lazy-loaded on-demand via IPC)
+ private Map<String, JsonObject> springBootMetadataCache;
+ private boolean springBootMetadataLoaded;
+
private static final Pattern YAML_URI_PATTERN = Pattern.compile(
"^\\s*-?\\s*(?:uri|from|to|toD|wireTap|enrich|pollEnrich|deadLetterChannel):\\s*\"?([a-zA-Z][a-zA-Z0-9+.-]*:[^\"\\s]*)");
private static final Pattern YAML_EIP_PATTERN = Pattern.compile(
@@ -476,14 +482,17 @@ class SourceTab extends AbstractTab {
loadDirectory(Path.of(entry.path()));
} else {
Path filePath = Path.of(entry.path());
- sourceViewer.loadFile(filePath);
if (isCamelSourceFile(filePath)) {
sourceViewer.setQuickDocProvider(this::provideCamelQuickDocs);
+ sourceViewer.setDeprecatedLineScanner(null);
} else if (isPropertiesFile(filePath)) {
sourceViewer.setQuickDocProvider(this::providePropertiesQuickDocs);
+
sourceViewer.setDeprecatedLineScanner(this::scanDeprecatedProperties);
} else {
sourceViewer.setQuickDocProvider(null);
+ sourceViewer.setDeprecatedLineScanner(null);
}
+ sourceViewer.loadFile(filePath);
focusOnViewer = true;
}
}
@@ -525,13 +534,13 @@ class SourceTab extends AbstractTab {
}
}
- private Map<Integer, List<String>> provideCamelQuickDocs(List<JsonObject>
codeData) {
+ private Map<Integer, List<SourceViewer.DocEntry>>
provideCamelQuickDocs(List<JsonObject> codeData) {
CamelCatalog catalog = getCatalog();
if (catalog == null || codeData.isEmpty()) {
return Map.of();
}
- Map<Integer, List<String>> result = new LinkedHashMap<>();
+ Map<Integer, List<SourceViewer.DocEntry>> result = new
LinkedHashMap<>();
for (int i = 0; i < codeData.size(); i++) {
String code = codeData.get(i).getString("code");
if (code == null) {
@@ -561,14 +570,14 @@ class SourceTab extends AbstractTab {
return
path.getFileName().toString().toLowerCase().endsWith(".properties");
}
- private Map<Integer, List<String>>
providePropertiesQuickDocs(List<JsonObject> codeData) {
+ private Map<Integer, List<SourceViewer.DocEntry>>
providePropertiesQuickDocs(List<JsonObject> codeData) {
CamelCatalog catalog = getCatalog();
if (catalog == null || codeData.isEmpty()) {
return Map.of();
}
ensureMainOptionsCache(catalog);
- Map<Integer, List<String>> result = new LinkedHashMap<>();
+ Map<Integer, List<SourceViewer.DocEntry>> result = new
LinkedHashMap<>();
for (int i = 0; i < codeData.size(); i++) {
String code = codeData.get(i).getString("code");
if (code == null) {
@@ -583,14 +592,68 @@ class SourceTab extends AbstractTab {
continue;
}
String key = trimmed.substring(0, eq).trim();
- if (!key.startsWith("camel.")) {
- continue;
- }
BaseOptionModel opt = lookupPropertyOption(catalog, key);
if (opt != null) {
String doc = RoutesTab.formatOptionDoc(opt);
if (doc != null) {
- result.put(i, List.of(doc));
+ result.put(i, List.of(opt.isDeprecated()
+ ? SourceViewer.DocEntry.deprecated(doc)
+ : SourceViewer.DocEntry.of(doc)));
+ }
+ continue;
+ }
+ // fallback to Spring Boot configuration metadata for non-camel
properties
+ ensureSpringBootMetadataCache();
+ if (springBootMetadataCache != null) {
+ JsonObject sbProp = springBootMetadataCache.get(key);
+ if (sbProp != null) {
+ String doc = SpringBootMetadataHelper.formatDoc(sbProp);
+ if (doc != null) {
+ boolean deprecated =
Boolean.TRUE.equals(sbProp.get("deprecated"));
+ result.put(i, List.of(deprecated
+ ? SourceViewer.DocEntry.deprecated(doc)
+ : SourceViewer.DocEntry.of(doc)));
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+ private Set<Integer> scanDeprecatedProperties(List<JsonObject> codeData) {
+ CamelCatalog catalog = getCatalog();
+ if (catalog == null || codeData.isEmpty()) {
+ return Set.of();
+ }
+ ensureMainOptionsCache(catalog);
+
+ Set<Integer> result = new HashSet<>();
+ for (int i = 0; i < codeData.size(); i++) {
+ String code = codeData.get(i).getString("code");
+ if (code == null) {
+ continue;
+ }
+ String trimmed = code.trim();
+ if (trimmed.isEmpty() || trimmed.startsWith("#") ||
trimmed.startsWith("!")) {
+ continue;
+ }
+ int eq = trimmed.indexOf('=');
+ if (eq <= 0) {
+ continue;
+ }
+ String key = trimmed.substring(0, eq).trim();
+ BaseOptionModel opt = lookupPropertyOption(catalog, key);
+ if (opt != null) {
+ if (opt.isDeprecated()) {
+ result.add(i);
+ }
+ continue;
+ }
+ ensureSpringBootMetadataCache();
+ if (springBootMetadataCache != null) {
+ JsonObject sbProp = springBootMetadataCache.get(key);
+ if (sbProp != null &&
Boolean.TRUE.equals(sbProp.get("deprecated"))) {
+ result.add(i);
}
}
}
@@ -663,6 +726,8 @@ class SourceTab extends AbstractTab {
componentOptionsCache.clear();
languageOptionsCache.clear();
dataformatOptionsCache.clear();
+ springBootMetadataCache = null;
+ springBootMetadataLoaded = false;
propsCatalogVersion = version;
}
if (mainOptionsCache == null) {
@@ -678,6 +743,18 @@ class SourceTab extends AbstractTab {
}
}
+ private void ensureSpringBootMetadataCache() {
+ if (springBootMetadataLoaded) {
+ return;
+ }
+ springBootMetadataLoaded = true;
+ IntegrationInfo info = ctx.findSelectedIntegration();
+ if (info == null || !"Spring Boot".equals(info.platform)) {
+ return;
+ }
+ springBootMetadataCache = SpringBootMetadataHelper.fetchMetadata(ctx,
info.pid);
+ }
+
private void renderFileList(Frame frame, Rect area) {
Style fileBorderStyle = focusOnViewer ? Theme.muted() :
Style.EMPTY.fg(Theme.accent());
if (entries.isEmpty()) {
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 679e39ea674b..710da4eb5fe3 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
@@ -21,6 +21,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.IntConsumer;
@@ -58,9 +59,24 @@ import org.apache.camel.util.json.Jsoner;
*/
class SourceViewer {
+ record DocEntry(String text, boolean deprecated) {
+ static DocEntry of(String text) {
+ return new DocEntry(text, false);
+ }
+
+ static DocEntry deprecated(String text) {
+ return new DocEntry(text, true);
+ }
+ }
+
@FunctionalInterface
interface QuickDocProvider {
- Map<Integer, List<String>> provideAll(List<JsonObject> codeData);
+ Map<Integer, List<DocEntry>> provideAll(List<JsonObject> codeData);
+ }
+
+ @FunctionalInterface
+ interface DeprecatedLineScanner {
+ Set<Integer> scan(List<JsonObject> codeData);
}
private boolean visible;
@@ -92,7 +108,9 @@ class SourceViewer {
private int markdownScroll;
private QuickDocProvider quickDocProvider;
private boolean quickDocEnabled;
- private Map<Integer, List<String>> quickDocEntries =
Collections.emptyMap();
+ private Map<Integer, List<DocEntry>> quickDocEntries =
Collections.emptyMap();
+ private DeprecatedLineScanner deprecatedLineScanner;
+ private Set<Integer> deprecatedLines = Collections.emptySet();
private Style titleStyle;
private Style borderStyle;
private boolean focused = true;
@@ -123,6 +141,7 @@ class SourceViewer {
onLineSelected = null;
quickDocEnabled = false;
quickDocEntries = Collections.emptyMap();
+ deprecatedLines = Collections.emptySet();
}
void reset() {
@@ -150,6 +169,8 @@ class SourceViewer {
quickDocProvider = null;
quickDocEnabled = false;
quickDocEntries = Collections.emptyMap();
+ deprecatedLineScanner = null;
+ deprecatedLines = Collections.emptySet();
}
void setOnLineSelected(IntConsumer callback) {
@@ -183,6 +204,10 @@ class SourceViewer {
this.quickDocProvider = provider;
}
+ void setDeprecatedLineScanner(DeprecatedLineScanner scanner) {
+ this.deprecatedLineScanner = scanner;
+ }
+
boolean handleKeyEvent(KeyEvent ke) {
if (!visible) {
return false;
@@ -420,7 +445,7 @@ class SourceViewer {
for (int i = lines.size() - 1; i >= 0; i--) {
visualFromEnd += wrapRowCount(lines.get(i), contentWidth);
if (quickDocEnabled) {
- List<String> docs = quickDocEntries.get(i);
+ List<DocEntry> docs = quickDocEntries.get(i);
if (docs != null) {
visualFromEnd += docs.size();
}
@@ -451,11 +476,11 @@ class SourceViewer {
for (int i = scrollY; i < lines.size() && visible.size() <
visibleLines; i++) {
String raw = lines.get(i);
boolean isSelected = (i == selectedLine);
- Line line = highlightSourceLine(raw, hSkip, isSelected,
inner.width());
+ Line line = highlightSourceLine(raw, i, hSkip, isSelected,
inner.width());
line = search.applyHighlights(line, i, currentMatchLine);
visible.add(line);
- List<String> docLines = quickDocEnabled ? quickDocEntries.get(i) :
null;
+ List<DocEntry> docLines = quickDocEnabled ? quickDocEntries.get(i)
: null;
if (docLines != null) {
String code = i < codeData.size() &&
codeData.get(i).get("code") != null
? codeData.get(i).get("code").toString()
@@ -464,8 +489,8 @@ class SourceViewer {
while (si < code.length() && code.charAt(si) == ' ') {
si++;
}
- for (String docText : docLines) {
- for (Line docLine : renderQuickDocLines(docText, si,
gutterWidth, inner.width())) {
+ for (DocEntry docEntry : docLines) {
+ for (Line docLine : renderQuickDocLines(docEntry, si,
gutterWidth, inner.width())) {
if (visible.size() >= visibleLines) {
break;
}
@@ -576,6 +601,7 @@ class SourceViewer {
rawMarkdownContent = null;
markdownMode = false;
}
+ scanDeprecatedLines();
} catch (java.io.IOException e) {
title = fileName;
lines = List.of("(Failed to read file: " + e.getMessage() + ")");
@@ -966,7 +992,7 @@ class SourceViewer {
};
}
- private Line highlightSourceLine(String raw, int hSkip, boolean
isSelected, int viewportWidth) {
+ private Line highlightSourceLine(String raw, int lineIndex, int hSkip,
boolean isSelected, int viewportWidth) {
int prefixEnd = 0;
while (prefixEnd < raw.length() && (raw.charAt(prefixEnd) == ' ' ||
Character.isDigit(raw.charAt(prefixEnd)))) {
prefixEnd++;
@@ -976,6 +1002,7 @@ class SourceViewer {
String code = raw.substring(prefixEnd);
Line highlighted = SyntaxHighlighter.highlightLine(code, language);
+ boolean isDeprecated = deprecatedLines.contains(lineIndex);
List<Span> spans = new ArrayList<>();
Style selBg = focused ? Theme.selectionBg() :
Theme.selectionBg().dim();
@@ -988,7 +1015,9 @@ class SourceViewer {
spans.add(Span.styled(s.content(), s.style().patch(selBg)));
}
} else {
- spans.add(Span.raw(" "));
+ spans.add(isDeprecated
+ ? Span.styled(" ⚠ ", Theme.warning())
+ : Span.raw(" "));
if (!prefix.isEmpty()) {
spans.add(Span.styled(prefix, Style.EMPTY.dim()));
}
@@ -1093,7 +1122,8 @@ class SourceViewer {
return 0;
}
- private List<Line> renderQuickDocLines(String text, int sourceIndent, int
gutterWidth, int viewportWidth) {
+ private List<Line> renderQuickDocLines(DocEntry entry, int sourceIndent,
int gutterWidth, int viewportWidth) {
+ String text = entry.text();
String prefix = " ".repeat(gutterWidth + 5 + sourceIndent);
String marker = "ℹ ";
Style docStyle = Style.EMPTY.dim().italic();
@@ -1148,7 +1178,7 @@ class SourceViewer {
for (int i = fromLine; i < toLine && i < lines.size(); i++) {
count += wrapRowCount(lines.get(i), contentWidth);
if (quickDocEnabled) {
- List<String> docs = quickDocEntries.get(i);
+ List<DocEntry> docs = quickDocEntries.get(i);
if (docs != null) {
count += docs.size();
}
@@ -1164,6 +1194,15 @@ class SourceViewer {
return (line.length() + contentWidth - 1) / contentWidth;
}
+ private void scanDeprecatedLines() {
+ if (deprecatedLineScanner != null && !codeData.isEmpty()) {
+ Set<Integer> result = deprecatedLineScanner.scan(codeData);
+ deprecatedLines = result != null ? result : Collections.emptySet();
+ } else {
+ deprecatedLines = Collections.emptySet();
+ }
+ }
+
private static String objToString(Object o) {
return o != null ? o.toString() : "";
}
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SpringBootMetadataHelper.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SpringBootMetadataHelper.java
new file mode 100644
index 000000000000..0593b5a81672
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SpringBootMetadataHelper.java
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.dsl.jbang.core.commands.tui;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.camel.tooling.model.BaseOptionModel;
+import org.apache.camel.tooling.model.MainModel;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+
+/**
+ * Helper for fetching and formatting Spring Boot configuration metadata from
a running integration via the CLI
+ * connector IPC mechanism.
+ */
+final class SpringBootMetadataHelper {
+
+ private SpringBootMetadataHelper() {
+ }
+
+ static Map<String, JsonObject> fetchMetadata(MonitorContext ctx, String
pid) {
+ JsonObject root = new JsonObject();
+ root.put("action", "spring-boot-configuration");
+
+ JsonObject response = ctx.executeAction(pid, root, 5000);
+ if (response == null) {
+ return Map.of();
+ }
+
+ Object propsObj = response.get("properties");
+ if (!(propsObj instanceof JsonArray arr)) {
+ return Map.of();
+ }
+
+ Map<String, JsonObject> result = new HashMap<>();
+ for (int i = 0; i < arr.size(); i++) {
+ Object item = arr.get(i);
+ if (item instanceof JsonObject prop) {
+ String name = prop.getString("name");
+ if (name != null && !name.isEmpty()) {
+ result.put(name, prop);
+ }
+ }
+ }
+ return result;
+ }
+
+ static String formatDoc(JsonObject prop) {
+ StringBuilder sb = new StringBuilder();
+ String description = prop.getString("description");
+ if (description != null && !description.isEmpty()) {
+ sb.append(RoutesTab.truncateText(description, 80));
+ }
+
+ List<String> meta = new ArrayList<>();
+ String type = prop.getString("type");
+ if (type != null) {
+ int lastDot = type.lastIndexOf('.');
+ if (lastDot >= 0) {
+ type = type.substring(lastDot + 1);
+ }
+ meta.add(type);
+ }
+ Object defaultValue = prop.get("defaultValue");
+ if (defaultValue != null) {
+ meta.add("default: " + defaultValue);
+ }
+ if (!meta.isEmpty()) {
+ if (!sb.isEmpty()) {
+ sb.append(" ");
+ }
+ sb.append("(").append(String.join(", ", meta)).append(")");
+ }
+
+ if (Boolean.TRUE.equals(prop.get("deprecated"))) {
+ sb.append(" [deprecated]");
+ }
+
+ return !sb.isEmpty() ? sb.toString() : null;
+ }
+
+ static BaseOptionModel toOptionModel(JsonObject prop) {
+ MainModel.MainOptionModel model = new MainModel.MainOptionModel();
+ model.setName(prop.getString("name"));
+ model.setDescription(prop.getString("description"));
+
+ String type = prop.getString("type");
+ if (type != null) {
+ int lastDot = type.lastIndexOf('.');
+ if (lastDot >= 0) {
+ type = type.substring(lastDot + 1);
+ }
+ model.setType(type);
+ }
+
+ Object defaultValue = prop.get("defaultValue");
+ if (defaultValue != null) {
+ model.setDefaultValue(defaultValue.toString());
+ }
+
+ if (Boolean.TRUE.equals(prop.get("deprecated"))) {
+ model.setDeprecated(true);
+ }
+
+ return model;
+ }
+}