mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
SSR: show filters in editor inlays instead of tool tips (IDEA-210453)
GitOrigin-RevId: 34555e014ed4c698b814966333dff0d740506bab
This commit is contained in:
committed by
intellij-monorepo-bot
parent
d7b1667b34
commit
0899b035e7
+13
-7
@@ -273,13 +273,19 @@ public class StructuralSearchDialog extends DialogWrapper {
|
||||
private void initializeFilterPanel() {
|
||||
final MatchOptions matchOptions = getConfiguration().getMatchOptions();
|
||||
final CompiledPattern compiledPattern = PatternCompiler.compilePattern(getProject(), matchOptions, false, false);
|
||||
if (compiledPattern != null) {
|
||||
myFilterPanel.setCompiledPattern(compiledPattern);
|
||||
}
|
||||
if (!myFilterPanel.isInitialized()) {
|
||||
myFilterPanel.initFilters(UIUtil.getOrAddVariableConstraint(Configuration.CONTEXT_VAR_NAME, myConfiguration));
|
||||
}
|
||||
myFilterPanel.setValid(compiledPattern != null);
|
||||
ApplicationManager.getApplication().invokeLater(() -> {
|
||||
if (compiledPattern != null) {
|
||||
final SubstitutionShortInfoHandler handler = SubstitutionShortInfoHandler.retrieve(mySearchCriteriaEdit.getEditor());
|
||||
if (handler != null) {
|
||||
handler.updateEditorInlays();
|
||||
}
|
||||
myFilterPanel.setCompiledPattern(compiledPattern);
|
||||
}
|
||||
if (!myFilterPanel.isInitialized()) {
|
||||
myFilterPanel.initFilters(UIUtil.getOrAddVariableConstraint(Configuration.CONTEXT_VAR_NAME, myConfiguration));
|
||||
}
|
||||
myFilterPanel.setValid(compiledPattern != null);
|
||||
});
|
||||
}
|
||||
|
||||
private Configuration createConfiguration(Configuration template) {
|
||||
|
||||
+133
-26
@@ -1,13 +1,16 @@
|
||||
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.structuralsearch.plugin.ui;
|
||||
|
||||
import com.intellij.codeInsight.hint.TooltipController;
|
||||
import com.intellij.codeInsight.hint.TooltipGroup;
|
||||
import com.intellij.codeInsight.template.Template;
|
||||
import com.intellij.codeInsight.template.TemplateManager;
|
||||
import com.intellij.codeInsight.template.impl.TemplateImplUtil;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.editor.LogicalPosition;
|
||||
import com.intellij.openapi.editor.*;
|
||||
import com.intellij.openapi.editor.event.*;
|
||||
import com.intellij.openapi.editor.markup.TextAttributes;
|
||||
import com.intellij.openapi.ui.GraphicsConfig;
|
||||
import com.intellij.openapi.util.Disposer;
|
||||
import com.intellij.openapi.util.Key;
|
||||
import com.intellij.openapi.util.registry.Registry;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
@@ -19,6 +22,7 @@ import com.intellij.structuralsearch.plugin.replace.ui.ReplaceConfiguration;
|
||||
import com.intellij.ui.ColorUtil;
|
||||
import com.intellij.ui.HintHint;
|
||||
import com.intellij.util.SmartList;
|
||||
import com.intellij.util.ui.GraphicsUtil;
|
||||
import com.intellij.util.ui.JBUI;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -26,7 +30,10 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class SubstitutionShortInfoHandler implements DocumentListener, EditorMouseMotionListener, CaretListener {
|
||||
@@ -37,8 +44,9 @@ public class SubstitutionShortInfoHandler implements DocumentListener, EditorMou
|
||||
private final Editor editor;
|
||||
@Nullable private final Consumer<? super String> myCurrentVariableCallback;
|
||||
public static final Key<Configuration> CURRENT_CONFIGURATION_KEY = Key.create("SS.CurrentConfiguration");
|
||||
private final Map<String, Inlay<FilterRenderer>> inlays = new HashMap<>();
|
||||
|
||||
SubstitutionShortInfoHandler(@NotNull Editor _editor, @Nullable Consumer<? super String> currentVariableCallback) {
|
||||
private SubstitutionShortInfoHandler(@NotNull Editor _editor, @Nullable Consumer<? super String> currentVariableCallback) {
|
||||
editor = _editor;
|
||||
myCurrentVariableCallback = currentVariableCallback;
|
||||
}
|
||||
@@ -98,7 +106,7 @@ public class SubstitutionShortInfoHandler implements DocumentListener, EditorMou
|
||||
myCurrentVariableCallback.accept(Configuration.CONTEXT_VAR_NAME);
|
||||
}
|
||||
|
||||
if (variableName != null) {
|
||||
if (variableName != null && !text.isEmpty()) {
|
||||
showTooltip(editor, start, end + 1, text);
|
||||
}
|
||||
}
|
||||
@@ -109,6 +117,7 @@ public class SubstitutionShortInfoHandler implements DocumentListener, EditorMou
|
||||
variables.clear();
|
||||
variables.addAll(TemplateImplUtil.parseVariables(document.getCharsSequence()).keySet());
|
||||
modificationTimeStamp = document.getModificationStamp();
|
||||
updateEditorInlays();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,6 +131,7 @@ public class SubstitutionShortInfoHandler implements DocumentListener, EditorMou
|
||||
if (event.getOldLength() == event.getNewLength()) return;
|
||||
// to handle backspace & delete (backspace strangely is not reported to the caret listener)
|
||||
handleInputFocusMovement(editor.getCaretModel().getLogicalPosition(), true);
|
||||
updateEditorInlays();
|
||||
}
|
||||
|
||||
public List<String> getVariables() {
|
||||
@@ -131,9 +141,9 @@ public class SubstitutionShortInfoHandler implements DocumentListener, EditorMou
|
||||
|
||||
@NotNull
|
||||
static String getShortParamString(NamedScriptableDefinition namedScriptableDefinition, boolean editLink) {
|
||||
final boolean newDialog = Registry.is("ssr.use.new.search.dialog");
|
||||
final boolean verbose = !Registry.is("ssr.use.new.search.dialog");
|
||||
if (namedScriptableDefinition == null) {
|
||||
return SSRBundle.message(newDialog ? "no.filters.tooltip.message" : "no.constraints.specified.tooltip.message");
|
||||
return verbose ? SSRBundle.message("no.constraints.specified.tooltip.message") : "";
|
||||
}
|
||||
|
||||
final StringBuilder buf = new StringBuilder();
|
||||
@@ -142,10 +152,18 @@ public class SubstitutionShortInfoHandler implements DocumentListener, EditorMou
|
||||
final String linkColor = ColorUtil.toHtmlColor(JBUI.CurrentTheme.Link.linkColor());
|
||||
if (namedScriptableDefinition instanceof MatchVariableConstraint) {
|
||||
final MatchVariableConstraint constraint = (MatchVariableConstraint)namedScriptableDefinition;
|
||||
if (constraint.isPartOfSearchResults() && !newDialog) {
|
||||
final String name = constraint.getName();
|
||||
if (!Configuration.CONTEXT_VAR_NAME.equals(name)) {
|
||||
final int maxCount = constraint.getMaxCount();
|
||||
final int minCount = constraint.getMinCount();
|
||||
if (verbose || minCount != 1 || maxCount != 1) {
|
||||
append(buf, SSRBundle.message("min.occurs.tooltip.message", minCount, (maxCount == Integer.MAX_VALUE) ? "∞" : maxCount));
|
||||
}
|
||||
}
|
||||
if (constraint.isPartOfSearchResults() && verbose) {
|
||||
append(buf, SSRBundle.message("target.tooltip.message"));
|
||||
}
|
||||
if (constraint.getRegExp() != null && !constraint.getRegExp().isEmpty()) {
|
||||
if (!constraint.getRegExp().isEmpty()) {
|
||||
append(buf, SSRBundle.message("text.tooltip.message",
|
||||
constraint.isInvertRegExp() ? 1 : 0,
|
||||
StringUtil.escapeXmlEntities(constraint.getRegExp()),
|
||||
@@ -161,7 +179,6 @@ public class SubstitutionShortInfoHandler implements DocumentListener, EditorMou
|
||||
append(buf, SSRBundle.message("reference.target.tooltip.message", constraint.isInvertReference() ? 1 : 0, text));
|
||||
}
|
||||
|
||||
constraint.getNameOfExprType();
|
||||
if (!constraint.getNameOfExprType().isEmpty()) {
|
||||
append(buf, SSRBundle.message("exprtype.tooltip.message",
|
||||
constraint.isInvertExprType() ? 1 : 0,
|
||||
@@ -183,27 +200,17 @@ public class SubstitutionShortInfoHandler implements DocumentListener, EditorMou
|
||||
final String text = StringUtil.escapeXmlEntities(StringUtil.unquoteString(constraint.getWithinConstraint()));
|
||||
append(buf, SSRBundle.message("within.constraints.tooltip.message", constraint.isInvertWithinConstraint() ? 1 : 0, text));
|
||||
}
|
||||
|
||||
final String name = constraint.getName();
|
||||
if (!Configuration.CONTEXT_VAR_NAME.equals(name)) {
|
||||
final int maxCount = constraint.getMaxCount();
|
||||
final int minCount = constraint.getMinCount();
|
||||
if (!newDialog || minCount != 1 || maxCount != 1) {
|
||||
append(buf, SSRBundle.message("min.occurs.tooltip.message", minCount, (maxCount == Integer.MAX_VALUE) ? "∞" : maxCount));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final String script = namedScriptableDefinition.getScriptCodeConstraint();
|
||||
if (script != null && script.length() > 2) {
|
||||
final String text = "<pre><code>" + StringUtil.escapeXmlEntities(StringUtil.unquoteString(script)) + "</code></pre>";
|
||||
append(buf, SSRBundle.message("script.tooltip.message", text));
|
||||
append(buf, SSRBundle.message("script.tooltip.message"));
|
||||
}
|
||||
|
||||
if (buf.length() == 0 && !editLink) {
|
||||
buf.append(SSRBundle.message(!newDialog ? "no.constraints.specified.tooltip.message" : "no.filters.tooltip.message"));
|
||||
if (buf.length() == 0 && !editLink && verbose) {
|
||||
buf.append(SSRBundle.message("no.constraints.specified.tooltip.message"));
|
||||
}
|
||||
if (editLink && newDialog) {
|
||||
if (editLink && !verbose && !Registry.is("ssr.use.editor.inlays.instead.of.tool.tips")) {
|
||||
if (buf.length() > 0) buf.append("<br>");
|
||||
buf.append("<a style=\"color:")
|
||||
.append(linkColor)
|
||||
@@ -220,6 +227,9 @@ public class SubstitutionShortInfoHandler implements DocumentListener, EditorMou
|
||||
}
|
||||
|
||||
private static void showTooltip(@NotNull Editor editor, final int start, int end, @NotNull String text) {
|
||||
if (Registry.is("ssr.use.editor.inlays.instead.of.tool.tips") && Registry.is("ssr.use.new.search.dialog")) {
|
||||
return;
|
||||
}
|
||||
final Rectangle visibleArea = editor.getScrollingModel().getVisibleArea();
|
||||
final Point left = editor.logicalPositionToXY(editor.offsetToLogicalPosition(start));
|
||||
final int documentLength = editor.getDocument().getTextLength();
|
||||
@@ -244,7 +254,7 @@ public class SubstitutionShortInfoHandler implements DocumentListener, EditorMou
|
||||
}
|
||||
|
||||
static SubstitutionShortInfoHandler retrieve(Editor editor) {
|
||||
return editor.getUserData(LISTENER_KEY);
|
||||
return editor == null ? null : editor.getUserData(LISTENER_KEY);
|
||||
}
|
||||
|
||||
static void install(Editor editor, @Nullable Consumer<? super String> currentVariableCallback) {
|
||||
@@ -254,4 +264,101 @@ public class SubstitutionShortInfoHandler implements DocumentListener, EditorMou
|
||||
editor.getCaretModel().addCaretListener(handler);
|
||||
editor.putUserData(LISTENER_KEY, handler);
|
||||
}
|
||||
|
||||
void updateEditorInlays() {
|
||||
if (!Registry.is("ssr.use.editor.inlays.instead.of.tool.tips") || !Registry.is("ssr.use.new.search.dialog")) {
|
||||
return;
|
||||
}
|
||||
final String text = editor.getDocument().getText();
|
||||
final Template template = TemplateManager.getInstance(editor.getProject()).createTemplate("", "", text);
|
||||
final int segmentsCount = template.getSegmentsCount();
|
||||
final InlayModel inlayModel = editor.getInlayModel();
|
||||
final HashSet<String> variables = new HashSet<>(inlays.keySet());
|
||||
final Configuration configuration = editor.getUserData(CURRENT_CONFIGURATION_KEY);
|
||||
if (configuration == null) return;
|
||||
int variableNameLength = 0;
|
||||
for (int i = 0; i < segmentsCount; i++) {
|
||||
final int offset = template.getSegmentOffset(i);
|
||||
final String name = template.getSegmentName(i);
|
||||
variableNameLength += name.length() + 2;
|
||||
final NamedScriptableDefinition variable = configuration.findVariable(name);
|
||||
final String labelText = getShortParamString(variable, false);
|
||||
if (labelText.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
final Inlay<FilterRenderer> inlay = inlays.get(name);
|
||||
if (inlay == null) {
|
||||
inlays.put(name, inlayModel.addInlineElement(offset + variableNameLength, new FilterRenderer(labelText)));
|
||||
}
|
||||
else {
|
||||
final FilterRenderer renderer = inlay.getRenderer();
|
||||
renderer.setText(labelText);
|
||||
inlay.updateSize();
|
||||
variables.remove(name);
|
||||
}
|
||||
}
|
||||
final Inlay<FilterRenderer> inlay = inlays.get(Configuration.CONTEXT_VAR_NAME);
|
||||
if (inlay == null) {
|
||||
final NamedScriptableDefinition variable = configuration.findVariable(Configuration.CONTEXT_VAR_NAME);
|
||||
final String labelText = getShortParamString(variable, false);
|
||||
if (!labelText.isEmpty()) {
|
||||
inlays.put(Configuration.CONTEXT_VAR_NAME,
|
||||
inlayModel.addBlockElement(text.length() + variableNameLength, true, false, 0,
|
||||
new FilterRenderer("complete pattern: " + labelText)));
|
||||
}
|
||||
}
|
||||
for (String variable : variables) {
|
||||
Disposer.dispose(inlays.remove(variable));
|
||||
}
|
||||
}
|
||||
|
||||
private static class FilterRenderer implements EditorCustomElementRenderer {
|
||||
|
||||
private String myText;
|
||||
|
||||
FilterRenderer(String text) {
|
||||
myText = text;
|
||||
}
|
||||
|
||||
public void setText(String text) {
|
||||
myText = text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int calcWidthInPixels(@NotNull Editor editor) {
|
||||
return getFontMetrics(editor).stringWidth(myText) + 12;
|
||||
}
|
||||
|
||||
private static Font getFont() {
|
||||
return UIManager.getFont("Label.font");
|
||||
}
|
||||
|
||||
private static FontMetrics getFontMetrics(Editor editor) {
|
||||
return editor.getContentComponent().getFontMetrics(getFont()) ;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paint(@NotNull Inlay inlay, @NotNull Graphics g, @NotNull Rectangle r, @NotNull TextAttributes textAttributes) {
|
||||
final Editor editor = inlay.getEditor();
|
||||
final TextAttributes attributes = editor.getColorsScheme().getAttributes(DefaultLanguageHighlighterColors.INLINE_PARAMETER_HINT);
|
||||
if (attributes == null) {
|
||||
return;
|
||||
}
|
||||
final FontMetrics metrics = getFontMetrics(editor);
|
||||
final Color backgroundColor = attributes.getBackgroundColor();
|
||||
if (backgroundColor != null) {
|
||||
final GraphicsConfig config = GraphicsUtil.setupAAPainting(g);
|
||||
GraphicsUtil.paintWithAlpha(g, 0.55f);
|
||||
g.setColor(backgroundColor);
|
||||
g.fillRoundRect(r.x + 2, r.y, r.width - 4, r.height, 8, 8);
|
||||
config.restore();
|
||||
}
|
||||
final Color foregroundColor = attributes.getForegroundColor();
|
||||
if (foregroundColor != null) {
|
||||
g.setColor(foregroundColor);
|
||||
g.setFont(getFont());
|
||||
g.drawString(myText, r.x + 6, r.y + r.height - metrics.getDescent());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import com.intellij.openapi.fileTypes.FileTypes;
|
||||
import com.intellij.openapi.fileTypes.LanguageFileType;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.popup.Balloon;
|
||||
import com.intellij.openapi.util.registry.Registry;
|
||||
import com.intellij.openapi.wm.ToolWindowId;
|
||||
import com.intellij.psi.PsiDocumentManager;
|
||||
import com.intellij.psi.PsiElement;
|
||||
@@ -197,6 +198,9 @@ public class UIUtil {
|
||||
completeMatchInfo.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseEntered(MouseEvent ignore) {
|
||||
if (Registry.is("ssr.use.editor.inlays.instead.of.tool.tips") && Registry.is("ssr.use.new.search.dialog")) {
|
||||
return;
|
||||
}
|
||||
final Configuration configuration = configurationProducer.get();
|
||||
if (configuration == null) {
|
||||
return;
|
||||
|
||||
@@ -187,14 +187,13 @@ editvarcontraints.edit.variables=Edit Variables
|
||||
no.constraints.specified.tooltip.message=no constraints specified
|
||||
no.filters.tooltip.message=no filters
|
||||
script.option.text=Script text\:
|
||||
occurs.tooltip.message=occurs: {0}
|
||||
min.occurs.tooltip.message=count=[{0},{1}]
|
||||
min.occurs.tooltip.message=[{0},{1}]
|
||||
target.tooltip.message=target
|
||||
text.tooltip.message=text{0,choice,0#=|1#\u2260}{1}{2,choice,0#|1#'<span style="color:{4}">, whole words</span>'}{3,choice,0#|1#'<span style="color:{4}">, within hierarchy</span>'}
|
||||
hierarchy.tooltip.message=search within hierarchy
|
||||
hierarchy.tooltip.message=within hierarchy
|
||||
exprtype.tooltip.message=type{0,choice,0#=|1#\u2260}{1}{2,choice,0#|1#'<span style="color:{3}">, within hierarchy</span>'}
|
||||
expected.type.tooltip.message=expected type{0,choice,0#=|1#\u2260}{1}{2,choice,0#|1#'<span style="color:{3}">, within hierarchy</span>'}
|
||||
script.tooltip.message=script=<br> {0}
|
||||
script.tooltip.message=script
|
||||
within.constraints.tooltip.message=within{0,choice,0#=|1#\u2260}{1}
|
||||
reference.target.tooltip.message=reference{0,choice,0#=|1#\u2260}{1}
|
||||
complete.match.variable.tooltip.message=<html>Complete Match: {0}
|
||||
|
||||
@@ -1544,6 +1544,8 @@ ssr.template.from.selection.builder=false
|
||||
ssr.template.from.selection.builder.description=Allows to build template with live-template-like placeholders when invoking SSR from editor with selection
|
||||
ssr.save.templates.to.ide.instead.of.project.workspace=true
|
||||
ssr.save.templates.to.ide.instead.of.project.workspace.description=Makes saved Structural Search templates globally available for all projects
|
||||
ssr.use.editor.inlays.instead.of.tool.tips=true
|
||||
ssr.use.editor.inlays.instead.of.tool.tips.description=Replaces filter tool tips with editor inlays in the new Structural Search dialog
|
||||
|
||||
jdk.regex.soe.workaround=true
|
||||
jdk.regex.soe.workaround.description=In regular expression pattern replace choice \\n|. with . (and DOT_ALL option) to prevent stack overflow during matching
|
||||
|
||||
Reference in New Issue
Block a user