mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-18 22:17:05 +07:00
option to show parameter hints on method completion, ability to switch method overloads (Java only)
This commit is contained in:
@@ -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() {
|
||||
|
||||
+31
@@ -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);
|
||||
}
|
||||
}
|
||||
+24
@@ -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));
|
||||
}
|
||||
}
|
||||
+149
@@ -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();
|
||||
}
|
||||
}
|
||||
+24
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -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();
|
||||
|
||||
+22
-4
@@ -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;
|
||||
}
|
||||
|
||||
+223
-22
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+8
-138
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -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>
|
||||
|
||||
+2
@@ -542,6 +542,8 @@ public interface CodeInsightTestFixture extends IdeaProjectTestFixture {
|
||||
|
||||
void testInlays();
|
||||
|
||||
void checkResultWithInlays(String text);
|
||||
|
||||
void assertPreferredCompletionItems(int selected, @NotNull String... expected);
|
||||
|
||||
/**
|
||||
|
||||
+10
@@ -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();
|
||||
|
||||
+10
-5
@@ -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
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user