Skip to content

Commit 6252664

Browse files
[DQL] Switching to a new Inlay engine (#127)
* [DQL] Switching to a new Inlay engine * Resolving code review issues
1 parent 549808c commit 6252664

File tree

12 files changed

+296
-100
lines changed

12 files changed

+296
-100
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
tenants.
1818
- Double-clicking the executed DQL query in the `Services` tab will now either open the related file (if possible),
1919
or open a new DQL query console with the execution context.
20+
- Switched to a new Inlay provider. The user can now change inlay settings in `Settings` > `Editor` > `Inlay hints` >
21+
`Other` > `DQL`.
2022
- The "Show DQL query" option when executing DQL will now try to show the parsed query instead of the raw one, if
2123
possible.
2224
- Adding support for "Expression DQL" file (with `.dqlexpr` extension), which allows to define a DQL expression without

src/main/grammar/dql.bnf

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,11 @@
6565
}
6666

6767
root ::= query?
68-
query ::= command (command)* { pin=1 }
68+
query ::= command (command)* {
69+
pin=1
70+
mixin="pl.thedeem.intellij.dql.psi.elements.impl.QueryElementImpl"
71+
implements="pl.thedeem.intellij.dql.psi.elements.QueryElement"
72+
}
6973

7074
command ::= PIPE? command_keyword command_parameters? {
7175
pin=2

src/main/java/pl/thedeem/intellij/dql/highlighting/DQLInlayParameterHintsProvider.java

Lines changed: 0 additions & 60 deletions
This file was deleted.
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
package pl.thedeem.intellij.dql.highlighting.hints;
2+
3+
import com.intellij.codeInsight.hints.*;
4+
import com.intellij.codeInsight.hints.presentation.PresentationFactory;
5+
import com.intellij.openapi.editor.Editor;
6+
import com.intellij.psi.PsiElement;
7+
import com.intellij.psi.PsiFile;
8+
import com.intellij.ui.components.JBCheckBox;
9+
import com.intellij.util.xmlb.annotations.Attribute;
10+
import org.jetbrains.annotations.Nls;
11+
import org.jetbrains.annotations.NotNull;
12+
import pl.thedeem.intellij.dql.DQLBundle;
13+
import pl.thedeem.intellij.dql.definition.model.MappedParameter;
14+
import pl.thedeem.intellij.dql.definition.model.Parameter;
15+
import pl.thedeem.intellij.dql.psi.DQLCommand;
16+
import pl.thedeem.intellij.dql.psi.DQLFunctionExpression;
17+
import pl.thedeem.intellij.dql.psi.DQLParameterExpression;
18+
19+
import javax.swing.*;
20+
import java.util.ArrayList;
21+
import java.util.List;
22+
import java.util.function.Consumer;
23+
24+
/**
25+
* Provides inlay for parameters in DQL.
26+
* Yields a lot of experimental usages warnings, but it is the recommended way to implement this feature.
27+
*/
28+
public class DQLParameterNameHintsProvider implements InlayHintsProvider<DQLParameterNameHintsProvider.DQLParameterNameHintsSettings> {
29+
private static final SettingsKey<DQLParameterNameHintsSettings> KEY = new SettingsKey<>("dql.parameter.name.hints");
30+
31+
@Override
32+
public @NotNull SettingsKey<DQLParameterNameHintsSettings> getKey() {
33+
return KEY;
34+
}
35+
36+
@Override
37+
public @NotNull String getName() {
38+
return DQLBundle.message("settings.inlayHints.parameters.name");
39+
}
40+
41+
@Override
42+
public @NotNull @Nls String getDescription() {
43+
return DQLBundle.message("settings.inlayHints.parameters.description");
44+
}
45+
46+
@Override
47+
public @NotNull DQLParameterNameHintsSettings createSettings() {
48+
return new DQLParameterNameHintsSettings();
49+
}
50+
51+
@Override
52+
public @NotNull ImmediateConfigurable createConfigurable(@NotNull DQLParameterNameHintsSettings settings) {
53+
return listener -> {
54+
JPanel panel = new JPanel();
55+
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
56+
panel.add(createOptionComponent(
57+
DQLBundle.message("settings.inlayHints.parameters.functions.description"),
58+
settings.showFunctionHints,
59+
(selected) -> {
60+
settings.showFunctionHints = selected;
61+
listener.settingsChanged();
62+
}
63+
));
64+
panel.add(createOptionComponent(
65+
DQLBundle.message("settings.inlayHints.parameters.commands.description"),
66+
settings.showCommandHints,
67+
(selected) -> {
68+
settings.showCommandHints = selected;
69+
listener.settingsChanged();
70+
}
71+
));
72+
panel.add(createOptionComponent(
73+
DQLBundle.message("settings.inlayHints.parameters.hideHintsForFirstCommandParameter.description"),
74+
settings.hideHintsForFirstCommandParameter,
75+
(selected) -> {
76+
settings.hideHintsForFirstCommandParameter = selected;
77+
listener.settingsChanged();
78+
}
79+
));
80+
panel.add(createOptionComponent(
81+
DQLBundle.message("settings.inlayHints.parameters.showHintsForSingularParameter.description"),
82+
settings.showHintsForSingularParameter,
83+
(selected) -> {
84+
settings.showHintsForSingularParameter = selected;
85+
listener.settingsChanged();
86+
}
87+
));
88+
return panel;
89+
};
90+
}
91+
92+
@Override
93+
public @NotNull String getPreviewText() {
94+
return /* language=DQLPart */ """
95+
data record(f1 = 5, f2 = 10, f3 = 10, field = "10")
96+
| filter matchesValue("", "value", "otherValue", caseSensitive: true)
97+
| parse field, ""\"
98+
INT:i
99+
""\"
100+
| summarize by: {
101+
i
102+
}, {
103+
f1 = max(f1), f2 = min(f2), count()
104+
}
105+
| sort i desc
106+
""";
107+
}
108+
109+
@Override
110+
public InlayHintsCollector getCollectorFor(@NotNull PsiFile file,
111+
@NotNull Editor editor,
112+
@NotNull DQLParameterNameHintsSettings settings,
113+
@NotNull InlayHintsSink sink) {
114+
PresentationFactory factory = new PresentationFactory(editor);
115+
116+
return new FactoryInlayHintsCollector(editor) {
117+
@Override
118+
public boolean collect(@NotNull PsiElement element, @NotNull Editor editor, @NotNull InlayHintsSink sink) {
119+
if (element instanceof DQLCommand command && settings.showCommandHints) {
120+
addHints(factory, sink, getHints(element, command.getParameters(), settings));
121+
} else if (element instanceof DQLFunctionExpression function && settings.showFunctionHints) {
122+
addHints(factory, sink, getHints(element, function.getParameters(), settings));
123+
}
124+
return true;
125+
}
126+
};
127+
}
128+
129+
@SuppressWarnings("UnstableApiUsage")
130+
protected void addHints(@NotNull PresentationFactory factory,
131+
@NotNull InlayHintsSink sink,
132+
@NotNull List<Hint> hints) {
133+
for (Hint hint : hints) {
134+
var p = factory.roundWithBackgroundAndSmallInset(factory.smallTextWithoutBackground(hint.text + ":"));
135+
sink.addInlineElement(hint.offset, false, p, false);
136+
}
137+
}
138+
139+
private static @NotNull List<Hint> getHints(@NotNull PsiElement element, @NotNull List<MappedParameter> parameters, @NotNull DQLParameterNameHintsSettings settings) {
140+
List<Hint> result = new ArrayList<>();
141+
if (parameters.size() == 1 && !settings.showHintsForSingularParameter) {
142+
return result;
143+
}
144+
for (int i = 0; i < parameters.size(); i++) {
145+
if (i == 0 && element instanceof DQLCommand && settings.hideHintsForFirstCommandParameter) {
146+
continue;
147+
}
148+
MappedParameter parameter = parameters.get(i);
149+
Parameter definition = parameter.definition();
150+
if (definition == null) {
151+
continue;
152+
}
153+
List<List<PsiElement>> groups = parameter.getParameterGroups();
154+
for (List<PsiElement> group : groups) {
155+
if (!(group.getFirst() instanceof DQLParameterExpression)) {
156+
int textOffset = group.getFirst().getTextOffset();
157+
if (definition.variadic()) {
158+
result.add(new Hint((!parameter.included().isEmpty() ? "…" : "") + definition.name(), textOffset));
159+
} else if (parameters.size() > 1) {
160+
result.add(new Hint(definition.name(), textOffset));
161+
}
162+
}
163+
}
164+
}
165+
return result;
166+
}
167+
168+
protected @NotNull JComponent createOptionComponent(@NotNull String name, boolean isSelected, @NotNull Consumer<Boolean> onChange) {
169+
JCheckBox check = new JBCheckBox(name, isSelected);
170+
check.addActionListener(e -> onChange.accept(check.isSelected()));
171+
return check;
172+
}
173+
174+
protected record Hint(@NotNull String text, int offset) {
175+
}
176+
177+
public static final class DQLParameterNameHintsSettings {
178+
@Attribute("showFunctionHints")
179+
public boolean showFunctionHints = true;
180+
181+
@Attribute("showCommandHints")
182+
public boolean showCommandHints = true;
183+
184+
@Attribute("hideHintsForFirstCommandParameter")
185+
public boolean hideHintsForFirstCommandParameter = true;
186+
187+
@Attribute("showHintsForSingularParameter")
188+
public boolean showHintsForSingularParameter = false;
189+
}
190+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package pl.thedeem.intellij.dql.psi.elements;
2+
3+
import org.jetbrains.annotations.NotNull;
4+
5+
import java.util.List;
6+
import java.util.Map;
7+
8+
public interface QueryElement extends BaseElement {
9+
@NotNull Map<String, List<VariableElement>> getDefinedVariables();
10+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package pl.thedeem.intellij.dql.psi.elements.impl;
2+
3+
import com.intellij.extapi.psi.ASTWrapperPsiElement;
4+
import com.intellij.lang.ASTNode;
5+
import com.intellij.navigation.ItemPresentation;
6+
import com.intellij.openapi.util.Key;
7+
import com.intellij.psi.util.*;
8+
import org.jetbrains.annotations.NotNull;
9+
import pl.thedeem.intellij.common.StandardItemPresentation;
10+
import pl.thedeem.intellij.dql.DQLBundle;
11+
import pl.thedeem.intellij.dql.DQLIcon;
12+
import pl.thedeem.intellij.dql.psi.DQLVariableExpression;
13+
import pl.thedeem.intellij.dql.psi.elements.QueryElement;
14+
import pl.thedeem.intellij.dql.psi.elements.VariableElement;
15+
import pl.thedeem.intellij.dql.services.query.DQLFieldNamesService;
16+
17+
import java.util.*;
18+
19+
public abstract class QueryElementImpl extends ASTWrapperPsiElement implements QueryElement {
20+
private static final Key<CachedValue<Map<String, List<VariableElement>>>> CACHED_DEFINED_VARIABLES =
21+
Key.create("DQL_QUERY_DEFINED_VARIABLES");
22+
23+
public QueryElementImpl(@NotNull ASTNode node) {
24+
super(node);
25+
}
26+
27+
@Override
28+
public ItemPresentation getPresentation() {
29+
return new StandardItemPresentation(DQLBundle.message("presentation.query"), this, DQLIcon.DYNATRACE_LOGO);
30+
}
31+
32+
@Override
33+
public String getFieldName() {
34+
return DQLFieldNamesService.getInstance().calculateFieldName(getName());
35+
}
36+
37+
@Override
38+
public @NotNull Map<String, List<VariableElement>> getDefinedVariables() {
39+
CachedValue<Map<String, List<VariableElement>>> cached = getUserData(CACHED_DEFINED_VARIABLES);
40+
if (cached == null) {
41+
cached = CachedValuesManager.getManager(getProject()).createCachedValue(
42+
() -> new CachedValueProvider.Result<>(recalculateVariables(),
43+
PsiModificationTracker.MODIFICATION_COUNT
44+
),
45+
false
46+
);
47+
putUserData(CACHED_DEFINED_VARIABLES, cached);
48+
}
49+
return Objects.requireNonNullElse(cached.getValue(), Map.of());
50+
}
51+
52+
private @NotNull Map<String, List<VariableElement>> recalculateVariables() {
53+
Map<String, List<VariableElement>> result = new HashMap<>();
54+
for (DQLVariableExpression var : PsiTreeUtil.findChildrenOfType(this, DQLVariableExpression.class)) {
55+
String name = var.getName();
56+
result.computeIfAbsent(name, k -> new ArrayList<>()).add(var);
57+
}
58+
return Collections.unmodifiableMap(result);
59+
}
60+
}

src/main/java/pl/thedeem/intellij/dql/psi/elements/impl/VariableElementImpl.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,11 +124,11 @@ public boolean accessesData() {
124124
@Override
125125
public @Nullable String getValue() {
126126
PsiElement definition = getDefinition();
127+
DQLVariablesService service = DQLVariablesService.getInstance(getProject());
127128
if (definition instanceof JsonProperty property) {
128-
DQLVariablesService service = DQLVariablesService.getInstance(getProject());
129129
return service.getVariableValue(property.getValue());
130130
}
131-
return null;
131+
return service.getVariableValue(null);
132132
}
133133

134134
private PsiElement recalculateReference() {

src/main/java/pl/thedeem/intellij/dql/services/variables/DQLVariablesService.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import com.intellij.json.psi.JsonValue;
44
import com.intellij.openapi.project.Project;
5-
import com.intellij.openapi.util.Key;
65
import com.intellij.psi.PsiElement;
76
import com.intellij.psi.PsiFile;
87
import org.jetbrains.annotations.NotNull;
@@ -13,13 +12,11 @@
1312
import java.util.List;
1413

1514
public interface DQLVariablesService {
16-
Key<List<VariableDefinition>> DEFINED_VARIABLES = Key.create("DEFINED_DQL_VARIABLES");
17-
1815
static DQLVariablesService getInstance(@NotNull Project project) {
1916
return project.getService(DQLVariablesService.class);
2017
}
2118

22-
@Nullable Path getDefaultVariablesFile(PsiElement element);
19+
@Nullable Path getDefaultVariablesFile(@NotNull PsiElement element);
2320

2421
@NotNull List<PsiElement> findVariableDefinitionFiles(@NotNull String variableName, @NotNull PsiFile file);
2522

0 commit comments

Comments
 (0)