diff --git a/platform/lang-api/src/com/intellij/codeInsight/lookup/LookupElementPresentation.java b/platform/lang-api/src/com/intellij/codeInsight/lookup/LookupElementPresentation.java index 8c3fd139f5d4..e2919e15f9cc 100644 --- a/platform/lang-api/src/com/intellij/codeInsight/lookup/LookupElementPresentation.java +++ b/platform/lang-api/src/com/intellij/codeInsight/lookup/LookupElementPresentation.java @@ -130,7 +130,6 @@ public class LookupElementPresentation { } @Nullable - @Deprecated public String getTailText() { if (myTail == null) return null; return StringUtil.join(myTail, new Function() { diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionPreview.java b/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionPreview.java new file mode 100644 index 000000000000..21c772d039e6 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionPreview.java @@ -0,0 +1,181 @@ +/* + * Copyright 2000-2012 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.completion; + +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.codeInsight.lookup.LookupElementPresentation; +import com.intellij.codeInsight.lookup.impl.LookupCellRenderer; +import com.intellij.codeInsight.lookup.impl.LookupImpl; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.application.AccessToken; +import com.intellij.openapi.application.WriteAction; +import com.intellij.openapi.command.CommandProcessor; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.markup.HighlighterLayer; +import com.intellij.openapi.editor.markup.HighlighterTargetArea; +import com.intellij.openapi.editor.markup.RangeHighlighter; +import com.intellij.openapi.editor.markup.TextAttributes; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.TextRange; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.Nullable; + +import java.awt.*; +import java.util.ArrayList; +import java.util.List; + +/** + * @author peter + */ +public class CompletionPreview { + private final LookupImpl myLookup; + private Disposable myUninstaller; + private int myPreviewStart; + + public CompletionPreview(LookupImpl lookup) { + myLookup = lookup; + } + + public void installPreview() { + List items = myLookup.getItems(); + if (items.isEmpty()) { + return; + } + + LookupElement first = items.get(0); + final String text = getPreviewText(first); + if (text == null) { + return; + } + + final String prefix = myLookup.itemPattern(first); + final Iterable fragments = LookupCellRenderer.getMatchingFragments(prefix, text); + if (fragments == null) { + return; + } + + ArrayList ranges = ContainerUtil.newArrayList(fragments); + if (ranges.isEmpty()) { + return; + } + + final int lastMatch = ranges.get(ranges.size() - 1).getEndOffset(); + + final Editor editor = myLookup.getEditor(); + final int caret = editor.getCaretModel().getOffset(); + myPreviewStart = caret - prefix.length(); + final int previewCaret = myPreviewStart + lastMatch; + final int previewEnd = myPreviewStart + text.length(); + + final List highlighters = ContainerUtil.newArrayList(); + myLookup.performGuardedChange(new Runnable() { + @Override + public void run() { + Runnable runnable = new Runnable() { + public void run() { + AccessToken token = WriteAction.start(); + try { + editor.getDocument().insertString(caret, text.substring(lastMatch)); + editor.getDocument().replaceString(myPreviewStart, caret, text.substring(0, lastMatch)); + editor.getCaretModel().moveToOffset(previewCaret); + } + finally { + token.finish(); + } + } + }; + CommandProcessor.getInstance().runUndoTransparentAction(runnable); + + int lastOffset = 0; + for (TextRange range : fragments) { + if (range.getStartOffset() > lastOffset) { + highlighters.add(createRange(lastOffset, range.getStartOffset(), true)); + } + highlighters.add(createRange(range.getStartOffset(), range.getEndOffset(), false)); + lastOffset = range.getEndOffset(); + } + if (lastOffset < text.length()) { + highlighters.add(createRange(lastOffset, text.length(), true)); + } + + } + }, "preview"); + myLookup.setPreview(this); + + myUninstaller = new Disposable() { + @Override + public void dispose() { + myLookup.setPreview(null); + myUninstaller = null; + + if (editor.isDisposed()) { + return; + } + + for (RangeHighlighter highlighter : highlighters) { + editor.getMarkupModel().removeHighlighter(highlighter); + } + + myLookup.performGuardedChange(new Runnable() { + @Override + public void run() { + Runnable runnable = new Runnable() { + public void run() { + AccessToken token = WriteAction.start(); + try { + editor.getDocument().replaceString(myPreviewStart, previewEnd, prefix); + editor.getCaretModel().moveToOffset(myPreviewStart + prefix.length()); + } + finally { + token.finish(); + } + } + }; + CommandProcessor.getInstance().runUndoTransparentAction(runnable); + } + }, "remove preview"); + } + }; + Disposer.register(myLookup, myUninstaller); + } + + private RangeHighlighter createRange(final int start, final int end, boolean generated) { + return myLookup.getEditor().getMarkupModel().addRangeHighlighter(myPreviewStart + start, myPreviewStart + end, HighlighterLayer.LAST, + new TextAttributes(generated ? Color.LIGHT_GRAY : Color.BLACK, null, null, null, Font.PLAIN), + HighlighterTargetArea.EXACT_RANGE); + } + + public void uninstallPreview() { + if (myUninstaller != null) { + Disposer.dispose(myUninstaller); + assert myUninstaller == null; + } + } + + @Nullable + private static String getPreviewText(LookupElement item) { + LookupElementPresentation presentation = LookupElementPresentation.renderElement(item); + String text = presentation.getItemText(); + if (text == null) { + return null; + } + String tailText = presentation.getTailText(); + if (tailText != null && tailText.startsWith("(") && tailText.contains(")")) { + text += "()"; + } + return text; + } +} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionProgressIndicator.java b/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionProgressIndicator.java index 29e7a907a531..86b8393dc212 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionProgressIndicator.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionProgressIndicator.java @@ -67,7 +67,6 @@ import com.intellij.util.containers.ContainerUtil; import com.intellij.util.messages.MessageBusConnection; import com.intellij.util.ui.update.MergingUpdateQueue; import com.intellij.util.ui.update.Update; -import gnu.trove.TObjectHashingStrategy; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.TestOnly; @@ -118,6 +117,7 @@ public class CompletionProgressIndicator extends ProgressIndicatorBase implement } }; private volatile int myCount; + private volatile boolean myShowPreview; private final ConcurrentHashMap myItemSorters = new ConcurrentHashMap( ContainerUtil.identityStrategy()); private final PropertyChangeListener myLookupManagerListener; @@ -170,7 +170,12 @@ public class CompletionProgressIndicator extends ProgressIndicatorBase implement void duringCompletion(CompletionInitializationContext initContext) { if (isAutopopupCompletion()) { if (shouldFocusLookup(myParameters)) { - myLookup.setFocused(true); + if (Registry.is("ide.completion.show.preview")) { + myShowPreview = true; + myLookup.setFocused(false); + } else { + myLookup.setFocused(true); + } } else if (FeatureUsageTracker.getInstance().isToBeAdvertisedInLookup(CodeCompletionFeatures.EDITING_COMPLETION_CONTROL_ENTER, getProject())) { myLookup.addAdvertisement("Press " + CompletionContributor.getActionShortcut(IdeActions.ACTION_CHOOSE_LOOKUP_ITEM_ALWAYS) + @@ -328,6 +333,9 @@ public class CompletionProgressIndicator extends ProgressIndicatorBase implement if (!myLookup.showLookup()) { return false; } + if (myShowPreview) { + new CompletionPreview(myLookup).installPreview(); + } justShown = true; } myLookup.refreshUi(true, justShown); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupCellRenderer.java b/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupCellRenderer.java index f13ca83b24ec..2f21a20d8f7b 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupCellRenderer.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupCellRenderer.java @@ -292,7 +292,7 @@ public class LookupCellRenderer implements ListCellRenderer { final String prefix = item instanceof EmptyLookupItem ? "" : myLookup.itemPattern(item); if (prefix.length() > 0) { - Iterable ranges = new MinusculeMatcher("*" + prefix, NameUtil.MatchingCaseSensitivity.NONE).matchingFragments(name); + Iterable ranges = getMatchingFragments(prefix, name); if (ranges != null) { SimpleTextAttributes highlighted = new SimpleTextAttributes(style, selected ? SELECTED_PREFIX_FOREGROUND_COLOR : PREFIX_FOREGROUND_COLOR); @@ -303,6 +303,10 @@ public class LookupCellRenderer implements ListCellRenderer { nameComponent.append(name, base); } + public static Iterable getMatchingFragments(String prefix, String name) { + return new MinusculeMatcher("*" + prefix, NameUtil.MatchingCaseSensitivity.NONE).matchingFragments(name); + } + private int setTypeTextLabel(LookupElement item, final Color background, Color foreground, 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 59446768a099..8c09529a7d37 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 @@ -17,10 +17,7 @@ package com.intellij.codeInsight.lookup.impl; import com.intellij.codeInsight.CodeInsightBundle; -import com.intellij.codeInsight.completion.CodeCompletionFeatures; -import com.intellij.codeInsight.completion.CompletionLookupArranger; -import com.intellij.codeInsight.completion.PrefixMatcher; -import com.intellij.codeInsight.completion.ShowHideIntentionIconLookupAction; +import com.intellij.codeInsight.completion.*; import com.intellij.codeInsight.completion.impl.CamelHumpMatcher; import com.intellij.codeInsight.hint.HintManager; import com.intellij.codeInsight.hint.HintManagerImpl; @@ -150,6 +147,7 @@ public class LookupImpl extends LightweightHint implements LookupEx, Disposable private int myMaximumHeight = Integer.MAX_VALUE; private boolean myFinishing; private boolean myUpdating; + private CompletionPreview myPreview; private final ModalityState myModalityState; public LookupImpl(Project project, Editor editor, @NotNull LookupArranger arranger) { @@ -728,6 +726,10 @@ public class LookupImpl extends LightweightHint implements LookupEx, Disposable public boolean performGuardedChange(Runnable change, @Nullable final String debug) { checkValid(); assert !myChangeGuard : "already in change"; + if (myPreview != null) { + myPreview.uninstallPreview(); + assert myPreview == null; + } myChangeGuard = true; boolean result; @@ -1467,4 +1469,12 @@ public class LookupImpl extends LightweightHint implements LookupEx, Disposable return myPresentableArranger.getRelevanceStrings(); } } + + public CompletionPreview getPreview() { + return myPreview; + } + + public void setPreview(CompletionPreview preview) { + myPreview = preview; + } } diff --git a/platform/platform-resources-en/src/misc/registry.properties b/platform/platform-resources-en/src/misc/registry.properties index 035f579cb6f2..04bec81612bb 100644 --- a/platform/platform-resources-en/src/misc/registry.properties +++ b/platform/platform-resources-en/src/misc/registry.properties @@ -196,6 +196,10 @@ ide.completion.allow.finishing.by.chars=true # suppress inspection "UnusedProperty" ide.completion.allow.finishing.by.chars.description=Controls whether typing dot, space, etc. may select the current lookup item +ide.completion.show.preview=false +# suppress inspection "UnusedProperty" +ide.completion.show.preview.description=Controls whether the completion autopopup should show its first result as a preview in editor + ide.completion.middle.matching=true # suppress inspection "UnusedProperty" ide.completion.middle.matching.description=Suggest items in completion that contain the entered string somewhere in the middle.