Skip to content

Commit fdbf4de

Browse files
committed
make property placeholders in xml files a bit more generic - allow to configure different placeholder start / end tokens and customize xml namespaces they're active in
1 parent 1cfd675 commit fdbf4de

25 files changed

+897
-193
lines changed

src/main/java/com/github/cameltooling/idea/completion/extension/CamelEndpointNameCompletionExtension.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public boolean isValid(@NotNull CompletionParameters parameters, @NotNull Proces
5757
if (query.contains("?")) {
5858
//do not add completions when inside endpoint query params
5959
return false;
60-
} else if (query.contains(CamelIdeaUtils.PROPERTY_PLACEHOLDER_START_TAG)) {
60+
} else if (query.contains(CamelIdeaUtils.PROPERTY_PLACEHOLDER_START_TOKEN)) {
6161
//do not add completions when endpoint contains property placeholders
6262
return false;
6363
}

src/main/java/com/github/cameltooling/idea/completion/extension/CamelPropertyCompletion.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,16 +87,16 @@ default Icon getFileIcon(VirtualFile vf) {
8787
};
8888
}
8989

90-
default String getPrefix(CompletionQuery query, @NotNull String placeholderStartTag) {
90+
default String getPrefix(CompletionQuery query, @NotNull String placeholderStartToken) {
9191
PsiAnnotation annotation = PsiTreeUtil.getParentOfType(query.element(), PsiAnnotation.class);
9292
if (annotation != null && CamelIdeaUtils.PROPERTY_INJECT_ANNOTATION.equals(annotation.getQualifiedName())) {
9393
return query.valueAtPosition();
9494
}
9595

9696
String prefix;
97-
int beginIndex = query.valueAtPosition().lastIndexOf(placeholderStartTag);
97+
int beginIndex = query.valueAtPosition().lastIndexOf(placeholderStartToken);
9898
if (beginIndex >= 0) {
99-
prefix = query.valueAtPosition().substring(beginIndex + placeholderStartTag.length());
99+
prefix = query.valueAtPosition().substring(beginIndex + placeholderStartToken.length());
100100
} else {
101101
prefix = "";
102102
}
@@ -130,8 +130,8 @@ public void handleInsert(@NotNull InsertionContext context, @NotNull LookupEleme
130130
boolean insidePropertyPlaceholder = CamelIdeaUtils.getService().hasUnclosedPropertyPlaceholder(query.valueAtPosition());
131131
boolean suffixClosesPlaceholder = CamelIdeaUtils.getService().closesPropertyPlaceholder(query.suffix());
132132
String docSuffix = doc.getText(new TextRange(pos, doc.getTextLength()));
133-
if (insidePropertyPlaceholder && !suffixClosesPlaceholder && !docSuffix.startsWith(CamelIdeaUtils.PROPERTY_PLACEHOLDER_END_TAG)) {
134-
doc.insertString(pos, CamelIdeaUtils.PROPERTY_PLACEHOLDER_END_TAG);
133+
if (insidePropertyPlaceholder && !suffixClosesPlaceholder && !docSuffix.startsWith(CamelIdeaUtils.PROPERTY_PLACEHOLDER_END_TOKEN)) {
134+
doc.insertString(pos, CamelIdeaUtils.PROPERTY_PLACEHOLDER_END_TOKEN);
135135
}
136136
}
137137

src/main/java/com/github/cameltooling/idea/completion/extension/CamelPropertyPlaceholderSmartCompletionExtension.java

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,11 @@
1717
package com.github.cameltooling.idea.completion.extension;
1818

1919
import java.util.ArrayList;
20+
import java.util.Arrays;
2021
import java.util.List;
2122
import java.util.Set;
2223

23-
import com.github.cameltooling.idea.util.BlueprintUtils;
24+
import com.github.cameltooling.idea.reference.propertyplaceholder.PropertyPlaceholderBasedPropertyReference;
2425
import com.github.cameltooling.idea.util.CamelIdeaUtils;
2526
import com.intellij.codeInsight.completion.CompletionParameters;
2627
import com.intellij.codeInsight.completion.CompletionResultSet;
@@ -32,6 +33,7 @@
3233
import com.intellij.openapi.vfs.VfsUtil;
3334
import com.intellij.openapi.vfs.VirtualFile;
3435
import com.intellij.psi.PsiAnnotation;
36+
import com.intellij.psi.PsiElement;
3537
import com.intellij.psi.PsiFile;
3638
import com.intellij.psi.util.PsiTreeUtil;
3739
import com.intellij.util.ProcessingContext;
@@ -50,7 +52,7 @@
5052
public class CamelPropertyPlaceholderSmartCompletionExtension implements CamelCompletionExtension {
5153

5254
private static final Logger LOG = Logger.getInstance(CamelPropertyPlaceholderSmartCompletionExtension.class);
53-
static final Key<String> PROP_PLACEHOLDER_START_TAG = new Key<>("placeholderStartTag");
55+
static final Key<String> PROP_PLACEHOLDER_START_TOKEN = new Key<>("placeholderStartToken");
5456

5557
private final List<CamelPropertyCompletion> propertyCompletionProviders = new ArrayList<>();
5658

@@ -121,25 +123,27 @@ public void addCompletions(@NotNull CompletionParameters parameters, @NotNull Pr
121123

122124
@Override
123125
public boolean isValid(@NotNull CompletionParameters parameters, ProcessingContext context, CompletionQuery query) {
124-
PsiAnnotation annotation = PsiTreeUtil.getParentOfType(parameters.getPosition(), PsiAnnotation.class);
126+
PsiElement position = parameters.getPosition();
127+
PsiAnnotation annotation = PsiTreeUtil.getParentOfType(position, PsiAnnotation.class);
125128
if (annotation != null && CamelIdeaUtils.PROPERTY_INJECT_ANNOTATION.equals(annotation.getQualifiedName())) {
126129
return true;
127130
}
128131

129132
if (CamelIdeaUtils.getService().hasUnclosedPropertyPlaceholder(query.valueAtPosition())) {
130-
context.put(PROP_PLACEHOLDER_START_TAG, CamelIdeaUtils.PROPERTY_PLACEHOLDER_START_TAG);
131-
return true;
132-
} else if (isAllowedBlueprintPlaceholderAt(query)) {
133-
context.put(PROP_PLACEHOLDER_START_TAG, BlueprintUtils.PROPERTY_PLACEHOLDER_START_TAG);
133+
context.put(PROP_PLACEHOLDER_START_TOKEN, CamelIdeaUtils.PROPERTY_PLACEHOLDER_START_TOKEN);
134134
return true;
135135
} else {
136+
PropertyPlaceholderBasedPropertyReference ref = Arrays.stream(position.getReferences())
137+
.filter(PropertyPlaceholderBasedPropertyReference.class::isInstance)
138+
.map(PropertyPlaceholderBasedPropertyReference.class::cast)
139+
.findFirst()
140+
.orElse(null);
141+
if (ref != null) {
142+
context.put(PROP_PLACEHOLDER_START_TOKEN, ref.getPlaceholderDefinition().getStartToken());
143+
return true;
144+
}
136145
return false;
137146
}
138147
}
139148

140-
private static boolean isAllowedBlueprintPlaceholderAt(CompletionQuery query) {
141-
return BlueprintUtils.getService().hasUnclosedPropertyPlaceholder(query.valueAtPosition())
142-
&& BlueprintUtils.getService().isAllowedPropertyPlaceholderLocation(query.element());
143-
}
144-
145149
}

src/main/java/com/github/cameltooling/idea/completion/extension/PropertiesPropertyPlaceholdersSmartCompletion.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
import com.intellij.util.ProcessingContext;
3232
import org.jetbrains.annotations.NotNull;
3333

34-
import static com.github.cameltooling.idea.completion.extension.CamelPropertyPlaceholderSmartCompletionExtension.PROP_PLACEHOLDER_START_TAG;
34+
import static com.github.cameltooling.idea.completion.extension.CamelPropertyPlaceholderSmartCompletionExtension.PROP_PLACEHOLDER_START_TOKEN;
3535
import static com.intellij.lang.properties.references.PropertiesCompletionContributor.LOOKUP_ELEMENT_RENDERER;
3636

3737
/**
@@ -53,8 +53,8 @@ public boolean isValidFile(@NotNull PsiFile file) {
5353

5454
@Override
5555
public void buildResultSet(@NotNull ProcessingContext context, CompletionResultSet resultSet, CompletionQuery query, PsiFile file) {
56-
String startTag = context.get(PROP_PLACEHOLDER_START_TAG);
57-
String prefix = getPrefix(query, startTag == null ? "" : startTag);
56+
String startToken = context.get(PROP_PLACEHOLDER_START_TOKEN);
57+
String prefix = getPrefix(query, startToken == null ? "" : startToken);
5858
if (file instanceof PropertiesFile pf) {
5959
addPropertyResults(pf.getProperties(),
6060
IProperty::getKey,

src/main/java/com/github/cameltooling/idea/completion/extension/YamlPropertyPlaceholdersSmartCompletion.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
import org.yaml.snakeyaml.Yaml;
3737
import org.yaml.snakeyaml.constructor.SafeConstructor;
3838

39-
import static com.github.cameltooling.idea.completion.extension.CamelPropertyPlaceholderSmartCompletionExtension.PROP_PLACEHOLDER_START_TAG;
39+
import static com.github.cameltooling.idea.completion.extension.CamelPropertyPlaceholderSmartCompletionExtension.PROP_PLACEHOLDER_START_TOKEN;
4040

4141
/**
4242
* To support smart completion where properties are loaded from <tt>.yaml</tt> files.
@@ -73,8 +73,8 @@ public void buildResultSet(@NotNull ProcessingContext context, CompletionResultS
7373
return;
7474
}
7575
VirtualFile virtualFile = file.getVirtualFile();
76-
String startTag = context.get(PROP_PLACEHOLDER_START_TAG);
77-
String prefix = getPrefix(query, startTag == null ? "" : startTag);
76+
String startToken = context.get(PROP_PLACEHOLDER_START_TOKEN);
77+
String prefix = getPrefix(query, startToken == null ? "" : startToken);
7878
CompletionContext ctx = new CompletionContext(prefix, virtualFile, query, resultSet);
7979
getProperties(virtualFile).forEach((key, value) -> {
8080
if (!isIgnored(key) && haveCommonStart(key, prefix)) {
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package com.github.cameltooling.idea.preference.propertyplaceholder;
18+
19+
import com.intellij.openapi.project.Project;
20+
import com.intellij.openapi.ui.DialogWrapper;
21+
import com.intellij.ui.ToolbarDecorator;
22+
import com.intellij.ui.components.JBLabel;
23+
import com.intellij.ui.components.JBList;
24+
import com.intellij.util.ui.JBUI;
25+
import org.jetbrains.annotations.Nullable;
26+
27+
import javax.swing.*;
28+
import java.awt.*;
29+
import java.util.ArrayList;
30+
import java.util.List;
31+
32+
class PropertyPlaceholderEditDialog extends DialogWrapper {
33+
34+
private JTextField startField;
35+
private JTextField endField;
36+
37+
private DefaultListModel<String> namespacesModel;
38+
private JBList<String> namespacesList;
39+
40+
PropertyPlaceholderEditDialog(Project project) {
41+
super(project, true);
42+
init();
43+
}
44+
45+
@Override
46+
protected void init() {
47+
setTitle("Edit Property Placeholder Definition");
48+
startField = new JTextField();
49+
endField = new JTextField();
50+
namespacesModel = new DefaultListModel<>();
51+
namespacesList = new JBList<>(namespacesModel);
52+
namespacesList.setVisibleRowCount(6);
53+
super.init();
54+
}
55+
56+
@Nullable
57+
@Override
58+
protected JComponent createCenterPanel() {
59+
JPanel panel = new JPanel(new GridBagLayout());
60+
GridBagConstraints gbcLabel = new GridBagConstraints();
61+
gbcLabel.gridx = 0;
62+
gbcLabel.anchor = GridBagConstraints.WEST;
63+
gbcLabel.insets = JBUI.insets(5, 5, 0, 5);
64+
65+
GridBagConstraints gbcField = new GridBagConstraints();
66+
gbcField.gridx = 1;
67+
gbcField.weightx = 1.0;
68+
gbcField.fill = GridBagConstraints.HORIZONTAL;
69+
gbcField.insets = JBUI.insets(5, 0, 0, 5);
70+
71+
int row = 0;
72+
gbcLabel.gridy = row;
73+
gbcField.gridy = row;
74+
panel.add(new JBLabel("Start token:"), gbcLabel);
75+
panel.add(startField, gbcField);
76+
77+
row++;
78+
gbcLabel.gridy = row;
79+
gbcField.gridy = row;
80+
panel.add(new JBLabel("End token:"), gbcLabel);
81+
panel.add(endField, gbcField);
82+
83+
row++;
84+
gbcLabel.gridy = row;
85+
gbcField.gridy = row;
86+
gbcField.weighty = 1.0;
87+
gbcField.fill = GridBagConstraints.BOTH;
88+
89+
panel.add(new JBLabel("XML namespaces:"), gbcLabel);
90+
91+
// Decorate the list with add/remove/edit actions
92+
ToolbarDecorator decorator = ToolbarDecorator.createDecorator(namespacesList)
93+
.setAddAction(button -> addNamespace())
94+
.setEditAction(button -> editSelectedNamespace())
95+
.setRemoveAction(button -> removeSelectedNamespaces());
96+
JComponent listPanel = decorator.createPanel();
97+
panel.add(listPanel, gbcField);
98+
99+
startField.setPreferredSize(new Dimension(200, startField.getPreferredSize().height));
100+
endField.setPreferredSize(new Dimension(200, endField.getPreferredSize().height));
101+
102+
return panel;
103+
}
104+
105+
private void addNamespace() {
106+
String value = JOptionPane.showInputDialog(getContentPanel(), "New namespace:", "Add Namespace", JOptionPane.PLAIN_MESSAGE);
107+
if (value != null) {
108+
String trimmed = value.trim();
109+
if (!trimmed.isEmpty() && !containsNamespace(trimmed)) {
110+
namespacesModel.addElement(trimmed);
111+
}
112+
}
113+
}
114+
115+
private void editSelectedNamespace() {
116+
int idx = namespacesList.getSelectedIndex();
117+
if (idx >= 0) {
118+
String current = namespacesModel.get(idx);
119+
String value = JOptionPane.showInputDialog(getContentPanel(), "Edit namespace:", current);
120+
if (value != null) {
121+
String trimmed = value.trim();
122+
if (!trimmed.isEmpty() && (!containsNamespace(trimmed) || trimmed.equals(current))) {
123+
namespacesModel.set(idx, trimmed);
124+
}
125+
}
126+
}
127+
}
128+
129+
private void removeSelectedNamespaces() {
130+
int[] indices = namespacesList.getSelectedIndices();
131+
if (indices.length > 0) {
132+
// Remove from last to first to keep indices valid
133+
for (int i = indices.length - 1; i >= 0; i--) {
134+
namespacesModel.remove(indices[i]);
135+
}
136+
}
137+
}
138+
139+
private boolean containsNamespace(String ns) {
140+
for (int i = 0; i < namespacesModel.getSize(); i++) {
141+
if (namespacesModel.get(i).equals(ns)) return true;
142+
}
143+
return false;
144+
}
145+
146+
@Override
147+
public JComponent getPreferredFocusedComponent() {
148+
return startField;
149+
}
150+
151+
void initValues(PropertyPlaceholderSettingsEntry entry) {
152+
if (entry != null) {
153+
startField.setText(entry.getStartToken());
154+
endField.setText(entry.getEndToken());
155+
namespacesModel.clear();
156+
for (String ns : entry.getNamespaces()) {
157+
if (ns != null) {
158+
String trimmed = ns.trim();
159+
if (!trimmed.isEmpty() && !containsNamespace(trimmed)) {
160+
namespacesModel.addElement(trimmed);
161+
}
162+
}
163+
}
164+
}
165+
}
166+
167+
String getStartToken() {
168+
return startField.getText().trim();
169+
}
170+
171+
String getEndToken() {
172+
return endField.getText().trim();
173+
}
174+
175+
List<String> getNamespaces() {
176+
List<String> result = new ArrayList<>();
177+
for (int i = 0; i < namespacesModel.getSize(); i++) {
178+
result.add(namespacesModel.get(i));
179+
}
180+
return result;
181+
}
182+
183+
}

0 commit comments

Comments
 (0)