option to show parameter hints on method completion, ability to switch method overloads (Java only)

This commit is contained in:
Dmitry Batrak
2017-03-07 12:14:44 +03:00
parent 06b3784d6a
commit d73e849642
21 changed files with 708 additions and 201 deletions
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2009 JetBrains s.r.o.
* Copyright 2000-2017 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.
@@ -17,6 +17,10 @@ package com.intellij.codeInsight.completion;
import com.intellij.codeInsight.AutoPopupController;
import com.intellij.codeInsight.completion.util.MethodParenthesesHandler;
import com.intellij.codeInsight.daemon.impl.ParameterHintsPresentationManager;
import com.intellij.codeInsight.hint.ParameterInfoController;
import com.intellij.codeInsight.hint.ShowParameterInfoContext;
import com.intellij.codeInsight.hint.api.impls.MethodParameterInfoHandler;
import com.intellij.codeInsight.lookup.*;
import com.intellij.codeInsight.lookup.impl.JavaElementLookupRenderer;
import com.intellij.codeInsight.template.*;
@@ -26,10 +30,13 @@ import com.intellij.codeInsight.template.impl.TemplateManagerImpl;
import com.intellij.codeInsight.template.impl.TemplateState;
import com.intellij.featureStatistics.FeatureUsageTracker;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.editor.CaretModel;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.Inlay;
import com.intellij.openapi.editor.event.DocumentAdapter;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.ClassConditionKey;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Key;
@@ -44,6 +51,8 @@ import com.intellij.psi.util.TypeConversionUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
@@ -175,6 +184,7 @@ public class JavaMethodCallElement extends LookupItem<PsiMethod> implements Type
}
startArgumentLiveTemplate(context, method);
showParameterHints(context, method, methodCall);
}
static PsiCallExpression findCallAtOffset(InsertionContext context, int offset) {
@@ -270,6 +280,45 @@ public class JavaMethodCallElement extends LookupItem<PsiMethod> implements Type
return true;
}
public static void showParameterHints(InsertionContext context, PsiMethod method, PsiCallExpression methodCall) {
PsiParameterList parameterList = method.getParameterList();
int parametersCount = parameterList.getParametersCount();
if (methodCall == null ||
parametersCount == 0 ||
context.getCompletionChar() == Lookup.COMPLETE_STATEMENT_SELECT_CHAR ||
Registry.is("java.completion.argument.live.template") ||
!Registry.is("java.completion.argument.hints")) {
return;
}
Editor editor = context.getEditor();
CaretModel caretModel = editor.getCaretModel();
int offset = caretModel.getOffset();
caretModel.moveToOffset(offset - 1); // avoid caret impact on hints location
editor.getDocument().insertString(offset, StringUtil.repeat(", ", parametersCount - 1));
List<Inlay> addedHints = new ArrayList<>(parametersCount);
for (PsiParameter parameter : parameterList.getParameters()) {
String name = parameter.getName();
if (name != null) {
addedHints.add(ParameterHintsPresentationManager.getInstance().addHint(editor, offset, name + ":", false, true));
}
offset += 2;
}
int braceOffset = caretModel.getOffset();
caretModel.moveToLogicalPosition(editor.offsetToLogicalPosition(braceOffset + 1).leanForward(true));
Project project = context.getProject();
MethodParameterInfoHandler handler = new MethodParameterInfoHandler();
ShowParameterInfoContext infoContext = new ShowParameterInfoContext(editor, project, context.getFile(), braceOffset, braceOffset);
handler.findElementForParameterInfo(infoContext);
Disposer.register(new ParameterInfoController(project, editor, braceOffset, infoContext.getItemsToShow(), null, methodCall.getArgumentList(), handler, false, false), () -> {
for (Inlay inlay : addedHints) {
if (inlay != null) ParameterHintsPresentationManager.getInstance().unpin(inlay);
}
});
}
private static void setupNonFilledArgumentRemoving(final Editor editor, final TemplateState templateState) {
AtomicInteger maxEditedVariable = new AtomicInteger(-1);
editor.getDocument().addDocumentListener(new DocumentAdapter() {
@@ -0,0 +1,31 @@
/*
* Copyright 2000-2017 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.editorActions;
import com.intellij.openapi.actionSystem.ActionPromoter;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.util.containers.ContainerUtil;
import java.util.List;
public class JavaMethodOverloadSwitchActionPromoter implements ActionPromoter {
@Override
public List<AnAction> promote(List<AnAction> actions, DataContext context) {
return ContainerUtil.findAll(actions, a -> a instanceof JavaMethodOverloadSwitchUpAction ||
a instanceof JavaMethodOverloadSwitchDownAction);
}
}
@@ -0,0 +1,24 @@
/*
* Copyright 2000-2017 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.editorActions;
import com.intellij.openapi.editor.actionSystem.EditorAction;
public class JavaMethodOverloadSwitchDownAction extends EditorAction {
public JavaMethodOverloadSwitchDownAction() {
super(new JavaMethodOverloadSwitchHandler(false));
}
}
@@ -0,0 +1,149 @@
/*
* Copyright 2000-2017 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.editorActions;
import com.intellij.codeInsight.completion.CompletionMemory;
import com.intellij.codeInsight.daemon.impl.ParameterHintsPresentationManager;
import com.intellij.codeInsight.hint.ParameterInfoController;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.editor.Caret;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.Inlay;
import com.intellij.openapi.editor.actionSystem.EditorWriteActionHandler;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.psi.*;
import com.intellij.psi.infos.CandidateInfo;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
public class JavaMethodOverloadSwitchHandler extends EditorWriteActionHandler {
private static final Key<Map<String, String>> ENTERED_PARAMETERS = Key.create("entered.parameters");
private final boolean mySwitchUp;
public JavaMethodOverloadSwitchHandler(boolean up) {
mySwitchUp = up;
}
@Override
protected boolean isEnabledForCaret(@NotNull Editor editor, @NotNull Caret caret, DataContext dataContext) {
if (!Registry.is("java.completion.argument.hints") || !ParameterInfoController.existsForEditor(editor)) return false;
Project project = CommonDataKeys.PROJECT.getData(dataContext);
if (project == null) return false;
PsiDocumentManager.getInstance(project).commitDocument(editor.getDocument());
PsiElement exprList = getExpressionList(editor, caret.getOffset(), project);
if (exprList == null) return false;
int lbraceOffset = exprList.getTextRange().getStartOffset();
return ParameterInfoController.findControllerAtOffset(editor, lbraceOffset) != null;
}
@Nullable
private static PsiElement getExpressionList(@NotNull Editor editor, int offset, @NotNull Project project) {
PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
return file != null ? ParameterInfoController.findArgumentList(file, offset, -1) : null;
}
@Override
public void executeWriteAction(Editor editor, @Nullable Caret caret, DataContext dataContext) {
if (caret == null) caret = editor.getCaretModel().getPrimaryCaret();
Project project = CommonDataKeys.PROJECT.getData(dataContext);
if (project == null) return;
PsiDocumentManager.getInstance(project).commitAllDocuments();
PsiElement exprList = getExpressionList(editor, caret.getOffset(), project);
if (!(exprList instanceof PsiExpressionList)) return;
int lbraceOffset = exprList.getTextRange().getStartOffset();
ParameterInfoController controller = ParameterInfoController.findControllerAtOffset(editor, lbraceOffset);
if (controller == null) return;
Object[] objects = controller.getObjects();
Object highlighted = controller.getHighlighted();
if (objects == null || objects.length <= 1 || highlighted == null) return;
int currentIndex = ContainerUtil.indexOf(Arrays.asList(objects), highlighted);
if (currentIndex < 0) return;
PsiMethod currentMethod = (PsiMethod)((CandidateInfo)objects[currentIndex]).getElement();
Map<String, String> enteredParameters = exprList.getUserData(ENTERED_PARAMETERS);
if (enteredParameters == null) {
exprList.putUserData(ENTERED_PARAMETERS, enteredParameters = new HashMap<>());
}
PsiExpression[] enteredExpressions = ((PsiExpressionList)exprList).getExpressions();
assert enteredExpressions.length == 0 || enteredExpressions.length == currentMethod.getParameterList().getParametersCount();
for (int i = 0; i < enteredExpressions.length; i++) {
PsiExpression expression = enteredExpressions[i];
String value = expression.getText().trim();
if (!value.isEmpty()) {
String key = getParameterKey(currentMethod, i);
enteredParameters.put(key, value);
}
}
PsiMethod targetMethod = (PsiMethod)((CandidateInfo)objects[(currentIndex + (mySwitchUp ? -1 : 1) + objects.length) % objects.length]).getElement();
PsiParameterList parameterList = targetMethod.getParameterList();
int parametersCount = parameterList.getParametersCount();
caret.moveToOffset(lbraceOffset); // avoid caret impact on hints location
int offset = lbraceOffset + 1;
int endOffset = exprList.getTextRange().getEndOffset() - 1;
List<Inlay> oldInlays = editor.getInlayModel().getInlineElementsInRange(offset, endOffset);
for (Inlay inlay : oldInlays) {
Disposer.dispose(inlay);
}
editor.getDocument().deleteString(offset, endOffset);
int targetCaretPosition = -1;
List<Inlay> addedHints = new ArrayList<>(parametersCount);
for (int i = 0; i < parametersCount; i++) {
String key = getParameterKey(targetMethod, i);
String value = enteredParameters.getOrDefault(key, "");
if (value.isEmpty() && targetCaretPosition == -1) targetCaretPosition = offset;
if (i < parametersCount - 1) value += ", ";
editor.getDocument().insertString(offset, value);
String name = parameterList.getParameters()[i].getName();
if (name != null) {
addedHints.add(ParameterHintsPresentationManager.getInstance().addHint(editor, offset, name + ":", false, true));
}
offset += value.length();
}
if (targetCaretPosition == -1) targetCaretPosition = offset;
caret.moveToLogicalPosition(editor.offsetToLogicalPosition(targetCaretPosition).leanForward(true));
Disposer.register(controller, () -> {
for (Inlay hint : addedHints) {
if (hint != null) ParameterHintsPresentationManager.getInstance().unpin(hint);
}
});
PsiDocumentManager.getInstance(project).commitDocument(editor.getDocument());
CompletionMemory.registerChosenMethod(targetMethod, (PsiCall)exprList.getParent());
controller.updateComponent(); // update popup immediately (otherwise, it will be updated only after delay)
}
private static String getParameterKey(PsiMethod method, int parameterIndex) {
PsiParameter parameter = method.getParameterList().getParameters()[parameterIndex];
return parameter.getName() + ":" + parameter.getType().getCanonicalText();
}
}
@@ -0,0 +1,24 @@
/*
* Copyright 2000-2017 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.editorActions;
import com.intellij.openapi.editor.actionSystem.EditorAction;
public class JavaMethodOverloadSwitchUpAction extends EditorAction {
public JavaMethodOverloadSwitchUpAction() {
super(new JavaMethodOverloadSwitchHandler(true));
}
}
@@ -0,0 +1,130 @@
/*
* Copyright 2000-2017 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.daemon.impl.ParameterHintsPresentationManager;
import com.intellij.codeInsight.hint.ParameterInfoController;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementPresentation;
import com.intellij.ide.highlighter.JavaFileType;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.util.registry.RegistryValue;
import com.intellij.util.ui.UIUtil;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
public class CompletionHintsTest extends LightFixtureCompletionTestCase {
private RegistryValue myRegistryValue = Registry.get("java.completion.argument.hints");
private boolean myStoredRegistryValue;
@Override
protected void setUp() throws Exception {
super.setUp();
myStoredRegistryValue = myRegistryValue.asBoolean();
myRegistryValue.setValue(true);
}
@Override
protected void tearDown() throws Exception {
try {
myRegistryValue.setValue(myStoredRegistryValue);
}
finally {
super.tearDown();
}
}
public void testBasicScenario() throws Exception {
// check hints appearance on completion
myFixture.configureByText(JavaFileType.INSTANCE, "class C { void m() { System.setPro<caret> } }");
complete("setProperty");
myFixture.checkResultWithInlays("class C { void m() { System.setProperty(<hint text=\"key:\"/>, <hint text=\"value:\"/>) } }");
// check that hints don't disappear after daemon highlighting passes
myFixture.doHighlighting();
myFixture.checkResultWithInlays("class C { void m() { System.setProperty(<hint text=\"key:\"/>, <hint text=\"value:\"/>) } }");
// test Tab/Shift+Tab navigation
myFixture.checkResult("class C { void m() { System.setProperty(<caret>, ) } }");
assertTrue(myFixture.getEditor().getCaretModel().getLogicalPosition().leansForward);
myFixture.performEditorAction("NextParameter");
myFixture.checkResult("class C { void m() { System.setProperty(, <caret>) } }");
assertTrue(myFixture.getEditor().getCaretModel().getLogicalPosition().leansForward);
myFixture.performEditorAction("PrevParameter");
myFixture.checkResult("class C { void m() { System.setProperty(<caret>, ) } }");
assertTrue(myFixture.getEditor().getCaretModel().getLogicalPosition().leansForward);
// test hints remain shown while entering parameter values
myFixture.type("\"a");
myFixture.performEditorAction("NextParameter");
myFixture.type("\"b");
myFixture.doHighlighting();
myFixture.checkResultWithInlays("class C { void m() { System.setProperty(<hint text=\"key:\"/>\"a\", <hint text=\"value:\"/>\"b\") } }");
// test hints disappearance when caret moves out of parameter list
myFixture.performEditorAction("EditorRight");
myFixture.performEditorAction("EditorRight");
ParameterInfoController.waitForDelayedActions(getEditor(), 10, TimeUnit.SECONDS);
myFixture.doHighlighting();
waitTillAnimationCompletes();
myFixture.checkResultWithInlays("class C { void m() { System.setProperty(\"a\", \"b\") } }");
}
public void testSwitchingOverloads() {
myFixture.configureByText(JavaFileType.INSTANCE, "class C { void m() { Character.to<caret> } }");
complete("toChars(int codePoint)");
myFixture.checkResultWithInlays("class C { void m() { Character.toChars(<hint text=\"codePoint:\"/>) } }");
showParameterInfo();
myFixture.performEditorAction("MethodOverloadSwitchDown");
myFixture.checkResultWithInlays("class C { void m() { Character.toChars(<hint text=\"codePoint:\"/>, <hint text=\"dst:\"/>, <hint text=\"dstIndex:\"/>) } }");
}
public void testSwitchingOverloadsWithParameterFilled() {
myFixture.configureByText(JavaFileType.INSTANCE, "class C { void m() { Character.to<caret> } }");
complete("toChars(int codePoint)");
type("123");
myFixture.checkResultWithInlays("class C { void m() { Character.toChars(<hint text=\"codePoint:\"/>123) } }");
showParameterInfo();
myFixture.performEditorAction("MethodOverloadSwitchDown");
myFixture.checkResultWithInlays("class C { void m() { Character.toChars(<hint text=\"codePoint:\"/>123, <hint text=\"dst:\"/>, <hint text=\"dstIndex:\"/>) } }");
myFixture.checkResult("class C { void m() { Character.toChars(123, <caret>, ) } }");
}
private void showParameterInfo() {
myFixture.performEditorAction("ParameterInfo");
UIUtil.dispatchAllInvocationEvents();
}
private void complete(String partOfItemText) {
LookupElement[] elements = myFixture.completeBasic();
LookupElement element = Stream.of(elements).filter(e -> {
LookupElementPresentation p = new LookupElementPresentation();
e.renderElement(p);
return (p.getItemText() + p.getTailText()).contains(partOfItemText);
}).findAny().get();
selectItem(element);
}
private void waitTillAnimationCompletes() {
long deadline = System.currentTimeMillis() + 60_000;
while (ParameterHintsPresentationManager.getInstance().isAnimationInProgress(getEditor())) {
if (System.currentTimeMillis() > deadline) fail("Too long waiting for animation to finish");
UIUtil.dispatchAllInvocationEvents();
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
* Copyright 2000-2017 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.
@@ -53,7 +53,7 @@ public class InlineElementData extends HighlightData {
@Override
public void addHighlToView(Editor view, EditorColorsScheme scheme, Map<TextAttributesKey, String> displayText) {
int offset = getStartOffset();
ParameterHintsPresentationManager.getInstance().addHint(view, offset, myText, false);
ParameterHintsPresentationManager.getInstance().addHint(view, offset, myText, false, false);
List<Inlay> inlays = view.getInlayModel().getInlineElementsInRange(offset, offset);
for (Inlay inlay : inlays) {
EditorCustomElementRenderer renderer = inlay.getRenderer();
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
* Copyright 2000-2017 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.
@@ -33,6 +33,7 @@ import com.intellij.util.Alarm;
import com.intellij.util.ui.GraphicsUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import javax.swing.*;
import java.awt.*;
@@ -42,6 +43,7 @@ import java.util.Iterator;
import java.util.Set;
public class ParameterHintsPresentationManager implements Disposable {
private static final Key<Boolean> PINNED = Key.create("parameter.hint.pinned");
private static final Key<MyFontMetrics> HINT_FONT_METRICS = Key.create("ParameterHintFontMetrics");
private static final Key<AnimationStep> ANIMATION_STEP = Key.create("ParameterHintAnimationStep");
@@ -62,17 +64,27 @@ public class ParameterHintsPresentationManager implements Disposable {
return inlay.getRenderer() instanceof MyRenderer;
}
public boolean isPinned(@NotNull Inlay inlay) {
return Boolean.TRUE.equals(inlay.getUserData(PINNED));
}
public void unpin(@NotNull Inlay inlay) {
inlay.putUserData(PINNED, null);
}
public String getHintText(@NotNull Inlay inlay) {
EditorCustomElementRenderer renderer = inlay.getRenderer();
return renderer instanceof MyRenderer ? ((MyRenderer)renderer).getText() : null;
}
public void addHint(@NotNull Editor editor, int offset, @NotNull String hintText, boolean useAnimation) {
public Inlay addHint(@NotNull Editor editor, int offset, @NotNull String hintText, boolean useAnimation, boolean pinned) {
MyRenderer renderer = new MyRenderer(editor, hintText, useAnimation);
Inlay inlay = editor.getInlayModel().addInlineElement(offset, renderer);
if (useAnimation && inlay != null) {
scheduleRendererUpdate(editor, inlay);
if (inlay != null) {
if (pinned) inlay.putUserData(PINNED, Boolean.TRUE);
if (useAnimation) scheduleRendererUpdate(editor, inlay);
}
return inlay;
}
public void deleteHint(@NotNull Editor editor, @NotNull Inlay hint) {
@@ -109,6 +121,12 @@ public class ParameterHintsPresentationManager implements Disposable {
myAlarm.addRequest(step, ANIMATION_STEP_MS, ModalityState.any());
}
@TestOnly
public boolean isAnimationInProgress(@NotNull Editor editor) {
ApplicationManager.getApplication().assertIsDispatchThread();
return editor.getUserData(ANIMATION_STEP) != null;
}
private static Font getFont(@NotNull Editor editor) {
return getFontMetrics(editor).getFont();
}
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
* Copyright 2000-2017 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.
@@ -140,10 +140,6 @@ public class ParameterInfoComponent extends JPanel {
return myHighlighted;
}
public void setRequestFocus(boolean requestFocus) {
myRequestFocus = requestFocus;
}
public boolean isRequestFocus() {
return myRequestFocus;
}
@@ -16,18 +16,19 @@
package com.intellij.codeInsight.hint;
import com.intellij.codeInsight.daemon.impl.ParameterHintsPresentationManager;
import com.intellij.codeInsight.lookup.Lookup;
import com.intellij.codeInsight.lookup.LookupManager;
import com.intellij.ide.IdeTooltip;
import com.intellij.injected.editor.EditorWindow;
import com.intellij.lang.parameterInfo.ParameterInfoHandler;
import com.intellij.lang.parameterInfo.ParameterInfoHandlerWithTabActionSupport;
import com.intellij.lang.parameterInfo.ParameterInfoUtils;
import com.intellij.lang.parameterInfo.UpdateParameterInfoContext;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.RangeMarker;
import com.intellij.openapi.editor.ScrollType;
import com.intellij.openapi.editor.*;
import com.intellij.openapi.editor.event.*;
import com.intellij.openapi.editor.impl.EditorImpl;
import com.intellij.openapi.project.DumbService;
@@ -36,19 +37,21 @@ import com.intellij.openapi.ui.popup.Balloon.Position;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.TokenType;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiUtilBase;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.ui.HintHint;
import com.intellij.ui.LightweightHint;
import com.intellij.util.Alarm;
import com.intellij.util.containers.JBIterable;
import com.intellij.util.messages.MessageBusConnection;
import com.intellij.util.text.CharArrayUtil;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import javax.swing.*;
import java.awt.*;
@@ -56,6 +59,8 @@ import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public class ParameterInfoController implements Disposable {
private final Project myProject;
@@ -64,15 +69,16 @@ public class ParameterInfoController implements Disposable {
private final RangeMarker myLbraceMarker;
private final LightweightHint myHint;
private final ParameterInfoComponent myComponent;
private final boolean myKeepOnHintHidden;
private final CaretListener myEditorCaretListener;
@NotNull private final ParameterInfoHandler<Object, Object> myHandler;
private final ShowParameterInfoHandler.BestLocationPointProvider myProvider;
private final MyBestLocationPointProvider myProvider;
private final Alarm myAlarm = new Alarm();
private static final int DELAY = 200;
private boolean myDisposed = false;
private boolean myDisposed;
/**
* Keeps Vector of ParameterInfoController's in Editor
@@ -85,7 +91,7 @@ public class ParameterInfoController implements Disposable {
ParameterInfoController controller = allControllers.get(i);
if (controller.myLbraceMarker.getStartOffset() == offset) {
if (controller.myHint.isVisible()) return controller;
if (controller.myKeepOnHintHidden || controller.myHint.isVisible()) return controller;
Disposer.dispose(controller);
--i;
}
@@ -103,27 +109,40 @@ public class ParameterInfoController implements Disposable {
return array;
}
public static boolean isShownForEditor(@NotNull Editor editor) {
public static boolean existsForEditor(@NotNull Editor editor) {
return !getAllControllers(editor).isEmpty();
}
public static boolean isAlreadyShown(Editor editor, int lbraceOffset) {
return findControllerAtOffset(editor, lbraceOffset) != null;
ParameterInfoController controller = findControllerAtOffset(editor, lbraceOffset);
return controller != null && controller.myHint.isVisible();
}
public ParameterInfoController(@NotNull Project project,
@NotNull Editor editor,
int lbraceOffset,
@NotNull LightweightHint hint,
Object[] descriptors,
Object highlighted,
PsiElement parameterOwner,
@NotNull ParameterInfoHandler handler,
@NotNull ShowParameterInfoHandler.BestLocationPointProvider provider) {
boolean showHint,
boolean requestFocus) {
myProject = project;
myEditor = editor;
myHandler = handler;
myProvider = provider;
myProvider = new MyBestLocationPointProvider(editor);
myLbraceMarker = editor.getDocument().createRangeMarker(lbraceOffset, lbraceOffset);
myHint = hint;
myComponent = (ParameterInfoComponent)myHint.getComponent();
myComponent = new ParameterInfoComponent(descriptors, editor, handler, requestFocus);
myHint = new LightweightHint(myComponent);
myKeepOnHintHidden = !showHint;
myHint.setSelectingHint(true);
myComponent.setParameterOwner(parameterOwner);
myComponent.setHighlightedParameter(highlighted);
myComponent.update(); // to have correct preferred size
if (showHint) {
showHint(requestFocus);
}
List<ParameterInfoController> allControllers = getAllControllers(myEditor);
allControllers.add(this);
@@ -175,12 +194,26 @@ public class ParameterInfoController implements Disposable {
public void dispose(){
if (myDisposed) return;
myDisposed = true;
myHint.hide();
List<ParameterInfoController> allControllers = getAllControllers(myEditor);
allControllers.remove(this);
myEditor.getCaretModel().removeCaretListener(myEditorCaretListener);
}
public void showHint(boolean requestFocus) {
Pair<Point, Short> pos = myProvider.getBestPointPosition(myHint, myComponent.getParameterOwner(), myLbraceMarker.getStartOffset(), true, HintManager.UNDER);
HintHint hintHint = HintManagerImpl.createHintHint(myEditor, pos.getFirst(), myHint, pos.getSecond());
hintHint.setExplicitClose(true);
hintHint.setRequestFocus(requestFocus);
Editor editorToShow = myEditor instanceof EditorWindow ? ((EditorWindow)myEditor).getDelegate() : myEditor;
// is case of injection we need to calculate position for EditorWindow
// also we need to show the hint in the main editor because of intention bulb
HintManagerImpl.getInstanceImpl().showEditorHint(myHint, editorToShow, pos.getFirst(), HintManager.HIDE_BY_ESCAPE | HintManager.UPDATE_BY_SCROLLING, 0, false, hintHint);
updateComponent();
}
private void adjustPositionForLookup(@NotNull Lookup lookup) {
if (!myHint.isVisible() || myEditor.isDisposed()) {
Disposer.dispose(this);
@@ -221,8 +254,34 @@ public class ParameterInfoController implements Disposable {
myAlarm.addRequest(request, DELAY, ModalityState.stateForComponent(myEditor.getComponent()));
}
private void updateComponent(){
if (!myHint.isVisible()){
public void updateComponent(){
if (myKeepOnHintHidden) {
boolean removeHints = true;
PsiElement owner = myComponent.getParameterOwner();
if (owner != null && owner.isValid()) {
int caretOffset = myEditor.getCaretModel().getOffset();
TextRange ownerTextRange = owner.getTextRange();
if (ownerTextRange != null) {
if (caretOffset > ownerTextRange.getStartOffset() && caretOffset < ownerTextRange.getEndOffset()) {
removeHints = false;
}
else {
for (PsiElement element : owner.getChildren()) {
if (element instanceof PsiErrorElement) {
removeHints = false;
break;
}
}
}
}
}
if (removeHints) {
Disposer.dispose(this);
return;
}
}
if (!myHint.isVisible() && !myKeepOnHintHidden && !ApplicationManager.getApplication().isUnitTestMode()) {
Disposer.dispose(this);
return;
}
@@ -290,12 +349,26 @@ public class ParameterInfoController implements Disposable {
PsiElement argsList = findArgumentList(file, offset, -1);
if (argsList == null) return;
myEditor.getCaretModel().moveToOffset(offset);
offset = adjustOffsetToInlay(offset);
myEditor.getCaretModel().moveToLogicalPosition(myEditor.offsetToLogicalPosition(offset).leanForward(true));
myEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
myEditor.getSelectionModel().removeSelection();
myHandler.updateParameterInfo(argsList, new MyUpdateParameterInfoContext(offset, file));
}
private int adjustOffsetToInlay(int offset) {
CharSequence text = myEditor.getDocument().getImmutableCharSequence();
String whitespaceChars = " \t";
int whitespaceStart = CharArrayUtil.shiftBackward(text, offset, whitespaceChars) + 1;
int whitespaceEnd = CharArrayUtil.shiftForward(text, offset, whitespaceChars);
List<Inlay> inlays = myEditor.getInlayModel().getInlineElementsInRange(whitespaceStart, whitespaceEnd);
for (Inlay inlay : inlays) {
if (ParameterHintsPresentationManager.getInstance().isParameterHint(inlay)) return inlay.getOffset();
}
return offset;
}
private int getPrevOrNextParameterOffset(boolean isNext) {
if (!(myHandler instanceof ParameterInfoHandlerWithTabActionSupport)) return -1;
ParameterInfoHandlerWithTabActionSupport handler = (ParameterInfoHandlerWithTabActionSupport)myHandler;
@@ -338,7 +411,93 @@ public class ParameterInfoController implements Disposable {
return null;
}
private class MyUpdateParameterInfoContext implements UpdateParameterInfoContext {
public Object[] getObjects() {
return myComponent.getObjects();
}
public Object getHighlighted() {
return myComponent.getHighlighted();
}
@TestOnly
public static void waitForDelayedActions(@NotNull Editor editor, long timeout, @NotNull TimeUnit unit) throws TimeoutException {
long deadline = System.currentTimeMillis() + unit.toMillis(timeout);
while (System.currentTimeMillis() < deadline) {
List<ParameterInfoController> controllers = getAllControllers(editor);
boolean hasPendingRequests = false;
for (ParameterInfoController controller : controllers) {
if (!controller.myAlarm.isEmpty()) {
hasPendingRequests = true;
break;
}
}
if (hasPendingRequests) UIUtil.dispatchAllInvocationEvents();
else return;
}
throw new TimeoutException();
}
/**
* @return Point in layered pane coordinate system
*/
static Pair<Point, Short> chooseBestHintPosition(Project project,
Editor editor,
LogicalPosition pos,
LightweightHint hint,
boolean awtTooltip, short preferredPosition) {
if (ApplicationManager.getApplication().isUnitTestMode()) return Pair.pair(new Point(), HintManager.DEFAULT);
HintManagerImpl hintManager = HintManagerImpl.getInstanceImpl();
Dimension hintSize = hint.getComponent().getPreferredSize();
JComponent editorComponent = editor.getComponent();
JLayeredPane layeredPane = editorComponent.getRootPane().getLayeredPane();
Point p1;
Point p2;
boolean isLookupShown = LookupManager.getInstance(project).getActiveLookup() != null;
if (isLookupShown) {
p1 = hintManager.getHintPosition(hint, editor, HintManager.UNDER);
p2 = hintManager.getHintPosition(hint, editor, HintManager.ABOVE);
}
else {
p1 = HintManagerImpl.getHintPosition(hint, editor, pos, HintManager.UNDER);
p2 = HintManagerImpl.getHintPosition(hint, editor, pos, HintManager.ABOVE);
}
if (!awtTooltip) {
p1.x = Math.min(p1.x, layeredPane.getWidth() - hintSize.width);
p1.x = Math.max(p1.x, 0);
p2.x = Math.min(p2.x, layeredPane.getWidth() - hintSize.width);
p2.x = Math.max(p2.x, 0);
}
boolean p1Ok = p1.y + hintSize.height < layeredPane.getHeight();
boolean p2Ok = p2.y >= 0;
if (isLookupShown) {
if (p1Ok) return new Pair<>(p1, HintManager.UNDER);
if (p2Ok) return new Pair<>(p2, HintManager.ABOVE);
}
else {
if (preferredPosition != HintManager.DEFAULT) {
if (preferredPosition == HintManager.ABOVE) {
if (p2Ok) return new Pair<>(p2, HintManager.ABOVE);
} else if (preferredPosition == HintManager.UNDER) {
if (p1Ok) return new Pair<>(p1, HintManager.UNDER);
}
}
if (p1Ok) return new Pair<>(p1, HintManager.UNDER);
if (p2Ok) return new Pair<>(p2, HintManager.ABOVE);
}
int underSpace = layeredPane.getHeight() - p1.y;
int aboveSpace = p2.y;
return aboveSpace > underSpace ? new Pair<>(new Point(p2.x, 0), HintManager.UNDER) : new Pair<>(p1,
HintManager.ABOVE);
}
public class MyUpdateParameterInfoContext implements UpdateParameterInfoContext {
private final int myOffset;
private final PsiFile myFile;
@@ -415,4 +574,46 @@ public class ParameterInfoController implements Disposable {
}
}
private static class MyBestLocationPointProvider {
private final Editor myEditor;
private int previousOffset = -1;
private Point previousBestPoint;
private Short previousBestPosition;
public MyBestLocationPointProvider(final Editor editor) {
myEditor = editor;
}
@NotNull
public Pair<Point, Short> getBestPointPosition(LightweightHint hint,
final PsiElement list,
int offset,
final boolean awtTooltip,
short preferredPosition) {
if (list != null) {
TextRange range = list.getTextRange();
if (!range.contains(offset)) {
offset = range.getStartOffset() + 1;
}
}
if (previousOffset == offset) return Pair.create(previousBestPoint, previousBestPosition);
final boolean isMultiline = list != null && StringUtil.containsAnyChar(list.getText(), "\n\r");
final LogicalPosition pos = myEditor.offsetToLogicalPosition(offset).leanForward(true);
Pair<Point, Short> position;
if (!isMultiline) {
position = chooseBestHintPosition(myEditor.getProject(), myEditor, pos, hint, awtTooltip, preferredPosition);
}
else {
Point p = HintManagerImpl.getHintPosition(hint, myEditor, pos, HintManager.ABOVE);
position = new Pair<>(p, HintManager.ABOVE);
}
previousBestPoint = position.getFirst();
previousBestPosition = position.getSecond();
previousOffset = offset;
return position;
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2009 JetBrains s.r.o.
* Copyright 2000-2017 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.
@@ -40,7 +40,7 @@ public class PrevNextParameterHandler extends EditorActionHandler {
@Override
protected boolean isEnabledForCaret(@NotNull Editor editor, @NotNull Caret caret, DataContext dataContext) {
if (!ParameterInfoController.isShownForEditor(editor)) return false;
if (!ParameterInfoController.existsForEditor(editor)) return false;
Project project = CommonDataKeys.PROJECT.getData(dataContext);
if (project == null) return false;
@@ -49,7 +49,7 @@ public class PrevNextParameterHandler extends EditorActionHandler {
if (exprList == null) return false;
int lbraceOffset = exprList.getTextRange().getStartOffset();
return ParameterInfoController.isAlreadyShown(editor, lbraceOffset) &&
return ParameterInfoController.findControllerAtOffset(editor, lbraceOffset) != null &&
ParameterInfoController.hasPrevOrNextParameter(editor, lbraceOffset, myIsNextParameterHandler);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
* Copyright 2000-2017 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.
@@ -15,29 +15,18 @@
*/
package com.intellij.codeInsight.hint;
import com.intellij.codeInsight.lookup.LookupManager;
import com.intellij.injected.editor.EditorWindow;
import com.intellij.lang.parameterInfo.CreateParameterInfoContext;
import com.intellij.lang.parameterInfo.ParameterInfoHandler;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.LogicalPosition;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.ui.HintHint;
import com.intellij.ui.LightweightHint;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
/**
* @author peter
*/
@@ -131,20 +120,6 @@ public class ShowParameterInfoContext implements CreateParameterInfoContext {
if (ParameterInfoController.isAlreadyShown(editor, elementStart)) return;
if (editor.isDisposed() || !editor.getComponent().isVisible()) return;
final ParameterInfoComponent component = new ParameterInfoComponent(descriptors, editor,handler,requestFocus);
component.setParameterOwner(element);
component.setRequestFocus(requestFocus);
if (highlighted != null) {
component.setHighlightedParameter(highlighted);
}
component.update(); // to have correct preferred size
final LightweightHint hint = new LightweightHint(component);
hint.setSelectingHint(true);
final HintManagerImpl hintManager = HintManagerImpl.getInstanceImpl();
final ShowParameterInfoHandler.BestLocationPointProvider provider = new MyBestLocationPointProvider(editor);
final Pair<Point, Short> pos = provider.getBestPointPosition(hint, element, elementStart, true, HintManager.UNDER);
PsiDocumentManager.getInstance(project).performLaterWhenAllCommitted(() -> {
if (editor.isDisposed() || DumbService.isDumb(project)) return;
@@ -152,15 +127,13 @@ public class ShowParameterInfoContext implements CreateParameterInfoContext {
final Document document = editor.getDocument();
if (document.getTextLength() < elementStart) return;
HintHint hintHint = HintManagerImpl.createHintHint(editor, pos.getFirst(), hint, pos.getSecond());
hintHint.setExplicitClose(true);
hintHint.setRequestFocus(requestFocus);
Editor editorToShow = editor instanceof EditorWindow ? ((EditorWindow)editor).getDelegate() : editor;
// is case of injection we need to calculate position for EditorWindow
// also we need to show the hint in the main editor because of intention bulb
hintManager.showEditorHint(hint, editorToShow, pos.getFirst(), HintManager.HIDE_BY_ESCAPE | HintManager.UPDATE_BY_SCROLLING, 0, false, hintHint);
new ParameterInfoController(project, editor, elementStart, hint, handler, provider);
ParameterInfoController controller = ParameterInfoController.findControllerAtOffset(editor, elementStart);
if (controller == null) {
new ParameterInfoController(project, editor, elementStart, descriptors, highlighted, element, handler, true, requestFocus);
}
else {
controller.showHint(requestFocus);
}
});
}
@@ -175,66 +148,6 @@ public class ShowParameterInfoContext implements CreateParameterInfoContext {
showParameterHint(list, editor, candidates, project, candidates.length > 1 ? highlighted : null, offset, handler, requestFocus);
}
/**
* @return Point in layered pane coordinate system
*/
static Pair<Point, Short> chooseBestHintPosition(Project project,
Editor editor,
int line,
int col,
LightweightHint hint,
boolean awtTooltip, short preferredPosition) {
HintManagerImpl hintManager = HintManagerImpl.getInstanceImpl();
Dimension hintSize = hint.getComponent().getPreferredSize();
JComponent editorComponent = editor.getComponent();
JLayeredPane layeredPane = editorComponent.getRootPane().getLayeredPane();
Point p1;
Point p2;
boolean isLookupShown = LookupManager.getInstance(project).getActiveLookup() != null;
if (isLookupShown) {
p1 = hintManager.getHintPosition(hint, editor, HintManager.UNDER);
p2 = hintManager.getHintPosition(hint, editor, HintManager.ABOVE);
}
else {
LogicalPosition pos = new LogicalPosition(line, col);
p1 = HintManagerImpl.getHintPosition(hint, editor, pos, HintManager.UNDER);
p2 = HintManagerImpl.getHintPosition(hint, editor, pos, HintManager.ABOVE);
}
if (!awtTooltip) {
p1.x = Math.min(p1.x, layeredPane.getWidth() - hintSize.width);
p1.x = Math.max(p1.x, 0);
p2.x = Math.min(p2.x, layeredPane.getWidth() - hintSize.width);
p2.x = Math.max(p2.x, 0);
}
boolean p1Ok = p1.y + hintSize.height < layeredPane.getHeight();
boolean p2Ok = p2.y >= 0;
if (isLookupShown) {
if (p1Ok) return new Pair<>(p1, HintManager.UNDER);
if (p2Ok) return new Pair<>(p2, HintManager.ABOVE);
}
else {
if (preferredPosition != HintManager.DEFAULT) {
if (preferredPosition == HintManager.ABOVE) {
if (p2Ok) return new Pair<>(p2, HintManager.ABOVE);
} else if (preferredPosition == HintManager.UNDER) {
if (p1Ok) return new Pair<>(p1, HintManager.UNDER);
}
}
if (p1Ok) return new Pair<>(p1, HintManager.UNDER);
if (p2Ok) return new Pair<>(p2, HintManager.ABOVE);
}
int underSpace = layeredPane.getHeight() - p1.y;
int aboveSpace = p2.y;
return aboveSpace > underSpace ? new Pair<>(new Point(p2.x, 0), HintManager.UNDER) : new Pair<>(p1,
HintManager.ABOVE);
}
public void setRequestFocus(boolean requestFocus) {
myRequestFocus = requestFocus;
}
@@ -242,47 +155,4 @@ public class ShowParameterInfoContext implements CreateParameterInfoContext {
public boolean isRequestFocus() {
return myRequestFocus;
}
static class MyBestLocationPointProvider implements ShowParameterInfoHandler.BestLocationPointProvider {
private final Editor myEditor;
private int previousOffset = -1;
private Point previousBestPoint;
private Short previousBestPosition;
public MyBestLocationPointProvider(final Editor editor) {
myEditor = editor;
}
@Override
@NotNull
public Pair<Point, Short> getBestPointPosition(LightweightHint hint,
final PsiElement list,
int offset,
final boolean awtTooltip,
short preferredPosition) {
if (list != null) {
TextRange range = list.getTextRange();
if (!range.contains(offset)) {
offset = range.getStartOffset() + 1;
}
}
if (previousOffset == offset) return Pair.create(previousBestPoint, previousBestPosition);
final boolean isMultiline = list != null && StringUtil.containsAnyChar(list.getText(), "\n\r");
final LogicalPosition pos = myEditor.offsetToLogicalPosition(offset);
Pair<Point, Short> position;
if (!isMultiline) {
position = chooseBestHintPosition(myEditor.getProject(), myEditor, pos.line, pos.column, hint, awtTooltip, preferredPosition);
}
else {
Point p = HintManagerImpl.getHintPosition(hint, myEditor, pos, HintManager.ABOVE);
position = new Pair<>(p, HintManager.ABOVE);
}
previousBestPoint = position.getFirst();
previousBestPosition = position.getSecond();
previousOffset = offset;
return position;
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
* Copyright 2000-2017 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.
@@ -142,7 +142,7 @@ public class ShowParameterInfoHandler implements CodeInsightActionHandler {
final LightweightHint hint = new LightweightHint(component);
hint.setSelectingHint(true);
final HintManagerImpl hintManager = HintManagerImpl.getInstanceImpl();
final Pair<Point, Short> pos = ShowParameterInfoContext.chooseBestHintPosition(project, editor, -1, -1, hint, true, HintManager.DEFAULT);
final Pair<Point, Short> pos = ParameterInfoController.chooseBestHintPosition(project, editor, null, hint, true, HintManager.DEFAULT);
ApplicationManager.getApplication().invokeLater(() -> {
if (!editor.getComponent().isShowing()) return;
hintManager.showEditorHint(hint, editor, pos.getFirst(),
@@ -160,15 +160,5 @@ public class ShowParameterInfoHandler implements CodeInsightActionHandler {
if (handlers.isEmpty()) return null;
return handlers.toArray(new ParameterInfoHandler[handlers.size()]);
}
interface BestLocationPointProvider {
@NotNull
Pair<Point, Short> getBestPointPosition(LightweightHint hint,
final PsiElement list,
int offset,
final boolean awtTooltip,
short preferredPosition);
}
}
@@ -146,7 +146,7 @@ public class ParameterHintsPassFactory extends AbstractProjectComponent implemen
String newText = myAnnotations.remove(offset);
String oldText = presentationManager.getHintText(inlay);
if (delayRemoval(inlay, caretMap)) continue;
if (delayRemoval(inlay, caretMap) || presentationManager.isPinned(inlay)) continue;
if (!Objects.equals(newText, oldText)) {
if (newText == null) {
removedHints.add(oldText);
@@ -160,7 +160,7 @@ public class ParameterHintsPassFactory extends AbstractProjectComponent implemen
for (Map.Entry<Integer, String> e : myAnnotations.entrySet()) {
int offset = e.getKey();
String text = e.getValue();
presentationManager.addHint(myEditor, offset, text, !firstTime && !removedHints.contains(text));
presentationManager.addHint(myEditor, offset, text, !firstTime && !removedHints.contains(text), false);
}
keeper.restoreOriginalLocation();
myEditor.putUserData(REPEATED_PASS, Boolean.TRUE);
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
* Copyright 2000-2017 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.
@@ -654,23 +654,19 @@ public class HintManagerImpl extends HintManager implements Disposable {
@PositionFlags short constraint,
boolean showByBalloon) {
Dimension hintSize = hint.getComponent().getPreferredSize();
int line1 = pos1.line;
int col1 = pos1.column;
int line2 = pos2.line;
int col2 = pos2.column;
Point location;
JComponent externalComponent = getExternalComponent(editor);
JComponent internalComponent = editor.getContentComponent();
if (constraint == RIGHT_UNDER) {
Point p = editor.logicalPositionToXY(new LogicalPosition(line2, col2));
Point p = editor.logicalPositionToXY(pos2);
if (!showByBalloon) {
p.y += editor.getLineHeight();
}
location = SwingUtilities.convertPoint(internalComponent, p, externalComponent);
}
else {
Point p = editor.logicalPositionToXY(new LogicalPosition(line1, col1));
Point p = editor.logicalPositionToXY(pos1);
if (constraint == UNDER) {
p.y += editor.getLineHeight();
}
@@ -80,6 +80,9 @@
<action id="EditorLookupUp"><keyboard-shortcut first-keystroke="control UP"/></action>
<action id="EditorLookupDown"><keyboard-shortcut first-keystroke="control DOWN"/></action>
<action id="MethodOverloadSwitchUp"><keyboard-shortcut first-keystroke="control UP"/></action>
<action id="MethodOverloadSwitchDown"><keyboard-shortcut first-keystroke="control DOWN"/></action>
<action id="ReformatCode">
<keyboard-shortcut first-keystroke="control alt L"/>
</action>
@@ -542,6 +542,8 @@ public interface CodeInsightTestFixture extends IdeaProjectTestFixture {
void testInlays();
void checkResultWithInlays(String text);
void assertPreferredCompletionItems(int selected, @NotNull String... expected);
/**
@@ -27,6 +27,7 @@ import com.intellij.codeInsight.daemon.GutterMark;
import com.intellij.codeInsight.daemon.impl.*;
import com.intellij.codeInsight.folding.CodeFoldingManager;
import com.intellij.codeInsight.highlighting.actions.HighlightUsagesAction;
import com.intellij.codeInsight.hints.InlayInfo;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInsight.intention.impl.IntentionListStep;
import com.intellij.codeInsight.intention.impl.ShowIntentionActionsHandler;
@@ -1705,6 +1706,15 @@ public class CodeInsightTestFixtureImpl extends BaseFixture implements CodeInsig
}
}
@Override
public void checkResultWithInlays(String text) {
Document checkDocument = new DocumentImpl(text);
InlayHintsChecker checker = new InlayHintsChecker(this);
List<InlayInfo> inlayInfos = checker.extractInlays(checkDocument);
checkResult(checkDocument.getText());
checker.verifyInlays(inlayInfos, text);
}
@Override
public void assertPreferredCompletionItems(final int selected, @NotNull final String... expected) {
final LookupImpl lookup = getLookup();
@@ -17,7 +17,6 @@ package com.intellij.testFramework.utils.inlays
import com.intellij.codeInsight.daemon.impl.ParameterHintsPresentationManager
import com.intellij.codeInsight.hints.InlayInfo
import com.intellij.codeInsight.hints.InlayParameterHintsExtension
import com.intellij.codeInsight.hints.settings.ParameterNameHintsSettings
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.editor.Document
@@ -57,10 +56,17 @@ class InlayHintsChecker(private val myFixture: CodeInsightTestFixture) {
val document = myFixture.getDocument(file)
val originalText = document.text
val expectedInlays: List<InlayInfo> = extractInlays(document)
myFixture.doHighlighting();
verifyInlays(expectedInlays, originalText)
}
fun verifyInlays(expectedInlays : List<InlayInfo>, originalText: String) {
val file = myFixture.file
val document = myFixture.getDocument(file)
val actual: List<Pair<Int, String>> = getActualInlays()
val expected = expectedInlays.map { Pair(it.offset, it.text) }
if (expectedInlays.size != actual.size || actual.zip(expected).any { it.first != it.second }) {
val proposedText = StringBuilder(document.text)
actual.asReversed().forEach { proposedText.insert(it.first, "<hint text=\"${it.second}\" />") }
@@ -70,9 +76,8 @@ class InlayHintsChecker(private val myFixture: CodeInsightTestFixture) {
} ?: throw ComparisonFailure("Hints differ", originalText, proposedText.toString())
}
}
private fun getActualInlays(): List<Pair<Int, String>> {
myFixture.doHighlighting()
val editor = myFixture.editor
val allInlays = editor.inlayModel.getInlineElementsInRange(0, editor.document.textLength)
@@ -369,6 +369,9 @@ show.live.templates.in.completion.description=Show live templates in completion
java.completion.make.outer.variables.final=true
java.completion.make.outer.variables.final.description=Make variables accessed from inner class final automatically
java.completion.argument.hints=false
java.completion.argument.hints.description=When completing a method call, show hints in place of all arguments
java.completion.argument.live.template=false
java.completion.argument.live.template.description=When completing a method call, start a live template with all arguments
+6
View File
@@ -1905,6 +1905,8 @@
<java.refactoring.chainCallExtractor implementation="com.intellij.refactoring.chainCall.GuavaFluentIterableChainCallExtractor"/>
<diff.lang.DiffIgnoredRangeProvider implementation="com.intellij.diff.lang.JavaDiffIgnoredRangeProvider"/>
<actionPromoter implementation="com.intellij.codeInsight.editorActions.JavaMethodOverloadSwitchActionPromoter" />
</extensions>
<actions>
@@ -1926,6 +1928,10 @@
<action class="com.intellij.execution.testDiscovery.ConfigureTestDiscoveryAction" internal="true" id="TestDiscoveryIndexChooser" text="Choose external test discovery indices">
<add-to-group group-id="Internal" anchor="last"/>
</action>
<action id="MethodOverloadSwitchUp" class="com.intellij.codeInsight.editorActions.JavaMethodOverloadSwitchUpAction"/>
<action id="MethodOverloadSwitchDown" class="com.intellij.codeInsight.editorActions.JavaMethodOverloadSwitchDownAction" />
</actions>
</idea-plugin>