diff --git a/src/main/java/org/overengineer/inlineproblems/InlineDrawer.java b/src/main/java/org/overengineer/inlineproblems/InlineDrawer.java index 5d9f920..b064f36 100644 --- a/src/main/java/org/overengineer/inlineproblems/InlineDrawer.java +++ b/src/main/java/org/overengineer/inlineproblems/InlineDrawer.java @@ -38,6 +38,7 @@ public void drawProblemLabel(InlineProblem problem) { problem, drawDetails.getTextColor(), drawDetails.getBackgroundColor(), + drawDetails.getTextColor().brighter(), settings ); diff --git a/src/main/java/org/overengineer/inlineproblems/InlineProblemLabel.java b/src/main/java/org/overengineer/inlineproblems/InlineProblemLabel.java index 6adb7ab..accaa18 100644 --- a/src/main/java/org/overengineer/inlineproblems/InlineProblemLabel.java +++ b/src/main/java/org/overengineer/inlineproblems/InlineProblemLabel.java @@ -1,15 +1,26 @@ package org.overengineer.inlineproblems; +import com.intellij.codeInsight.hints.presentation.InputHandler; +import com.intellij.codeInsight.intention.impl.ShowIntentionActionsHandler; import com.intellij.ide.ui.AntialiasingType; import com.intellij.ide.ui.UISettings; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.IdeActions; +import com.intellij.openapi.actionSystem.ex.ActionUtil; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.EditorCustomElementRenderer; import com.intellij.openapi.editor.Inlay; +import com.intellij.openapi.editor.ScrollType; import com.intellij.openapi.editor.impl.FontInfo; import com.intellij.openapi.editor.markup.GutterIconRenderer; import com.intellij.openapi.editor.markup.TextAttributes; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiFile; +import com.intellij.psi.util.PsiUtilBase; +import com.intellij.ui.paint.EffectPainter; import lombok.Getter; import lombok.Setter; +import org.jdesktop.swingx.action.ActionManager; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.overengineer.inlineproblems.entities.InlineProblem; @@ -17,18 +28,23 @@ import org.overengineer.inlineproblems.utils.FontUtil; import java.awt.*; +import java.awt.event.MouseEvent; import java.awt.font.FontRenderContext; @Getter -public class InlineProblemLabel implements EditorCustomElementRenderer { - +public class InlineProblemLabel implements EditorCustomElementRenderer, InputHandler { private final String text; private final Color textColor; private final Color backgroundColor; + private final Color hoverColor; private final boolean isDrawBox; private final boolean isRoundedCorners; private final boolean isFillBackground; + private boolean hovered; + private final boolean clickableContext; + private Inlay inlay; + private final int actualStartOffset; @Setter private boolean isBlockElement; @@ -47,10 +63,12 @@ public InlineProblemLabel( final InlineProblem problem, final Color textColor, final Color backgroundColor, + final Color hoverColor, final SettingsState settings ) { this.textColor = textColor; this.backgroundColor = backgroundColor; + this.hoverColor = hoverColor; this.isDrawBox = settings.isDrawBoxesAroundErrorLabels(); this.isRoundedCorners = settings.isRoundedCornerBoxes(); this.text = problem.getText(); @@ -59,6 +77,9 @@ public InlineProblemLabel( this.isUseEditorFont = settings.isUseEditorFont(); this.inlayFontSizeDelta = settings.getInlayFontSizeDelta(); + this.hovered = false; + this.actualStartOffset = problem.getActualStartffset(); + this.clickableContext = settings.isClickableContext(); } @Override @@ -80,7 +101,7 @@ public int calcWidthInPixels(@NotNull Editor editor) { @Override public void paint(@NotNull Inlay inlay, @NotNull Graphics graphics, @NotNull Rectangle targetRegion, @NotNull TextAttributes textAttributes) { Editor editor = inlay.getEditor(); - + this.inlay = inlay; // These offsets are applied here and not in the calc functions itself because we use it to shrink the drawn stuff a little bit int width = calcWidthInPixels(inlay) + DRAW_BOX_WIDTH_OFFSET; int height = calcHeightInPixels(inlay) + DRAW_BOX_HEIGHT_OFFSET; @@ -92,7 +113,7 @@ public void paint(@NotNull Inlay inlay, @NotNull Graphics graphics, @NotNull Rec // Apply delta on the boxes if (inlayFontSizeDelta != 0 && editorFontSize > inlayFontSizeDelta) { height -= inlayFontSizeDelta; - targetRegionY += (int)(inlayFontSizeDelta / 1.5); + targetRegionY += (int) (inlayFontSizeDelta / 1.5); } if (isDrawBox) { @@ -118,14 +139,13 @@ public void paint(@NotNull Inlay inlay, @NotNull Graphics graphics, @NotNull Rec 5 ); } - } - else { - graphics.drawRect( - targetRegion.x, - targetRegionY, - width, - height - ); + } else { + graphics.drawRect( + targetRegion.x, + targetRegionY, + width, + height + ); if (isFillBackground) { graphics.fillRect( @@ -138,7 +158,7 @@ public void paint(@NotNull Inlay inlay, @NotNull Graphics graphics, @NotNull Rec } } - graphics.setColor(textColor); + graphics.setColor(hovered ? hoverColor : textColor); graphics.setFont(FontUtil.getActiveFont(editor)); @@ -147,10 +167,63 @@ public void paint(@NotNull Inlay inlay, @NotNull Graphics graphics, @NotNull Rec targetRegion.x + DRAW_STRING_LINE_PLACEMENT_OFFSET_X, targetRegion.y + DRAW_STRING_LINE_PLACEMENT_OFFSET_Y + editor.getAscent() ); + if (hovered) + EffectPainter.LINE_UNDERSCORE.paint( + (Graphics2D) graphics, + targetRegion.x - DRAW_BOX_WIDTH_OFFSET, + targetRegion.y + editor.getAscent() , + width + (DRAW_BOX_WIDTH_OFFSET * 2), + editor.getAscent(), + FontUtil.getActiveFont(editor)); + } + + private void setHovered(boolean hovered) { + if (!this.clickableContext || this.hovered == hovered) { + return; + } + this.hovered = hovered; + if (inlay != null) + inlay.repaint(); } @Override public @Nullable GutterIconRenderer calcGutterIconRenderer(@NotNull Inlay inlay) { return EditorCustomElementRenderer.super.calcGutterIconRenderer(inlay); } + + @Override + public void mouseClicked(@NotNull MouseEvent mouseEvent, @NotNull Point point) { + if (!clickableContext) { + return; + } + if (mouseEvent.getButton() == MouseEvent.BUTTON1) { + mouseEvent.consume(); + + var editor = inlay.getEditor(); + editor.getCaretModel().moveToOffset(actualStartOffset); + editor.getScrollingModel().scrollToCaret(ScrollType.CENTER); + + var action = ActionManager.getInstance().getAction(IdeActions.ACTION_SHOW_INTENTION_ACTIONS); + if (action == null) { + Project project = editor.getProject(); + if (project == null) return; + PsiFile psiFileInEditor = PsiUtilBase.getPsiFileInEditor(editor, project); + if (psiFileInEditor == null) return; + new ShowIntentionActionsHandler().invoke(project, editor, psiFileInEditor, false); + } else { + ActionUtil.invokeAction((AnAction) action, editor.getComponent(), "EditorInlay", null, null); + } + } + } + + @Override + public void mouseMoved(@NotNull MouseEvent mouseEvent, @NotNull Point point) { + setHovered(true); + } + + @Override + public void mouseExited() { + setHovered(false); + } + } diff --git a/src/main/java/org/overengineer/inlineproblems/entities/InlineProblem.java b/src/main/java/org/overengineer/inlineproblems/entities/InlineProblem.java index c377223..b780efd 100644 --- a/src/main/java/org/overengineer/inlineproblems/entities/InlineProblem.java +++ b/src/main/java/org/overengineer/inlineproblems/entities/InlineProblem.java @@ -30,6 +30,7 @@ public class InlineProblem { private DrawDetails drawDetails; private int actualEndOffset; + private int actualStartffset; private boolean isBlockElement = false; @@ -68,6 +69,7 @@ public InlineProblem( this.project = textEditor.getEditor().getProject(); this.highlightInfoStartOffset = highlightInfo.hashCode(); this.rangeHighlighterHashCode = rangeHighlighter.hashCode(); + this.actualStartffset = highlightInfo.getStartOffset(); if (highlightInfo.getActualEndOffset() == 0) this.actualEndOffset = highlightInfo.getActualEndOffset(); diff --git a/src/main/java/org/overengineer/inlineproblems/settings/SettingsComponent.java b/src/main/java/org/overengineer/inlineproblems/settings/SettingsComponent.java index ca45b5e..68f59ab 100644 --- a/src/main/java/org/overengineer/inlineproblems/settings/SettingsComponent.java +++ b/src/main/java/org/overengineer/inlineproblems/settings/SettingsComponent.java @@ -68,6 +68,8 @@ public class SettingsComponent { private final JBCheckBox fillProblemLabels = new JBCheckBox(SettingsBundle.message("settings.fillProblemLabels")); private final JBCheckBox boldProblemLabels = new JBCheckBox(SettingsBundle.message("settings.boldProblemLabels")); private final JBCheckBox italicProblemLabels = new JBCheckBox(SettingsBundle.message("settings.italicProblemLabels")); + private final JBCheckBox clickableContext = new JBCheckBox(SettingsBundle.message("settings.enableClickableContext")); + private final JBTextField problemFilterList = new JBTextField(); private final JBTextField fileExtensionBlacklist = new JBTextField(); @@ -77,7 +79,7 @@ public class SettingsComponent { private final JBTextField additionalInfoSeverities = new JBTextField(); private final JBTextField additionalWarningSeverities = new JBTextField(); private final JBTextField additionalWeakWarningSeverities = new JBTextField(); - private final JBTextField additionalErrorSeverities = new JBTextField(); + private final JBTextField additionalErrorSeverities = new JBTextField(); @Getter private final JPanel settingsPanel; @@ -133,6 +135,7 @@ public SettingsComponent() { fillProblemLabels.setSelected(settingsState.isFillProblemLabels()); boldProblemLabels.setSelected(settingsState.isBoldProblemLabels()); italicProblemLabels.setSelected(settingsState.isItalicProblemLabels()); + clickableContext.setSelected(settingsState.isClickableContext()); additionalInfoSeverities.setText(settingsState.getAdditionalInfoSeveritiesAsString()); additionalWeakWarningSeverities.setText(settingsState.getAdditionalWeakWarningSeveritiesAsString()); @@ -154,6 +157,7 @@ public SettingsComponent() { .addComponent(fillProblemLabels, 0) .addComponent(boldProblemLabels, 0) .addComponent(italicProblemLabels, 0) + .addComponent(clickableContext, 0) .addSeparator() .addComponent(new JBLabel(SettingsBundle.message("settings.submenu.general"))) .addLabeledComponent(new JBLabel(SettingsBundle.message("settings.activeProblemListener")), enabledListener) @@ -264,8 +268,8 @@ public int getInlayFontSizeDelta() { // Convert the String try { val = Integer.parseInt(inlayFontSizeDeltaText.getText()); + } catch (NumberFormatException ignored) { } - catch (NumberFormatException ignored) {} if (val < 0) val = 0; @@ -317,6 +321,14 @@ public void setItalicProblemLabels(boolean isSelected) { italicProblemLabels.setSelected(isSelected); } + public boolean isClickableContext() { + return clickableContext.isSelected(); + } + + public void setClickableContext(boolean isSelected) { + clickableContext.setSelected(isSelected); + } + public boolean isShowErrors() { return showErrors.isSelected(); } @@ -600,8 +612,7 @@ private List getSeverityIntegerList(String text) { public int getManualScannerDelay() { try { return Math.max(Integer.parseInt(manualScannerDelay.getText()), 10); - } - catch (NumberFormatException e) { + } catch (NumberFormatException e) { return 100; } } diff --git a/src/main/java/org/overengineer/inlineproblems/settings/SettingsConfigurable.java b/src/main/java/org/overengineer/inlineproblems/settings/SettingsConfigurable.java index 276d653..a3f3299 100644 --- a/src/main/java/org/overengineer/inlineproblems/settings/SettingsConfigurable.java +++ b/src/main/java/org/overengineer/inlineproblems/settings/SettingsConfigurable.java @@ -52,6 +52,7 @@ public boolean isModified() { state.isFillProblemLabels() == settingsComponent.isFillProblemLabels() && state.isBoldProblemLabels() == settingsComponent.isBoldProblemLabels() && state.isItalicProblemLabels() == settingsComponent.isItalicProblemLabels() && + state.isClickableContext() == settingsComponent.isClickableContext() && state.getErrorTextColor().equals(settingsComponent.getErrorTextColor()) && state.getErrorBackgroundColor().equals(settingsComponent.getErrorLabelBackgroundColor()) && @@ -143,6 +144,7 @@ public void apply() { state.setFillProblemLabels(settingsComponent.isFillProblemLabels()); state.setBoldProblemLabels(settingsComponent.isBoldProblemLabels()); state.setItalicProblemLabels(settingsComponent.isItalicProblemLabels()); + state.setClickableContext(settingsComponent.isClickableContext()); state.setEnabledListener(settingsComponent.getEnabledListener()); state.setManualScannerDelay(settingsComponent.getManualScannerDelay()); @@ -213,6 +215,7 @@ public void reset() { settingsComponent.setFillProblemLabels(state.isFillProblemLabels()); settingsComponent.setBoldProblemLabels(state.isBoldProblemLabels()); settingsComponent.setItalicProblemLabels(state.isItalicProblemLabels()); + settingsComponent.setClickableContext(state.isClickableContext()); settingsComponent.setEnabledListener(state.getEnabledListener()); settingsComponent.setManualScannerDelay(state.getManualScannerDelay()); diff --git a/src/main/java/org/overengineer/inlineproblems/settings/SettingsState.java b/src/main/java/org/overengineer/inlineproblems/settings/SettingsState.java index b8ba8d6..9e451da 100644 --- a/src/main/java/org/overengineer/inlineproblems/settings/SettingsState.java +++ b/src/main/java/org/overengineer/inlineproblems/settings/SettingsState.java @@ -40,6 +40,7 @@ public class SettingsState implements PersistentStateComponent { private boolean showInfos = false; private boolean highlightInfos = false; private boolean showInfosInGutter = false; + private boolean clickableContext = false; /** * Colors renamed from 'Color' to 'Col' to solve diff --git a/src/main/resources/messages/SettingsBundle_en.properties b/src/main/resources/messages/SettingsBundle_en.properties index f1a04b5..844be19 100644 --- a/src/main/resources/messages/SettingsBundle_en.properties +++ b/src/main/resources/messages/SettingsBundle_en.properties @@ -52,4 +52,5 @@ settings.forceProblemsInOneLine=Force problems in the same line even if they are settings.useEditorFont=Use editor font instead of tooltip font settings.showOnlyHighestPerLine=Show only the problem with the highest severity per line settings.enableHtmlStripping=Enable stripping of HTML in the messages -settings.enableXmlUnescaping=Enable unescaping of XML in the messages \ No newline at end of file +settings.enableXmlUnescaping=Enable unescaping of XML in the messages +settings.enableClickableContext=Enable clickable inline problems for IDE context actions \ No newline at end of file diff --git a/src/main/resources/messages/SettingsBundle_zh_CN.properties b/src/main/resources/messages/SettingsBundle_zh_CN.properties index 0737999..17e91c6 100644 --- a/src/main/resources/messages/SettingsBundle_zh_CN.properties +++ b/src/main/resources/messages/SettingsBundle_zh_CN.properties @@ -51,3 +51,4 @@ settings.useEditorFont=\u4F7F\u7528\u7F16\u8F91\u5668\u5B57\u4F53\u800C\u4E0D\u6 settings.showOnlyHighestPerLine=\u4ec5\u663e\u793a\u6bcf\u884c\u4e2d\u6700\u4e25\u91cd\u7684\u95ee\u9898 settings.enableHtmlStripping=Enable stripping of HTML in the messages settings.enableXmlUnescaping=Enable unescaping of XML in the messages +settings.enableClickableContext=Enable clickable inline problems for IDE context actions \ No newline at end of file