diff --git a/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/parser/template/Parameter.java b/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/parser/template/Parameter.java index c7b84df3c..75b583b0e 100644 --- a/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/parser/template/Parameter.java +++ b/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/parser/template/Parameter.java @@ -34,6 +34,8 @@ public class Parameter extends Node implements JavaTypeInfoProvider { private ParametersContainer container; + private boolean hasDefaultValue; // #let name?="foo" + public Parameter(int start, int end) { super(start, end); this.startName = start; @@ -109,9 +111,11 @@ public String getName() { if (name == null) { int endName = getEndName(); Section section = getOwnerSection(); - if (section != null && section.getSectionKind() == SectionKind.LET) { + if (section != null + && (section.getSectionKind() == SectionKind.LET || section.getSectionKind() == SectionKind.SET)) { String text = getOwnerTemplate().getText(); if (text.charAt(endName - 1) == '?') { + hasDefaultValue = true; // ex : {#let name?"=main"} // name? --> name endName--; @@ -241,4 +245,17 @@ public boolean isOptional() { Expression expression = getJavaTypeExpression(); return expression != null ? expression.isOptional() : false; } + + /** + * Returns true if the parameter uses '?' to assign value (ex : #let + * name?="foo") + * which means that name has default value and false other. + * + * @return true if the parameter uses '?' to assign value (ex : #let + * name?="foo") + * which means that name has default value and false other. + */ + public boolean hasDefaultValue() { + return hasDefaultValue; + } } diff --git a/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/project/tags/UserTag.java b/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/project/tags/UserTag.java index 02dd8011b..8f16fdb69 100644 --- a/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/project/tags/UserTag.java +++ b/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/project/tags/UserTag.java @@ -115,16 +115,38 @@ private List createBody() { */ public static void generateUserTagParameter(UserTagParameter parameter, boolean snippetsSupported, int index, StringBuilder snippet) { + // Generate parameter name snippet.append(parameter.getName()); snippet.append("="); - snippet.append("\""); + + // Generate parameter value + String value = parameter.getName(); + char quote = '"'; + String defaultValue = parameter.getDefaultValue(); + if (defaultValue != null && !defaultValue.isEmpty()) { + value = defaultValue; + // there is a default value, remove the quote if needed + char start = defaultValue.charAt(0); + if (start == '"' || start == '\'') { + quote = start; + value = value.substring(1, value.length() - (value.endsWith(start + "") ? 1 : 0)); + } else { + quote = (char) -1; + } + } + + if (quote != -1) { + snippet.append(quote); + } + if (snippetsSupported) { - SnippetsBuilder.placeholders(index, parameter.getName(), snippet); + SnippetsBuilder.placeholders(index, value, snippet); } else { - snippet.append(parameter.getName()); + snippet.append(value); + } + if (quote != -1) { + snippet.append(quote); } - snippet.append("\""); - } /** diff --git a/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/project/tags/UserTagParameter.java b/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/project/tags/UserTagParameter.java index 780113a22..6e33596d4 100644 --- a/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/project/tags/UserTagParameter.java +++ b/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/project/tags/UserTagParameter.java @@ -23,10 +23,21 @@ public class UserTagParameter { private boolean required; + private String defaultValue; + public UserTagParameter(String name) { this.name = name; } + /** + * Returns the user tag parameter name. + * + * @return the user tag parameter name. + */ + public String getName() { + return name; + } + /** * Set the required flag. * @@ -45,12 +56,14 @@ public Boolean isRequired() { return required; } - /** - * Returns the user tag parameter name. - * - * @return the user tag parameter name. - */ - public String getName() { - return name; + public String getDefaultValue() { + return defaultValue; + } + + public void setDefaultValue(String defaultValue) { + this.defaultValue = defaultValue; + if (defaultValue != null) { + setRequired(false); + } } } diff --git a/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/project/tags/UserTagParameterCollector.java b/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/project/tags/UserTagParameterCollector.java index 98857bde5..658987bd6 100644 --- a/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/project/tags/UserTagParameterCollector.java +++ b/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/project/tags/UserTagParameterCollector.java @@ -47,17 +47,26 @@ public class UserTagParameterCollector extends ASTVisitor { private final Map parameters; - private final List> optionalParameterNamesStack; - - private final List> ignoreParameterNamesStack; + private final List> parameterNamesStack; private List globalVariables; + private static class ParamInfo { + public final String name; + public final boolean assigned; // true if parameter comes from a #let, #set and false otherwise. + public final String defaultValue; // ex : name?="foo", the default value is "foo" + + public ParamInfo(String name, boolean assigned, String defaultValue) { + this.name = name; + this.assigned = assigned; + this.defaultValue = defaultValue; + } + } + public UserTagParameterCollector(QuteProject project) { this.project = project; this.parameters = new LinkedHashMap<>(); - this.optionalParameterNamesStack = new ArrayList<>(); - this.ignoreParameterNamesStack = new ArrayList<>(); + this.parameterNamesStack = new ArrayList<>(); } @Override @@ -68,12 +77,12 @@ public boolean visit(IfSection node) { @Override public void endVisit(IfSection node) { - removeOptionalStack(); + removeParameterStack(); super.endVisit(node); } private void addOptionalStack(Section node) { - List optionalParameterNames = null; + List optionalParameterNames = null; List parameters = node.getParameters(); for (Parameter parameter : parameters) { String name = parameter.getName(); @@ -81,56 +90,53 @@ private void addOptionalStack(Section node) { if (optionalParameterNames == null) { optionalParameterNames = new ArrayList<>(); } - optionalParameterNames.add(name); + optionalParameterNames.add(new ParamInfo(name, false, null)); } } - optionalParameterNamesStack + parameterNamesStack .add(optionalParameterNames != null ? optionalParameterNames : Collections.emptyList()); } - private void removeOptionalStack() { - optionalParameterNamesStack.remove(optionalParameterNamesStack.size() - 1); + private void removeParameterStack() { + parameterNamesStack.remove(parameterNamesStack.size() - 1); } public boolean visit(LetSection node) { - addIgnoreStack(node); + addAssignedParameterStack(node); return super.visit(node); } @Override public void endVisit(LetSection node) { - removeIgnoreStack(); + removeParameterStack(); super.endVisit(node); } public boolean visit(SetSection node) { - addIgnoreStack(node); + addAssignedParameterStack(node); return super.visit(node); } @Override public void endVisit(SetSection node) { - removeIgnoreStack(); + removeParameterStack(); super.endVisit(node); } - private void addIgnoreStack(Section node) { - List ignoreParameterNames = null; + private void addAssignedParameterStack(Section node) { + List declaredParameterNames = null; List parameters = node.getParameters(); for (Parameter parameter : parameters) { String name = parameter.getName(); + String defaultValue = parameter.hasDefaultValue() ? parameter.getValue() : null; if (!StringUtils.isEmpty(name)) { - if (ignoreParameterNames == null) { - ignoreParameterNames = new ArrayList<>(); + if (declaredParameterNames == null) { + declaredParameterNames = new ArrayList<>(); } - ignoreParameterNames.add(name); + declaredParameterNames.add(new ParamInfo(name, true, defaultValue)); } } - ignoreParameterNamesStack.add(ignoreParameterNames != null ? ignoreParameterNames : Collections.emptyList()); - } - - private void removeIgnoreStack() { - ignoreParameterNamesStack.remove(ignoreParameterNamesStack.size() - 1); + parameterNamesStack.add(declaredParameterNames != null ? declaredParameterNames : Collections.emptyList()); } @Override @@ -147,28 +153,37 @@ public boolean visit(MethodPart node) { @Override public boolean visit(ObjectPart node) { if (isValid(node)) { - String partName = node.getPartName(); - - for (List ignoreNames : ignoreParameterNamesStack) { - if (ignoreNames.contains(partName)) { - return super.visit(node); - } + String partName = node.getPartName(); // {foo} + + // Get the parameter info from the parameter stack + ParamInfo paramInfo = getParamInfo(partName); + if (paramInfo != null && paramInfo.assigned && paramInfo.defaultValue == null) { + // The part name (foo) is defined in parent section #let, #set + // which have not default value + // {#let foo="bar"} + // {foo} + // It is not a user tag parameter. + return super.visit(node); } + // Here we are in several usecase: + // 1. foo is not declared in none parent section -> user tag parameter is + // required + // 2. foo is declared in #if section -> user tag parameter is optional + // 3. foo is declared in #let section with default value (#let foo?="bar") -> + // user tag parameter is optional and have a defaut value + // Get or create the user tag parameter UserTagParameter parameter = parameters.get(partName); if (parameter == null) { parameter = new UserTagParameter(partName); + parameter.setDefaultValue(paramInfo != null ? paramInfo.defaultValue : null); parameters.put(partName, parameter); } - // Compute required flag if needed - if (!parameter.isRequired()) { - boolean ignore = false; - for (List optionalNames : optionalParameterNamesStack) { - if (optionalNames.contains(partName)) { - ignore = true; - } - } + + // Compute user tag parameter required flag if needed + if (!parameter.isRequired() && parameter.getDefaultValue() == null) { + boolean ignore = paramInfo != null && !paramInfo.assigned; if (!ignore) { boolean required = true; Parts parts = node.getParent(); @@ -191,6 +206,17 @@ public boolean visit(ObjectPart node) { return super.visit(node); } + private ParamInfo getParamInfo(String partName) { + for (List params : parameterNamesStack) { + for (ParamInfo paramInfo : params) { + if (paramInfo.name.equals(partName)) { + return paramInfo; + } + } + } + return null; + } + public boolean isValid(ObjectPart node) { if (node.getNamespace() != null) { // ex : {uri:Login....} diff --git a/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/services/inlayhint/InlayHintASTVistor.java b/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/services/inlayhint/InlayHintASTVistor.java index 5ee5d8274..58d959e27 100644 --- a/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/services/inlayhint/InlayHintASTVistor.java +++ b/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/services/inlayhint/InlayHintASTVistor.java @@ -20,6 +20,7 @@ import java.util.concurrent.CompletableFuture; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.stream.Collectors; import org.eclipse.lsp4j.Command; import org.eclipse.lsp4j.InlayHint; @@ -44,6 +45,8 @@ import com.redhat.qute.parser.template.sections.SetSection; import com.redhat.qute.project.QuteProject; import com.redhat.qute.project.datamodel.resolvers.MessageValueResolver; +import com.redhat.qute.project.tags.UserTag; +import com.redhat.qute.project.tags.UserTagParameter; import com.redhat.qute.services.QuteCompletableFutures; import com.redhat.qute.services.ResolvingJavaTypeContext; import com.redhat.qute.settings.QuteInlayHintSettings; @@ -184,39 +187,41 @@ public boolean visit(Expression node) { String partName = objectOrMethodPart.getPartName(); Template template = node.getOwnerTemplate(); QuteProject project = template.getProject(); - CompletableFuture messageFuture = project - .findMessageValueResolver(namespace, partName); - MessageValueResolver message = messageFuture.getNow(null); - if (message != null) { - // It is a message value resolver - // {msg:hello} - // {msg:hello_name('Lucie')} - - String messageContent = message.getMessage(); - if (messageContent != null) { - try { - // Display the message as inlay hint - // {msg:hello} [Hello!] - // {msg:hello_name('Lucie')} [Hello {name}!] - InlayHint hint = new InlayHint(); - hint.setKind(InlayHintKind.Type); - if (canSupportJavaDefinition) { - // Clickable Message to edit it (ex : 'Hello {name}!') - InlayHintLabelPart messagePart = new InlayHintLabelPart(messageContent); - Command javaDefCommand = createEditJavaMessageCommand(messageContent, - message.getSourceType(), - message.getName(), project.getUri()); - messagePart.setCommand(javaDefCommand); - messagePart.setTooltip(javaDefCommand.getTitle()); - hint.setLabel(Either.forRight(Arrays.asList(messagePart))); - } else { - hint.setLabel(Either.forLeft(messageContent)); + if (project != null) { + CompletableFuture messageFuture = project + .findMessageValueResolver(namespace, partName); + MessageValueResolver message = messageFuture.getNow(null); + if (message != null) { + // It is a message value resolver + // {msg:hello} + // {msg:hello_name('Lucie')} + + String messageContent = message.getMessage(); + if (messageContent != null) { + try { + // Display the message as inlay hint + // {msg:hello} [Hello!] + // {msg:hello_name('Lucie')} [Hello {name}!] + InlayHint hint = new InlayHint(); + hint.setKind(InlayHintKind.Type); + if (canSupportJavaDefinition) { + // Clickable Message to edit it (ex : 'Hello {name}!') + InlayHintLabelPart messagePart = new InlayHintLabelPart(messageContent); + Command javaDefCommand = createEditJavaMessageCommand(messageContent, + message.getSourceType(), + message.getName(), project.getUri()); + messagePart.setCommand(javaDefCommand); + messagePart.setTooltip(javaDefCommand.getTitle()); + hint.setLabel(Either.forRight(Arrays.asList(messagePart))); + } else { + hint.setLabel(Either.forLeft(messageContent)); + } + Position position = template.positionAt(node.getEnd()); + hint.setPosition(position); + inlayHints.add(hint); + } catch (Exception e) { + LOGGER.log(Level.SEVERE, "Error while creating inlay hint for message", e); } - Position position = template.positionAt(node.getEnd()); - hint.setPosition(position); - inlayHints.add(hint); - } catch (Exception e) { - LOGGER.log(Level.SEVERE, "Error while creating inlay hint for message", e); } } } @@ -240,18 +245,70 @@ private boolean isAfterEndParameterVisible(Section node) { } private void createInlayHintParametersSection(Section node) { - if (!inlayHintSettings.isShowSectionParameterType()) { + boolean isShowSectionParameterType = inlayHintSettings.isShowSectionParameterType(); + boolean isShowSectionParameterDefaultValue = inlayHintSettings.isShowSectionParameterDefaultValue(); + if (!isShowSectionParameterType && isShowSectionParameterDefaultValue) { return; } - // {#let user[:User]=item.owner } - // {#set user[:User]=item.owner } - // {#form id[:String]=item.id } - // {#form item.id[:String] } + List parameters = node.getParameters(); + Template template = node.getOwnerTemplate(); QuteProject project = template.getProject(); - List parameters = node.getParameters(); + + // In case of the section is an user tag, collect all parameters which have a + // default value (#let name?="foo") + List userTagParameterDefaultValues = null; + UserTag userTag = isShowSectionParameterDefaultValue && project != null ? project.findUserTag(node.getTag()) + : null; + if (userTag != null) { + userTagParameterDefaultValues = userTag.getParameters() + .stream() + .filter(p -> p.getDefaultValue() != null) + .map(p -> p.getName()) + .collect(Collectors.toList()); + + } + + // Loop for declared section parameters for (Parameter parameter : parameters) { - createJavaTypeInlayHint(parameter, template, project); + if (userTagParameterDefaultValues != null) { + userTagParameterDefaultValues.remove(parameter.getName()); + } + if (isShowSectionParameterType) { + // {#let user[:User]=item.owner } + // {#set user[:User]=item.owner } + // {#form id[:String]=item.id } + // {#form item.id[:String] } + createJavaTypeInlayHint(parameter, template, project); + } + } + + if (userTag != null && userTagParameterDefaultValues != null) { + // The section is an user tag and some default value parameters are not declared + // Display them with inlay hint + for (String name : userTagParameterDefaultValues) { + UserTagParameter userTagParameter = userTag.findParameter(name); + String defaultValue = userTagParameter.getDefaultValue(); + if (defaultValue != null) { + // {#bundleScript [name="main.js"] /} + createInlayHint(node, template, name, defaultValue); + } + } + } + } + + private void createInlayHint(Section node, Template template, String name, String defaultValue) { + try { + InlayHint hint = new InlayHint(); + hint.setKind(InlayHintKind.Parameter); + hint.setLabel(Either.forLeft(name + "=" + defaultValue)); + int end = node.getEndParametersOffset(); + Position position = template.positionAt(end); + hint.setPosition(position); + inlayHints.add(hint); + } catch (Exception e) { + LOGGER.log(Level.SEVERE, "Error while creating inlay hint for user tag default value parameter", + e); } } diff --git a/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/settings/QuteInlayHintSettings.java b/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/settings/QuteInlayHintSettings.java index a8f2bf9ba..f589ca2c6 100644 --- a/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/settings/QuteInlayHintSettings.java +++ b/qute.ls/com.redhat.qute.ls/src/main/java/com/redhat/qute/settings/QuteInlayHintSettings.java @@ -25,9 +25,12 @@ public class QuteInlayHintSettings { private boolean showSectionParameterType; + private boolean showSectionParameterDefaultValue; + public QuteInlayHintSettings() { setEnabled(true); setShowSectionParameterType(true); + setShowSectionParameterDefaultValue(true); } /** @@ -58,9 +61,18 @@ public void setShowSectionParameterType(boolean showSectionParameterType) { this.showSectionParameterType = showSectionParameterType; } + public boolean isShowSectionParameterDefaultValue() { + return showSectionParameterDefaultValue; + } + + public void setShowSectionParameterDefaultValue(boolean showSectionParameterDefaultValue) { + this.showSectionParameterDefaultValue = showSectionParameterDefaultValue; + } + public void update(QuteInlayHintSettings newInlayHint) { this.setEnabled(newInlayHint.isEnabled()); this.setShowSectionParameterType(newInlayHint.isShowSectionParameterType()); + this.setShowSectionParameterDefaultValue(newInlayHint.isShowSectionParameterDefaultValue()); } @Override @@ -68,6 +80,7 @@ public int hashCode() { final int prime = 31; int result = 1; result = prime * result + (enabled ? 1231 : 1237); + result = prime * result + (showSectionParameterDefaultValue ? 1231 : 1237); result = prime * result + (showSectionParameterType ? 1231 : 1237); return result; } @@ -83,6 +96,8 @@ public boolean equals(Object obj) { QuteInlayHintSettings other = (QuteInlayHintSettings) obj; if (enabled != other.enabled) return false; + if (showSectionParameterDefaultValue != other.showSectionParameterDefaultValue) + return false; if (showSectionParameterType != other.showSectionParameterType) return false; return true; diff --git a/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/services/completions/QuteCompletionWithUserTagTest.java b/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/services/completions/QuteCompletionWithUserTagTest.java index 32b5760e3..cc0ed1d92 100644 --- a/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/services/completions/QuteCompletionWithUserTagTest.java +++ b/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/services/completions/QuteCompletionWithUserTagTest.java @@ -49,6 +49,26 @@ public void bundleStyle() throws Exception { r(0, 0, 0, 2))); } + @Test + public void bundleStyleWithingParameter() throws Exception { + String template = "{#bundleStyle |"; + + // Without snippet + testCompletionFor(template, // + false, // no snippet support + 1, // + c("name", // + "name=\"main.css\"", // + r(0, 14, 0, 14))); + + // With snippet support + testCompletionFor(template, // + true, // snippet support + 1, // + c("name", // + "name=\"${1:main.css}\"$0", // + r(0, 14, 0, 14))); + } @Test public void input() throws Exception { String template = "{#|"; diff --git a/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/services/diagnostics/QuteDiagnosticsWithUserTagTest.java b/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/services/diagnostics/QuteDiagnosticsWithUserTagTest.java index 3a75bdbdd..461c2a5aa 100644 --- a/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/services/diagnostics/QuteDiagnosticsWithUserTagTest.java +++ b/qute.ls/com.redhat.qute.ls/src/test/java/com/redhat/qute/services/diagnostics/QuteDiagnosticsWithUserTagTest.java @@ -33,8 +33,16 @@ public class QuteDiagnosticsWithUserTagTest { @Test public void bundleStyle() { + // test with default value declared in bundleStyle.html user tag + // {#let name?="main.css"} + // In this case: + + // - name is optional String template = "{#bundleStyle /}"; testDiagnosticsFor(template); + // - name can be overridden + template = "{#bundleStyle name='foo.css'/}"; + testDiagnosticsFor(template); } @Test