From c9dfdab0c9220a914ad980c5fda9f8b444cfffaf Mon Sep 17 00:00:00 2001 From: peter Date: Tue, 18 Oct 2016 16:54:10 +0200 Subject: [PATCH] Preview in the editor of the approximate insertion result of the currently selected lookup suggestion --- .../codeInsight/lookup/impl/LookupImpl.java | 2 + .../lookup/impl/LookupPreview.java | 113 ++++++++++++++++++ .../util/resources/misc/registry.properties | 5 +- 3 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupPreview.java diff --git a/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupImpl.java b/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupImpl.java index dafddf1bab56..fc39d201bee8 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupImpl.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupImpl.java @@ -105,6 +105,7 @@ public class LookupImpl extends LightweightHint implements LookupEx, Disposable private final List myListeners = ContainerUtil.createLockFreeCopyOnWriteList(); private PrefixChangeListener myPrefixChangeListener = new PrefixChangeListener.Adapter() {}; + private final LookupPreview myPreview = new LookupPreview(this); private long myStampShown = 0; private boolean myShown = false; @@ -853,6 +854,7 @@ public class LookupImpl extends LightweightHint implements LookupEx, Disposable listener.currentItemChanged(event); } } + myPreview.updatePreview(currentItem); } public boolean fillInCommonPrefix(boolean explicitlyInvoked) { diff --git a/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupPreview.java b/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupPreview.java new file mode 100644 index 000000000000..aa3cf7e50395 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupPreview.java @@ -0,0 +1,113 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.codeInsight.lookup.impl; + +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.codeInsight.lookup.LookupElementPresentation; +import com.intellij.openapi.editor.*; +import com.intellij.openapi.editor.colors.EditorColors; +import com.intellij.openapi.editor.colors.EditorFontType; +import com.intellij.openapi.editor.impl.EditorImpl; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.util.registry.Registry; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.ui.JBColor; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.FList; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.awt.*; +import java.util.ArrayList; +import java.util.List; + +/** + * @author peter + */ +class LookupPreview { + private final List myInlays = new ArrayList<>(); + private final LookupImpl myLookup; + + LookupPreview(LookupImpl lookup) { + myLookup = lookup; + } + + void updatePreview(@Nullable LookupElement item) { + if (!Registry.is("ide.lookup.preview.insertion")) return; + + myInlays.forEach(Disposer::dispose); + myInlays.clear(); + + String suffix = getSuffixText(item); + if (!suffix.isEmpty() && myLookup.getTopLevelEditor() instanceof EditorImpl) { + myLookup.getTopLevelEditor().getCaretModel().runForEachCaret(caret -> { + ensureCaretBeforeInlays(caret); + addInlay(suffix, caret.getOffset()); + }); + } + } + + private static void ensureCaretBeforeInlays(Caret caret) { + LogicalPosition position = caret.getLogicalPosition(); + if (position.leansForward) { + caret.moveToLogicalPosition(position.leanForward(false)); + } + } + + private String getSuffixText(@Nullable LookupElement item) { + if (item != null) { + String itemText = StringUtil.notNullize(LookupElementPresentation.renderElement(item).getItemText()); + FList fragments = LookupCellRenderer.getMatchingFragments(myLookup.itemPattern(item), itemText); + if (fragments != null && !fragments.isEmpty()) { + List list = ContainerUtil.newArrayList(fragments); + return itemText.substring(list.get(list.size() - 1).getEndOffset(), itemText.length()); + } + } + return ""; + } + + private void addInlay(String suffix, int caretOffset) { + Inlay inlay = myLookup.getTopLevelEditor().getInlayModel().addInlineElement(caretOffset, createGrayRenderer(suffix)); + if (inlay != null) { + myInlays.add(inlay); + Disposer.register(myLookup, inlay); + } + } + + @NotNull + private static EditorCustomElementRenderer createGrayRenderer(final String suffix) { + return new EditorCustomElementRenderer() { + @Override + public int calcWidthInPixels(@NotNull Editor editor) { + return editor.getContentComponent().getFontMetrics(getFont(editor)).stringWidth(suffix); + } + + @Override + public void paint(@NotNull Editor editor, @NotNull Graphics g, @NotNull Rectangle r) { + g.setColor(editor.getColorsScheme().getColor(EditorColors.CARET_ROW_COLOR)); + g.fillRect(r.x, r.y, r.width, r.height); + g.setColor(JBColor.GRAY); + g.setFont(getFont(editor)); + g.drawString(suffix, r.x, r.y + ((EditorImpl)editor).getAscent()); + } + + private Font getFont(@NotNull Editor editor) { + return editor.getColorsScheme().getFont(EditorFontType.PLAIN); + } + }; + } +} diff --git a/platform/util/resources/misc/registry.properties b/platform/util/resources/misc/registry.properties index f625b37dc18f..6449f11f07b5 100644 --- a/platform/util/resources/misc/registry.properties +++ b/platform/util/resources/misc/registry.properties @@ -381,6 +381,9 @@ ide.completion.show.lower.case.classes.description=Show non-imported classes sta ide.completion.delay.autopopup.until.completed=false ide.completion.delay.autopopup.until.completed.description=Controls if completion autopopup is shown immediately and populated in background, or delayed until all suggestion are calculated +ide.lookup.preview.insertion=false +ide.lookup.preview.insertion.description=Preview in the editor of the approximate insertion result of the currently selected lookup suggestion + ide.completion.middle.matching=true ide.completion.middle.matching.description=Suggest items in completion that contain the entered string somewhere in the middle. @@ -888,4 +891,4 @@ ide.projectView.globalOptions.description=Make Project View options such as auto compiler.ref.index=false compiler.ref.index.description=Enables find usages using references from compiler indices -compiler.ref.index.restartRequired=true \ No newline at end of file +compiler.ref.index.restartRequired=true