completion preview initial

This commit is contained in:
peter
2012-12-05 11:54:41 +01:00
parent 08e2322de8
commit efc263cd5e
6 changed files with 214 additions and 8 deletions
@@ -130,7 +130,6 @@ public class LookupElementPresentation {
}
@Nullable
@Deprecated
public String getTailText() {
if (myTail == null) return null;
return StringUtil.join(myTail, new Function<TextFragment, String>() {
@@ -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<LookupElement> 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<TextRange> fragments = LookupCellRenderer.getMatchingFragments(prefix, text);
if (fragments == null) {
return;
}
ArrayList<TextRange> 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<RangeHighlighter> 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;
}
}
@@ -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<LookupElement, CompletionSorterImpl> myItemSorters = new ConcurrentHashMap<LookupElement, CompletionSorterImpl>(
ContainerUtil.<LookupElement>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);
@@ -292,7 +292,7 @@ public class LookupCellRenderer implements ListCellRenderer {
final String prefix = item instanceof EmptyLookupItem ? "" : myLookup.itemPattern(item);
if (prefix.length() > 0) {
Iterable<TextRange> ranges = new MinusculeMatcher("*" + prefix, NameUtil.MatchingCaseSensitivity.NONE).matchingFragments(name);
Iterable<TextRange> 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<TextRange> getMatchingFragments(String prefix, String name) {
return new MinusculeMatcher("*" + prefix, NameUtil.MatchingCaseSensitivity.NONE).matchingFragments(name);
}
private int setTypeTextLabel(LookupElement item,
final Color background,
Color foreground,
@@ -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;
}
}
@@ -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.