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 9008dd64be28 CAMEL-25242: camel-jbang - source editor: complete the
arguments of simple functions (#27239)
9008dd64be28 is described below
commit 9008dd64be28137a6c543ac47bafdf9d0d59e791
Author: Claus Ibsen <[email protected]>
AuthorDate: Fri Oct 2 08:05:03 2026 +0200
CAMEL-25242: camel-jbang - source editor: complete the arguments of simple
functions (#27239)
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
---
.../camel-tui-source-simple-bean-completion.png | Bin 0 -> 240079 bytes
.../ROOT/pages/camel-jbang-tui-source-editor.adoc | 5 ++
.../dsl/jbang/core/commands/tui/ProjectBeans.java | 9 ++
.../core/commands/tui/SimpleCompletionContext.java | 52 +++++++++++-
.../jbang/core/commands/tui/SimpleCompletions.java | 68 +++++++++++++++
.../jbang/core/commands/tui/SourceEditAssist.java | 25 +++++-
.../dsl/jbang/core/commands/tui/SourceTab.java | 18 +++-
.../dsl/jbang/core/commands/tui/SourceViewer.java | 31 +++++--
.../src/main/resources/tui/help/source.md | 4 +
.../core/commands/tui/SimpleCompletionTest.java | 93 +++++++++++++++++++++
10 files changed, 291 insertions(+), 14 deletions(-)
diff --git
a/docs/user-manual/modules/ROOT/images/jbang/camel-tui-source-simple-bean-completion.png
b/docs/user-manual/modules/ROOT/images/jbang/camel-tui-source-simple-bean-completion.png
new file mode 100644
index 000000000000..5a3d8c762edc
Binary files /dev/null and
b/docs/user-manual/modules/ROOT/images/jbang/camel-tui-source-simple-bean-completion.png
differ
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-jbang-tui-source-editor.adoc
b/docs/user-manual/modules/ROOT/pages/camel-jbang-tui-source-editor.adoc
index 821b9e109982..f0b0721d5902 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-tui-source-editor.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-tui-source-editor.adoc
@@ -144,6 +144,9 @@ What can be completed:
`exchangeProperty.`, `variable.`) the names the file sets or reads, then the
headers of the components it uses. After a
function and a space the operators: comparisons, `&&` and `||` where the EIP
takes a predicate (`when`, `filter`,
`validate`, `onWhen`, ...), chaining (`~>`) and a default value (`?:`) in
other expressions.
+ In the arguments of a function: after `+${date:+` the commands (`now`,
`exchangeCreated`, `header.`...), after the
+ next `:` common date patterns, and the time zones of
`+${date-with-timezone(..)}+`; after `+${bean:+` the beans
+ your project declares; after `+${properties:+` the keys of its `.properties`
files, with their values.
* *`application.properties`* -- Camel options (`+camel.main.*+`,
`+camel.component.*+`, ...) and Spring Boot properties
(`+server.*+`, `+spring.*+`, ...), with their values and `+{{placeholder}}+`
suggestions. This works even when the
project is not running.
@@ -152,6 +155,8 @@ image::jbang/camel-tui-source-xml-completion.png[In the
start tag of log, Tab li
image::jbang/camel-tui-source-simple-completion.png[After ${header. Tab lists
the headers the route sets, then the ones of the Kafka component]
+image::jbang/camel-tui-source-simple-bean-completion.png[After ${bean: Tab
lists the beans the project declares]
+
image::jbang/camel-tui-source-java-completion.png[After a split in a Java
route, Tab lists the options of the split first, then the EIPs]
image::jbang/camel-tui-source-java-argument-completion.png[In the argument of
a filter, Tab lists the methods of the header builder]
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ProjectBeans.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ProjectBeans.java
index 676d00790fff..648e966ab1d6 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ProjectBeans.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ProjectBeans.java
@@ -20,6 +20,8 @@ import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
@@ -75,6 +77,13 @@ final class ProjectBeans {
this.byClass = byClass;
}
+ /** The beans the files declare by name, sorted by name. */
+ List<Location> beans() {
+ List<Location> found = new ArrayList<>(byName.values());
+ found.sort(Comparator.comparing(Location::label,
String.CASE_INSENSITIVE_ORDER));
+ return found;
+ }
+
/** Reads the beans and classes the files declare. */
static ProjectBeans scan(List<Path> files) {
Map<String, Location> byName = new HashMap<>();
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SimpleCompletionContext.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SimpleCompletionContext.java
index 21b1768896d6..70340c08cb0e 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SimpleCompletionContext.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SimpleCompletionContext.java
@@ -40,7 +40,17 @@ record SimpleCompletionContext(Kind kind, String prefix,
String closing, List<St
HEADER,
PROPERTY,
VARIABLE,
- OPERATOR
+ OPERATOR,
+ /** The command of ${date:..} or ${date-with-timezone(..)}: now,
exchangeCreated, header.xxx... */
+ DATE_COMMAND,
+ /** The pattern after the command: yyyy-MM-dd... */
+ DATE_PATTERN,
+ /** The time zone of ${date-with-timezone(command:zone:pattern)} */
+ TIME_ZONE,
+ /** The bean of ${bean:name.method} */
+ BEAN,
+ /** The property key of ${properties:key} and ${propertiesExist:key} */
+ PROPERTY_KEY
}
/** How many lines above the cursor are looked at for the EIP the
expression belongs to. */
@@ -49,6 +59,14 @@ record SimpleCompletionContext(Kind kind, String prefix,
String closing, List<St
private static final Pattern NAME = Pattern.compile(
"(header|headers|in\\.header|in\\.headers|exchangeProperty|variable|variables)(\\.|\\[['\"]?)([\\w.\\-]*)");
private static final Pattern FUNCTION = Pattern.compile("[\\w.:\\-]*");
+ // the arguments of the functions that take a name or command the editor
knows the values of
+ private static final Pattern DATE_COMMAND =
Pattern.compile("date:([^:}]*)");
+ private static final Pattern DATE_PATTERN =
Pattern.compile("date:[^:}]+:(.*)");
+ private static final Pattern ZONED_COMMAND =
Pattern.compile("date-with-timezone\\(([^:)]*)");
+ private static final Pattern ZONED_ZONE =
Pattern.compile("date-with-timezone\\([^:)]+:([^:)]*)");
+ private static final Pattern ZONED_PATTERN =
Pattern.compile("date-with-timezone\\([^:)]+:[^:)]+:([^)]*)");
+ private static final Pattern BEAN = Pattern.compile("bean:([\\w\\-]*)");
+ private static final Pattern PROPERTY_KEY =
Pattern.compile("(?:properties|propertiesExist):([\\w.\\-]*)");
private static final Pattern OPERATOR =
Pattern.compile("\\s+([^\\s'\"$]*)");
private static final Pattern WORD = Pattern.compile("[A-Za-z_][\\w-]*");
@@ -83,6 +101,10 @@ record SimpleCompletionContext(Kind kind, String prefix,
String closing, List<St
}
if (!open.isEmpty()) {
String inner = line.substring(open.get(open.size() - 1), col);
+ SimpleCompletionContext argument = argument(inner);
+ if (argument != null) {
+ return argument;
+ }
Matcher m = NAME.matcher(inner);
if (m.matches()) {
Kind kind = switch (m.group(1)) {
@@ -107,6 +129,34 @@ record SimpleCompletionContext(Kind kind, String prefix,
String closing, List<St
return new SimpleCompletionContext(Kind.OPERATOR, m.group(1), " ",
owners(lines, row, firstOpen));
}
+ /** The argument of a function the cursor is in, when the editor knows its
values: ${date:|, ${bean:|... */
+ private static SimpleCompletionContext argument(String inner) {
+ Matcher m;
+ if ((m = DATE_PATTERN.matcher(inner)).matches()) {
+ return new SimpleCompletionContext(Kind.DATE_PATTERN, m.group(1),
"}", List.of());
+ }
+ if ((m = DATE_COMMAND.matcher(inner)).matches()) {
+ return new SimpleCompletionContext(Kind.DATE_COMMAND, m.group(1),
"", List.of());
+ }
+ if ((m = ZONED_PATTERN.matcher(inner)).matches()) {
+ return new SimpleCompletionContext(Kind.DATE_PATTERN, m.group(1),
")}", List.of());
+ }
+ if ((m = ZONED_ZONE.matcher(inner)).matches()) {
+ return new SimpleCompletionContext(Kind.TIME_ZONE, m.group(1),
":", List.of());
+ }
+ if ((m = ZONED_COMMAND.matcher(inner)).matches()) {
+ return new SimpleCompletionContext(Kind.DATE_COMMAND, m.group(1),
"", List.of());
+ }
+ if ((m = BEAN.matcher(inner)).matches()) {
+ // no } after the bean: a .method may follow
+ return new SimpleCompletionContext(Kind.BEAN, m.group(1), "",
List.of());
+ }
+ if ((m = PROPERTY_KEY.matcher(inner)).matches()) {
+ return new SimpleCompletionContext(Kind.PROPERTY_KEY, m.group(1),
"}", List.of());
+ }
+ return null;
+ }
+
/**
* A function of a simple expression on a line: the text between ${ and }
(to the end of the line when it is not
* closed yet), nested functions included.
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SimpleCompletions.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SimpleCompletions.java
index afd772196ecd..97f654a15462 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SimpleCompletions.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SimpleCompletions.java
@@ -16,6 +16,7 @@
*/
package org.apache.camel.dsl.jbang.core.commands.tui;
+import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
@@ -23,6 +24,7 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -54,12 +56,48 @@ final class SimpleCompletions {
private record Words(CamelCatalog catalog, Set<String> predicate,
Set<String> eip) {
}
+ /** The commands of ${date:..}; an offset (now-24h, header.due+1h30m) may
follow any of them. */
+ private static final Map<String, String> DATE_COMMANDS = dateCommands();
+ /** Patterns of java.text.SimpleDateFormat that dates are often formatted
with. */
+ private static final List<String> DATE_PATTERNS = List.of(
+ "yyyy-MM-dd", "yyyyMMdd", "yyyy-MM-dd HH:mm:ss",
"yyyy-MM-dd'T'HH:mm:ss", "yyyy-MM-dd'T'HH:mm:ss.SSSZ",
+ "yyyyMMddHHmmss", "HH:mm:ss", "HH:mm", "dd-MM-yyyy", "MM/dd/yyyy",
"EEE, dd MMM yyyy HH:mm:ss Z");
+
+ /**
+ * What the project knows that simple functions take: the beans it
declares (${bean:..}) and the keys of its
+ * .properties files (${properties:..}).
+ */
+ record Project(
+ Supplier<List<AutocompletePopup.CompletionItem>> beans,
+ Supplier<List<AutocompletePopup.CompletionItem>> properties) {
+
+ static final Project NONE = new Project(List::of, List::of);
+ }
+
private SimpleCompletions() {
}
+ private static Map<String, String> dateCommands() {
+ Map<String, String> commands = new LinkedHashMap<>();
+ commands.put("now", "The current date and time");
+ commands.put("millis", "The current time in milliseconds");
+ commands.put("exchangeCreated", "When the exchange was created");
+ commands.put("header.", "The date (Long or Date) in the header with
the given name");
+ commands.put("variable.", "The date (Long or Date) in the variable
with the given name");
+ commands.put("exchangeProperty.", "The date (Long or Date) in the
exchange property with the given name");
+ commands.put("file", "The last modified time of the file (with a file
consumer)");
+ return commands;
+ }
+
/** The completions at the context, given the lines of the file being
edited. */
static List<AutocompletePopup.CompletionItem> provide(
CamelCatalog catalog, SimpleCompletionContext context,
List<String> lines) {
+ return provide(catalog, context, lines, Project.NONE);
+ }
+
+ /** The completions at the context, given the lines of the file being
edited and what the project declares. */
+ static List<AutocompletePopup.CompletionItem> provide(
+ CamelCatalog catalog, SimpleCompletionContext context,
List<String> lines, Project project) {
if (catalog == null || context == null) {
return List.of();
}
@@ -76,9 +114,39 @@ final class SimpleCompletions {
case HEADER -> names(catalog, lines, "Header", "header", true);
case PROPERTY -> names(catalog, lines, "Property",
"exchangeProperty", false);
case VARIABLE -> names(catalog, lines, "Variable", "variable",
false);
+ case DATE_COMMAND -> dateCommandItems();
+ case DATE_PATTERN -> DATE_PATTERNS.stream()
+ .map(p -> new AutocompletePopup.CompletionItem(
+ p, "Formats the date with this
java.text.SimpleDateFormat pattern", "pattern", null, false,
+ null, null, false))
+ .toList();
+ case TIME_ZONE -> timeZones();
+ case BEAN -> project.beans().get();
+ case PROPERTY_KEY -> project.properties().get();
};
}
+ private static List<AutocompletePopup.CompletionItem> dateCommandItems() {
+ List<AutocompletePopup.CompletionItem> items = new ArrayList<>();
+ DATE_COMMANDS.forEach((command, doc) -> items.add(new
AutocompletePopup.CompletionItem(
+ command, doc + ". An offset may follow, such as -24h or
+1h30m.", "command", null, false, null, null,
+ false)));
+ return items;
+ }
+
+ /** The time zone ids of the JVM, UTC first. */
+ private static List<AutocompletePopup.CompletionItem> timeZones() {
+ List<String> zones = new ArrayList<>(ZoneId.getAvailableZoneIds());
+ zones.sort(String.CASE_INSENSITIVE_ORDER);
+ zones.remove("UTC");
+ zones.add(0, "UTC");
+ List<AutocompletePopup.CompletionItem> items = new ArrayList<>();
+ for (String zone : zones) {
+ items.add(new AutocompletePopup.CompletionItem(zone, "Time zone",
"zone", null, false, null, null, false));
+ }
+ return items;
+ }
+
static List<AutocompletePopup.CompletionItem> functions(LanguageModel
simple) {
List<AutocompletePopup.CompletionItem> items = new ArrayList<>();
for (LanguageModel.LanguageFunctionModel f : simple.getFunctions()) {
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceEditAssist.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceEditAssist.java
index 30f9bedfccf8..8a687a7227e6 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceEditAssist.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceEditAssist.java
@@ -1033,7 +1033,30 @@ final class SourceEditAssist {
/** The completions of the simple expression at the cursor (CAMEL-25219).
*/
List<AutocompletePopup.CompletionItem>
provideSimpleCompletions(SimpleCompletionContext context, List<String> lines) {
- return SimpleCompletions.provide(validationCatalog(), context, lines);
+ return provideSimpleCompletions(context, lines, List::of);
+ }
+
+ /**
+ * The completions of the simple expression at the cursor, the arguments
of ${bean:..} from the beans the project
+ * declares and of ${properties:..} from its .properties files
(CAMEL-25242).
+ */
+ List<AutocompletePopup.CompletionItem> provideSimpleCompletions(
+ SimpleCompletionContext context, List<String> lines,
Supplier<List<AutocompletePopup.CompletionItem>> beans) {
+ return SimpleCompletions.provide(validationCatalog(), context, lines,
+ new SimpleCompletions.Project(beans, this::propertyKeys));
+ }
+
+ /** The keys of the project's .properties files, with their values. */
+ private List<AutocompletePopup.CompletionItem> propertyKeys() {
+ List<AutocompletePopup.CompletionItem> items = new ArrayList<>();
+ for (AutocompletePopup.CompletionItem p : loadPropertyPlaceholders()) {
+ String key = p.key();
+ if (key.startsWith("{{") && key.endsWith("}}")) {
+ items.add(new AutocompletePopup.CompletionItem(
+ key.substring(2, key.length() - 2), p.description(),
"property", null, false, null, p.group()));
+ }
+ }
+ return items;
}
List<AutocompletePopup.CompletionItem>
provideComponentNameCompletions(String role) {
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 5a8b3faaefe9..7131d9ada5dc 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
@@ -846,8 +846,8 @@ class SourceTab extends AbstractTab {
String name = filePath.getFileName().toString();
sourceViewer.setUriCompletion(!routeFile ? null :
name.endsWith(".java") ? "java" : "xml");
// simple expressions in every route file: YAML, Java and XML
(CAMEL-25219)
- sourceViewer.setSimpleCompletion(
- routeFile || isCamelSourceFile(filePath) ?
assist::provideSimpleCompletions : null);
+ sourceViewer.setSimpleCompletion(routeFile ||
isCamelSourceFile(filePath)
+ ? (c, lines) -> assist.provideSimpleCompletions(c, lines,
this::beanItems) : null);
boolean xmlRoute = routeFile && name.toLowerCase().endsWith(".xml");
sourceViewer.setCursorQuickDocProvider(!routeFile &&
!isCamelSourceFile(filePath) ? null
: xmlRoute ? (lines, row, col) -> {
@@ -856,9 +856,9 @@ class SourceTab extends AbstractTab {
return !simple.isEmpty() ? simple :
assist.provideXmlQuickDoc(lines, row, col);
}
: assist::provideSimpleQuickDoc);
- // the elements, attributes and values of an XML route (CAMEL-25240)
// the route chain of a Java route (CAMEL-25241)
sourceViewer.setJavaCompletion(routeFile && name.endsWith(".java") ?
assist::provideJavaCompletions : null);
+ // the elements, attributes and values of an XML route (CAMEL-25240)
sourceViewer.setXmlCompletion(routeFile &&
name.toLowerCase().endsWith(".xml") ? assist::provideXmlCompletions : null);
if (isCamelSourceFile(filePath)) {
sourceViewer.setQuickDocProvider(assist::provideCamelQuickDocs);
@@ -1475,6 +1475,18 @@ class SourceTab extends AbstractTab {
return result;
}
+ /** The beans the project declares, for ${bean:..} in simple expressions
(CAMEL-25242). */
+ private List<AutocompletePopup.CompletionItem> beanItems() {
+ List<AutocompletePopup.CompletionItem> items = new ArrayList<>();
+ for (ProjectBeans.Location bean : projectBeans().beans()) {
+ String where = "Declared in " +
Path.of(bean.filePath()).getFileName() + ":" + (bean.line() + 1);
+ items.add(new AutocompletePopup.CompletionItem(
+ bean.label(), bean.type() != null ? where + " as " +
bean.type() : where, bean.type(), null, false,
+ null, null, false));
+ }
+ return items;
+ }
+
/** The beans and classes the project declares, read again when a file of
the project changed. */
ProjectBeans projectBeans() {
long now = System.currentTimeMillis();
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 35d89bc36dbd..692fa4bee868 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
@@ -1356,6 +1356,11 @@ class SourceViewer {
case PROPERTY -> "Exchange properties";
case VARIABLE -> "Variables";
case OPERATOR -> "Operators";
+ case DATE_COMMAND -> "Date commands";
+ case DATE_PATTERN -> "Date patterns";
+ case TIME_ZONE -> "Time zones";
+ case BEAN -> "Beans";
+ case PROPERTY_KEY -> "Properties";
});
pendingSimpleCompletion = new SimpleCompletion(row, col, c);
}
@@ -1388,8 +1393,7 @@ class SourceViewer {
String line = editState.getLine(sc.row());
int from = editState.cursorCol();
int end = from;
- boolean operator = c.kind() == SimpleCompletionContext.Kind.OPERATOR;
- while (end < line.length() && isSimpleWordChar(line.charAt(end),
operator)) {
+ while (end < line.length() && isSimpleWordChar(line.charAt(end),
c.kind())) {
end++;
}
for (int i = from; i < end; i++) {
@@ -1403,17 +1407,26 @@ class SourceViewer {
}
boolean hasClosing = !closing.isEmpty() && line.startsWith(closing,
end);
editState.insert(text + (hasClosing ? "" : closing));
- if (c.kind() == SimpleCompletionContext.Kind.FUNCTION &&
(text.endsWith(".") || text.endsWith("["))) {
- openAutocomplete();
+ boolean stem = c.kind() == SimpleCompletionContext.Kind.FUNCTION
+ && (text.endsWith(".") || text.endsWith("[") ||
text.endsWith(":") || text.endsWith("("));
+ if (stem || c.kind() == SimpleCompletionContext.Kind.TIME_ZONE) {
+ // header. opens the header names, date: the date commands, bean:
the beans, a time zone the patterns;
+ // only the simple completion, nothing else when the function has
no values to offer
+ openSimpleAutocomplete();
}
}
/** A character of the function, name or operator being completed. */
- private static boolean isSimpleWordChar(char c, boolean operator) {
- if (operator) {
- return !Character.isWhitespace(c) && c != '$' && c != '\'' && c !=
'"';
- }
- return Character.isLetterOrDigit(c) || c == '-' || c == '_' || c ==
'.' || c == ':';
+ private static boolean isSimpleWordChar(char c,
SimpleCompletionContext.Kind kind) {
+ return switch (kind) {
+ case OPERATOR -> !Character.isWhitespace(c) && c != '$' && c !=
'\'' && c != '"';
+ case FUNCTION, HEADER, PROPERTY, VARIABLE ->
+ Character.isLetterOrDigit(c) || c == '-' || c == '_' || c ==
'.' || c == ':';
+ // the argument only: the .method after a bean, the :pattern after
a date command stay
+ case PROPERTY_KEY -> Character.isLetterOrDigit(c) || c == '-' || c
== '_' || c == '.';
+ case DATE_PATTERN -> c != '}' && c != ')' && c != '"' && c != '\'';
+ default -> Character.isLetterOrDigit(c) || c == '-' || c == '_' ||
c == '/';
+ };
}
/**
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/source.md
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/source.md
index 69f9b614c013..d47e8417cb24 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/source.md
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/source.md
@@ -135,6 +135,10 @@ Press **F4** to enter edit mode, then **Tab** for
context-aware completion:
- After a function and a space, Tab lists the operators: comparisons and
`&&` `||` where the EIP takes a predicate (`when`, `filter`, `validate`,
`onWhen`...), chaining (`~>`) and the default value (`?:`) elsewhere
+- In the arguments of a function: the commands after `${date:` (`now`,
+ `exchangeCreated`, `header.`...) and date patterns after the next `:`, the
+ time zones of `date-with-timezone`, the project's beans after `${bean:`, the
+ keys of its `.properties` files after `${properties:`
Use **Up/Down** to navigate, **Enter** to accept, **Esc** to dismiss, and
type to filter the completion list (an exact or prefix match comes first).
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SimpleCompletionTest.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SimpleCompletionTest.java
index e01d45e42e46..eb7d21f9f05d 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SimpleCompletionTest.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SimpleCompletionTest.java
@@ -96,6 +96,99 @@ class SimpleCompletionTest {
assertThat(at("${variable.x").kind()).isEqualTo(SimpleCompletionContext.Kind.VARIABLE);
}
+ @Test
+ void theArgumentOfAFunction() {
+ SimpleCompletionContext c = at("${date:no");
+
assertThat(c.kind()).isEqualTo(SimpleCompletionContext.Kind.DATE_COMMAND);
+ assertThat(c.prefix()).isEqualTo("no");
+ c = at("${date:now:yyyy");
+
assertThat(c.kind()).isEqualTo(SimpleCompletionContext.Kind.DATE_PATTERN);
+ assertThat(c.prefix()).isEqualTo("yyyy");
+ assertThat(c.closing()).isEqualTo("}");
+
+
assertThat(at("${date-with-timezone(no").kind()).isEqualTo(SimpleCompletionContext.Kind.DATE_COMMAND);
+ c = at("${date-with-timezone(now:Eur");
+ assertThat(c.kind()).isEqualTo(SimpleCompletionContext.Kind.TIME_ZONE);
+ assertThat(c.closing()).isEqualTo(":");
+ c = at("${date-with-timezone(now:UTC:HH");
+
assertThat(c.kind()).isEqualTo(SimpleCompletionContext.Kind.DATE_PATTERN);
+ assertThat(c.closing()).isEqualTo(")}");
+
+ c = at("${bean:my");
+ assertThat(c.kind()).isEqualTo(SimpleCompletionContext.Kind.BEAN);
+ assertThat(c.closing()).isEmpty();
+ c = at("${properties:app.");
+
assertThat(c.kind()).isEqualTo(SimpleCompletionContext.Kind.PROPERTY_KEY);
+ assertThat(c.prefix()).isEqualTo("app.");
+
assertThat(at("${propertiesExist:x").kind()).isEqualTo(SimpleCompletionContext.Kind.PROPERTY_KEY);
+ }
+
+ @Test
+ void theValuesOfTheArguments() {
+ assertThat(keys(at("${date:"),
SimpleCompletions.Project.NONE)).contains("now", "exchangeCreated", "header.");
+ assertThat(keys(at("${date:now:"),
SimpleCompletions.Project.NONE)).contains("yyyy-MM-dd", "HH:mm:ss");
+ List<String> zones = keys(at("${date-with-timezone(now:"),
SimpleCompletions.Project.NONE);
+ assertThat(zones.get(0)).isEqualTo("UTC");
+ assertThat(zones).contains("Europe/Paris");
+
+ SimpleCompletions.Project project = new SimpleCompletions.Project(
+ () -> List.of(new
AutocompletePopup.CompletionItem("orderService", null, null, null, false, null,
null)),
+ () -> List.of(new AutocompletePopup.CompletionItem("app.name",
"Camel", null, null, false, null, null)));
+ assertThat(keys(at("${bean:"),
project)).containsExactly("orderService");
+ assertThat(keys(at("${properties:"),
project)).containsExactly("app.name");
+ }
+
+ private static List<String> keys(SimpleCompletionContext c,
SimpleCompletions.Project project) {
+ return SimpleCompletions.provide(catalog, c, List.of(),
project).stream()
+ .map(AutocompletePopup.CompletionItem::key).toList();
+ }
+
+ @Test
+ void anotherBeanKeepsTheMethod() throws Exception {
+ SourceViewer viewer = viewer("route.camel.yaml", """
+ - from:
+ uri: timer:tick
+ steps:
+ - setBody:
+ simple: "${bean:orderService.total}"
+ """);
+ viewer.setSimpleCompletion((c, lines) ->
SimpleCompletions.provide(catalog, c, lines,
+ new SimpleCompletions.Project(
+ () -> List.of(
+ new
AutocompletePopup.CompletionItem("orderService", null, null, null, false, null,
null),
+ new
AutocompletePopup.CompletionItem("priceCalculator", null, null, null, false,
null, null)),
+ List::of)));
+ cursorAt(viewer, 4, "orderService.total}\"");
+ tab(viewer);
+ type(viewer, "pri");
+ enter(viewer);
+ assertThat(line(viewer, 4)).isEqualTo(" simple:
\"${bean:priceCalculator.total}\"");
+ }
+
+ @Test
+ void aDateFromItsFunctionToItsPattern() throws Exception {
+ SourceViewer viewer = viewer("route.camel.yaml", """
+ - from:
+ uri: timer:tick
+ steps:
+ - setBody:
+ simple: "Created ${dat"
+ """);
+ cursorAt(viewer, 4, "\"");
+ tab(viewer);
+ type(viewer, "e:");
+ enter(viewer);
+ // date: opens the commands right away
+ type(viewer, "now");
+ enter(viewer);
+ assertThat(line(viewer, 4)).isEqualTo(" simple: \"Created
${date:now\"");
+ type(viewer, ":");
+ tab(viewer);
+ type(viewer, "yyyy-MM-dd");
+ enter(viewer);
+ assertThat(line(viewer, 4)).isEqualTo(" simple: \"Created
${date:now:yyyy-MM-dd}\"");
+ }
+
@Test
void theOperatorAfterAFunction() {
SimpleCompletionContext c = at(" .filter(simple(\"${header.foo}
con");