diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/JavaMethodCallElement.java b/java/java-impl/src/com/intellij/codeInsight/completion/JavaMethodCallElement.java index 80894c903314..f90ede5ddc2b 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/JavaMethodCallElement.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/JavaMethodCallElement.java @@ -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 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 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 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() { diff --git a/java/java-impl/src/com/intellij/codeInsight/editorActions/JavaMethodOverloadSwitchActionPromoter.java b/java/java-impl/src/com/intellij/codeInsight/editorActions/JavaMethodOverloadSwitchActionPromoter.java new file mode 100644 index 000000000000..9e6d05723530 --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInsight/editorActions/JavaMethodOverloadSwitchActionPromoter.java @@ -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 promote(List actions, DataContext context) { + return ContainerUtil.findAll(actions, a -> a instanceof JavaMethodOverloadSwitchUpAction || + a instanceof JavaMethodOverloadSwitchDownAction); + } +} diff --git a/java/java-impl/src/com/intellij/codeInsight/editorActions/JavaMethodOverloadSwitchDownAction.java b/java/java-impl/src/com/intellij/codeInsight/editorActions/JavaMethodOverloadSwitchDownAction.java new file mode 100644 index 000000000000..1aef923d6217 --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInsight/editorActions/JavaMethodOverloadSwitchDownAction.java @@ -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)); + } +} diff --git a/java/java-impl/src/com/intellij/codeInsight/editorActions/JavaMethodOverloadSwitchHandler.java b/java/java-impl/src/com/intellij/codeInsight/editorActions/JavaMethodOverloadSwitchHandler.java new file mode 100644 index 000000000000..5d192522b6ec --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInsight/editorActions/JavaMethodOverloadSwitchHandler.java @@ -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> 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 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 oldInlays = editor.getInlayModel().getInlineElementsInRange(offset, endOffset); + for (Inlay inlay : oldInlays) { + Disposer.dispose(inlay); + } + editor.getDocument().deleteString(offset, endOffset); + int targetCaretPosition = -1; + List 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(); + } +} diff --git a/java/java-impl/src/com/intellij/codeInsight/editorActions/JavaMethodOverloadSwitchUpAction.java b/java/java-impl/src/com/intellij/codeInsight/editorActions/JavaMethodOverloadSwitchUpAction.java new file mode 100644 index 000000000000..50ba335ad4c3 --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInsight/editorActions/JavaMethodOverloadSwitchUpAction.java @@ -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)); + } +} diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/completion/CompletionHintsTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/completion/CompletionHintsTest.java new file mode 100644 index 000000000000..5644f3681518 --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/codeInsight/completion/CompletionHintsTest.java @@ -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 } }"); + complete("setProperty"); + myFixture.checkResultWithInlays("class C { void m() { System.setProperty(, ) } }"); + + // check that hints don't disappear after daemon highlighting passes + myFixture.doHighlighting(); + myFixture.checkResultWithInlays("class C { void m() { System.setProperty(, ) } }"); + + // test Tab/Shift+Tab navigation + myFixture.checkResult("class C { void m() { System.setProperty(, ) } }"); + assertTrue(myFixture.getEditor().getCaretModel().getLogicalPosition().leansForward); + myFixture.performEditorAction("NextParameter"); + myFixture.checkResult("class C { void m() { System.setProperty(, ) } }"); + assertTrue(myFixture.getEditor().getCaretModel().getLogicalPosition().leansForward); + myFixture.performEditorAction("PrevParameter"); + myFixture.checkResult("class C { void m() { System.setProperty(, ) } }"); + 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(\"a\", \"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 } }"); + complete("toChars(int codePoint)"); + myFixture.checkResultWithInlays("class C { void m() { Character.toChars() } }"); + showParameterInfo(); + myFixture.performEditorAction("MethodOverloadSwitchDown"); + myFixture.checkResultWithInlays("class C { void m() { Character.toChars(, , ) } }"); + } + + public void testSwitchingOverloadsWithParameterFilled() { + myFixture.configureByText(JavaFileType.INSTANCE, "class C { void m() { Character.to } }"); + complete("toChars(int codePoint)"); + type("123"); + myFixture.checkResultWithInlays("class C { void m() { Character.toChars(123) } }"); + showParameterInfo(); + myFixture.performEditorAction("MethodOverloadSwitchDown"); + myFixture.checkResultWithInlays("class C { void m() { Character.toChars(123, , ) } }"); + myFixture.checkResult("class C { void m() { Character.toChars(123, , ) } }"); + } + + 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(); + } + } +} \ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/application/options/colors/highlighting/InlineElementData.java b/platform/lang-impl/src/com/intellij/application/options/colors/highlighting/InlineElementData.java index 6f027c47346e..c73357770e53 100644 --- a/platform/lang-impl/src/com/intellij/application/options/colors/highlighting/InlineElementData.java +++ b/platform/lang-impl/src/com/intellij/application/options/colors/highlighting/InlineElementData.java @@ -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 displayText) { int offset = getStartOffset(); - ParameterHintsPresentationManager.getInstance().addHint(view, offset, myText, false); + ParameterHintsPresentationManager.getInstance().addHint(view, offset, myText, false, false); List inlays = view.getInlayModel().getInlineElementsInRange(offset, offset); for (Inlay inlay : inlays) { EditorCustomElementRenderer renderer = inlay.getRenderer(); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/ParameterHintsPresentationManager.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/ParameterHintsPresentationManager.java index 32389bf0c34e..c1f2cb5d5c05 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/ParameterHintsPresentationManager.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/ParameterHintsPresentationManager.java @@ -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 PINNED = Key.create("parameter.hint.pinned"); private static final Key HINT_FONT_METRICS = Key.create("ParameterHintFontMetrics"); private static final Key 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(); } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/hint/ParameterInfoComponent.java b/platform/lang-impl/src/com/intellij/codeInsight/hint/ParameterInfoComponent.java index e2c530389028..d56e0ae0926f 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/hint/ParameterInfoComponent.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/hint/ParameterInfoComponent.java @@ -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; } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/hint/ParameterInfoController.java b/platform/lang-impl/src/com/intellij/codeInsight/hint/ParameterInfoController.java index 10220feb168b..c0b39419367e 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/hint/ParameterInfoController.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/hint/ParameterInfoController.java @@ -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 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 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 allControllers = getAllControllers(myEditor); allControllers.remove(this); myEditor.getCaretModel().removeCaretListener(myEditorCaretListener); } + public void showHint(boolean requestFocus) { + Pair 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 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 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 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 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 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; + } + } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/hint/PrevNextParameterHandler.java b/platform/lang-impl/src/com/intellij/codeInsight/hint/PrevNextParameterHandler.java index da5f8b1554d5..30e686ff0c35 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/hint/PrevNextParameterHandler.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/hint/PrevNextParameterHandler.java @@ -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); } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/hint/ShowParameterInfoContext.java b/platform/lang-impl/src/com/intellij/codeInsight/hint/ShowParameterInfoContext.java index 624103856dfb..421b536819e1 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/hint/ShowParameterInfoContext.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/hint/ShowParameterInfoContext.java @@ -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 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 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 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 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; - } - } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/hint/ShowParameterInfoHandler.java b/platform/lang-impl/src/com/intellij/codeInsight/hint/ShowParameterInfoHandler.java index 99e514bbe09c..33df15faee48 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/hint/ShowParameterInfoHandler.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/hint/ShowParameterInfoHandler.java @@ -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 pos = ShowParameterInfoContext.chooseBestHintPosition(project, editor, -1, -1, hint, true, HintManager.DEFAULT); + final Pair 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 getBestPointPosition(LightweightHint hint, - final PsiElement list, - int offset, - final boolean awtTooltip, - short preferredPosition); - } - } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/hints/ParameterHintsPassFactory.java b/platform/lang-impl/src/com/intellij/codeInsight/hints/ParameterHintsPassFactory.java index a98ddb0eb360..aca1d04682fc 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/hints/ParameterHintsPassFactory.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/hints/ParameterHintsPassFactory.java @@ -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 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); diff --git a/platform/platform-impl/src/com/intellij/codeInsight/hint/HintManagerImpl.java b/platform/platform-impl/src/com/intellij/codeInsight/hint/HintManagerImpl.java index 35ee1af03c91..db9be510e1f5 100644 --- a/platform/platform-impl/src/com/intellij/codeInsight/hint/HintManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/codeInsight/hint/HintManagerImpl.java @@ -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(); } diff --git a/platform/platform-resources/src/keymaps/$default.xml b/platform/platform-resources/src/keymaps/$default.xml index ca88b59a9742..a00d6a63132d 100644 --- a/platform/platform-resources/src/keymaps/$default.xml +++ b/platform/platform-resources/src/keymaps/$default.xml @@ -80,6 +80,9 @@ + + + diff --git a/platform/testFramework/src/com/intellij/testFramework/fixtures/CodeInsightTestFixture.java b/platform/testFramework/src/com/intellij/testFramework/fixtures/CodeInsightTestFixture.java index e06f47071a97..fb0c476546b2 100644 --- a/platform/testFramework/src/com/intellij/testFramework/fixtures/CodeInsightTestFixture.java +++ b/platform/testFramework/src/com/intellij/testFramework/fixtures/CodeInsightTestFixture.java @@ -542,6 +542,8 @@ public interface CodeInsightTestFixture extends IdeaProjectTestFixture { void testInlays(); + void checkResultWithInlays(String text); + void assertPreferredCompletionItems(int selected, @NotNull String... expected); /** diff --git a/platform/testFramework/src/com/intellij/testFramework/fixtures/impl/CodeInsightTestFixtureImpl.java b/platform/testFramework/src/com/intellij/testFramework/fixtures/impl/CodeInsightTestFixtureImpl.java index bbb4a31feef7..d21b2136fcc0 100644 --- a/platform/testFramework/src/com/intellij/testFramework/fixtures/impl/CodeInsightTestFixtureImpl.java +++ b/platform/testFramework/src/com/intellij/testFramework/fixtures/impl/CodeInsightTestFixtureImpl.java @@ -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 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(); diff --git a/platform/testFramework/src/com/intellij/testFramework/utils/inlays/InlayParameterHintsTest.kt b/platform/testFramework/src/com/intellij/testFramework/utils/inlays/InlayParameterHintsTest.kt index c6516436d310..12630cde0fbd 100644 --- a/platform/testFramework/src/com/intellij/testFramework/utils/inlays/InlayParameterHintsTest.kt +++ b/platform/testFramework/src/com/intellij/testFramework/utils/inlays/InlayParameterHintsTest.kt @@ -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 = extractInlays(document) + myFixture.doHighlighting(); + verifyInlays(expectedInlays, originalText) + } + + fun verifyInlays(expectedInlays : List, originalText: String) { + val file = myFixture.file + val document = myFixture.getDocument(file) val actual: List> = 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, "") } @@ -70,9 +76,8 @@ class InlayHintsChecker(private val myFixture: CodeInsightTestFixture) { } ?: throw ComparisonFailure("Hints differ", originalText, proposedText.toString()) } } - + private fun getActualInlays(): List> { - myFixture.doHighlighting() val editor = myFixture.editor val allInlays = editor.inlayModel.getInlineElementsInRange(0, editor.document.textLength) diff --git a/platform/util/resources/misc/registry.properties b/platform/util/resources/misc/registry.properties index 9496b715965f..0e5bb9710ae3 100644 --- a/platform/util/resources/misc/registry.properties +++ b/platform/util/resources/misc/registry.properties @@ -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 diff --git a/resources/src/META-INF/IdeaPlugin.xml b/resources/src/META-INF/IdeaPlugin.xml index 469681d20260..498f0adae99a 100644 --- a/resources/src/META-INF/IdeaPlugin.xml +++ b/resources/src/META-INF/IdeaPlugin.xml @@ -1905,6 +1905,8 @@ + + @@ -1926,6 +1928,10 @@ + + + +