PY-22004 Fixed: Show all possible parameter hints when function could be resolved to different locations

Update PyParameterInfoHandler to use pair of PyCallExpression and PyMarkedCallee as ParameterType and PyCallExpression.multiResolveCallee(PyResolveContext) to support multiresolved callees.
This commit is contained in:
Semyon Proshev
2017-01-27 18:19:53 +03:00
parent da669cc2bf
commit c7e1b58176
4 changed files with 260 additions and 154 deletions

View File

@@ -18,6 +18,7 @@ package com.jetbrains.python;
import com.intellij.codeInsight.CodeInsightBundle;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.lang.parameterInfo.*;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
@@ -32,16 +33,16 @@ import com.jetbrains.python.psi.impl.PyPsiUtils;
import com.jetbrains.python.psi.resolve.PyResolveContext;
import com.jetbrains.python.psi.types.TypeEvalContext;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import static com.jetbrains.python.psi.PyCallExpression.PyMarkedCallee;
/**
* @author dcheryasov
*/
public class PyParameterInfoHandler implements ParameterInfoHandler<PyArgumentList, PyCallExpression.PyArgumentsMapping> {
private static final String NO_PARAMS_MSG = CodeInsightBundle.message("parameter.info.no.parameters");
public class PyParameterInfoHandler implements ParameterInfoHandler<PyArgumentList, Pair<PyCallExpression, PyMarkedCallee>> {
@NotNull
private static final String NO_PARAMS_MSG = CodeInsightBundle.message("parameter.info.no.parameters");
@Override
public boolean couldShowInLookup() {
@@ -49,52 +50,62 @@ public class PyParameterInfoHandler implements ParameterInfoHandler<PyArgumentLi
}
@Override
public Object[] getParametersForLookup(final LookupElement item, final ParameterInfoContext context) {
@NotNull
public Object[] getParametersForLookup(LookupElement item, ParameterInfoContext context) {
return ArrayUtil.EMPTY_OBJECT_ARRAY;
}
@Override
public Object[] getParametersForDocumentation(final PyCallExpression.PyArgumentsMapping p, final ParameterInfoContext context) {
@NotNull
public Object[] getParametersForDocumentation(Pair<PyCallExpression, PyMarkedCallee> callAndCallee, ParameterInfoContext context) {
return ArrayUtil.EMPTY_OBJECT_ARRAY;
}
@Override
public PyArgumentList findElementForParameterInfo(@NotNull final CreateParameterInfoContext context) {
PyArgumentList argumentList = findArgumentList(context, -1);
@Nullable
public PyArgumentList findElementForParameterInfo(@NotNull CreateParameterInfoContext context) {
final PyArgumentList argumentList = findArgumentList(context, -1);
if (argumentList != null) {
final PyCallExpression callExpr = argumentList.getCallExpression();
if (callExpr != null) {
final PyCallExpression call = argumentList.getCallExpression();
if (call != null) {
final TypeEvalContext typeEvalContext = TypeEvalContext.userInitiated(argumentList.getProject(), argumentList.getContainingFile());
final PyResolveContext resolveContext = PyResolveContext.noImplicits().withRemote().withTypeEvalContext(typeEvalContext);
final PyCallExpression.PyArgumentsMapping mapping = callExpr.mapArguments(resolveContext);
if (mapping.getMarkedCallee() != null) {
context.setItemsToShow(new Object[] { mapping });
return argumentList;
}
final Object[] items = call.multiResolveCallee(resolveContext)
.stream()
.map(markedCallee -> Pair.createNonNull(call, markedCallee))
.toArray();
context.setItemsToShow(items);
return argumentList;
}
}
return null;
}
private static PyArgumentList findArgumentList(final ParameterInfoContext context, int parameterListStart) {
final int offset = context.getOffset();
PyArgumentList argumentList = ParameterInfoUtils.findParentOfType(context.getFile(), offset - 1, PyArgumentList.class);
if (argumentList != null) {
final TextRange range = argumentList.getTextRange();
if (parameterListStart >= 0 && range.getStartOffset() != parameterListStart){
argumentList = PsiTreeUtil.getParentOfType(argumentList, PyArgumentList.class);
}
@Nullable
private static PyArgumentList findArgumentList(@NotNull ParameterInfoContext context, int parameterListStart) {
final PyArgumentList argumentList =
ParameterInfoUtils.findParentOfType(context.getFile(), context.getOffset() - 1, PyArgumentList.class);
if (argumentList != null && parameterListStart >= 0 && argumentList.getTextRange().getStartOffset() != parameterListStart) {
return PsiTreeUtil.getParentOfType(argumentList, PyArgumentList.class);
}
return argumentList;
}
@Override
public void showParameterInfo(@NotNull final PyArgumentList element, @NotNull final CreateParameterInfoContext context) {
public void showParameterInfo(@NotNull PyArgumentList element, @NotNull CreateParameterInfoContext context) {
context.showHint(element, element.getTextOffset(), this);
}
@Override
public PyArgumentList findElementForUpdatingParameterInfo(@NotNull final UpdateParameterInfoContext context) {
@Nullable
public PyArgumentList findElementForUpdatingParameterInfo(@NotNull UpdateParameterInfoContext context) {
return findArgumentList(context, context.getParameterListStart());
}
@@ -103,88 +114,95 @@ public class PyParameterInfoHandler implements ParameterInfoHandler<PyArgumentLi
We cannot store an index since we cannot determine what is an argument until we actually map arguments to parameters.
This is because a tuple in arguments may be a whole argument or map to a tuple parameter.
*/
public void updateParameterInfo(@NotNull final PyArgumentList argumentList, @NotNull final UpdateParameterInfoContext context) {
@Override
public void updateParameterInfo(@NotNull PyArgumentList argumentList, @NotNull UpdateParameterInfoContext context) {
if (context.getParameterOwner() != argumentList) {
context.removeHint();
return;
}
// align offset to nearest expression; context may point to a space, etc.
List<PyExpression> flat_args = PyUtil.flattenedParensAndLists(argumentList.getArguments());
int alleged_cursor_offset = context.getOffset(); // this is already shifted backwards to skip spaces
final TextRange argListTextRange = argumentList.getTextRange();
if (!argListTextRange.contains(alleged_cursor_offset) && argumentList.getText().endsWith(")")) {
// align offset to nearest expression; context may point to a space, etc.
final List<PyExpression> flattenedArguments = PyUtil.flattenedParensAndLists(argumentList.getArguments());
final int allegedCursorOffset = context.getOffset(); // this is already shifted backwards to skip spaces
if (!argumentList.getTextRange().contains(allegedCursorOffset) && argumentList.getText().endsWith(")")) {
context.removeHint();
return;
}
PsiFile file = context.getFile();
CharSequence chars = file.getViewProvider().getContents();
final PsiFile file = context.getFile();
final CharSequence chars = file.getViewProvider().getContents();
int offset = -1;
for (PyExpression arg : flat_args) {
TextRange range = arg.getTextRange();
// widen the range to include all whitespace around the arg
int left = CharArrayUtil.shiftBackward(chars, range.getStartOffset()-1, " \t\r\n");
for (PyExpression argument : flattenedArguments) {
final TextRange range = argument.getTextRange();
// widen the range to include all whitespace around the argument
final int left = CharArrayUtil.shiftBackward(chars, range.getStartOffset() - 1, " \t\r\n");
int right = CharArrayUtil.shiftForwardCarefully(chars, range.getEndOffset(), " \t\r\n");
if (arg.getParent() instanceof PyListLiteralExpression || arg.getParent() instanceof PyTupleExpression) {
if (argument.getParent() instanceof PyListLiteralExpression || argument.getParent() instanceof PyTupleExpression) {
right = CharArrayUtil.shiftForward(chars, range.getEndOffset(), " \t\r\n])");
}
if (left <= alleged_cursor_offset && right >= alleged_cursor_offset) {
if (left <= allegedCursorOffset && right >= allegedCursorOffset) {
offset = range.getStartOffset();
break;
}
}
context.setCurrentParameter(offset);
}
@NotNull
@Override
public String getParameterCloseChars() {
return ",()"; // lpar may mean a nested tuple param, so it's included
}
@Override
public boolean tracksParameterIndex() {
return false;
}
@Override
public void updateUI(final PyCallExpression.PyArgumentsMapping oldMapping, @NotNull final ParameterInfoUIContext context) {
if (oldMapping == null) return;
final PyCallExpression callExpression = oldMapping.getCallExpression();
public void updateUI(@NotNull Pair<PyCallExpression, PyMarkedCallee> callAndCallee, @NotNull ParameterInfoUIContext context) {
final PyCallExpression callExpression = callAndCallee.getFirst();
PyPsiUtils.assertValid(callExpression);
// really we need to redo analysis every UI update; findElementForParameterInfo isn't called while typing
final TypeEvalContext typeEvalContext = TypeEvalContext.userInitiated(callExpression.getProject(), callExpression.getContainingFile());
final PyResolveContext resolveContext = PyResolveContext.noImplicits().withRemote().withTypeEvalContext(typeEvalContext);
final PyCallExpression.PyArgumentsMapping mapping = callExpression.mapArguments(resolveContext);
final PyMarkedCallee marked = mapping.getMarkedCallee();
if (marked == null) return; // resolution failed
final PyCallable callable = marked.getCallable();
final List<PyParameter> parameterList = PyUtil.getParameters(callable, typeEvalContext);
final List<PyNamedParameter> namedParameters = new ArrayList<>(parameterList.size());
final TypeEvalContext typeEvalContext = TypeEvalContext.userInitiated(callExpression.getProject(), callExpression.getContainingFile());
final PyCallExpression.PyArgumentsMapping mapping =
PyCallExpressionHelper.mapArguments(callExpression, callAndCallee.getSecond(), typeEvalContext);
final PyMarkedCallee markedCallee = mapping.getMarkedCallee();
if (markedCallee == null) return;
final List<PyParameter> parameters = PyUtil.getParameters(markedCallee.getCallable(), typeEvalContext);
final List<PyNamedParameter> namedParameters = new ArrayList<>(parameters.size());
// param -> hint index. indexes are not contiguous, because some hints are parentheses.
final Map<PyNamedParameter, Integer> parameterToIndex = new HashMap<>();
// formatting of hints: hint index -> flags. this includes flags for parens.
final Map<Integer, EnumSet<ParameterInfoUIContextEx.Flag>> hintFlags = new HashMap<>();
final List<String> hintsList = buildParameterListHint(parameterList, namedParameters, parameterToIndex, hintFlags, typeEvalContext);
final List<String> hintsList = buildParameterListHint(parameters, namedParameters, parameterToIndex, hintFlags, typeEvalContext);
final int currentParamOffset = context.getCurrentParameterIndex(); // in Python mode, we get an offset here, not an index!
// gray out enough first parameters as implicit (self, cls, ...)
for (int i=0; i < marked.getImplicitOffset(); i += 1) {
for (int i = 0; i < markedCallee.getImplicitOffset(); i++) {
hintFlags.get(parameterToIndex.get(namedParameters.get(i))).add(ParameterInfoUIContextEx.Flag.DISABLE); // show but mark as absent
}
final List<PyExpression> flattenedArgs = PyUtil.flattenedParensAndLists(callExpression.getArguments());
int lastParamIndex = collectHighlights(mapping, parameterList, parameterToIndex, hintFlags, flattenedArgs, currentParamOffset);
final List<PyExpression> flattenedArguments = PyUtil.flattenedParensAndLists(callExpression.getArguments());
final int lastParamIndex = collectHighlights(mapping, parameters, parameterToIndex, hintFlags, flattenedArguments, currentParamOffset);
highlightNext(marked, parameterList, namedParameters, parameterToIndex, hintFlags, flattenedArgs.isEmpty(), lastParamIndex);
highlightNext(markedCallee, parameters, namedParameters, parameterToIndex, hintFlags, flattenedArguments.isEmpty(), lastParamIndex);
String[] hints = ArrayUtil.toStringArray(hintsList);
if (context instanceof ParameterInfoUIContextEx) {
final ParameterInfoUIContextEx pic = (ParameterInfoUIContextEx)context;
EnumSet[] flags = new EnumSet[hintFlags.size()];
for (int i = 0; i < flags.length; i += 1) flags[i] = hintFlags.get(i);
for (int i = 0; i < flags.length; i++) flags[i] = hintFlags.get(i);
if (hints.length < 1) {
hints = new String[]{NO_PARAMS_MSG};
flags = new EnumSet[]{EnumSet.of(ParameterInfoUIContextEx.Flag.DISABLE)};
@@ -194,7 +212,7 @@ public class PyParameterInfoHandler implements ParameterInfoHandler<PyArgumentLi
pic.setupUIComponentPresentation(hints, flags, context.getDefaultParameterColor());
}
else { // fallback, no highlight
StringBuilder signatureBuilder = new StringBuilder();
final StringBuilder signatureBuilder = new StringBuilder();
if (hints.length > 1) {
for (String s : hints) signatureBuilder.append(s);
}

View File

@@ -709,25 +709,40 @@ public class PyCallExpressionHelper {
}
final TypeEvalContext context = resolveContext.getTypeEvalContext();
final List<PyCallExpression.PyArgumentsMapping> result = new ArrayList<>();
return ContainerUtil.map(callExpression.multiResolveCallee(resolveContext, implicitOffset),
markedCallee -> mapArguments(callExpression, argumentList, markedCallee, context));
}
for (PyCallExpression.PyMarkedCallee markedCallee : callExpression.multiResolveCallee(resolveContext, implicitOffset)) {
final List<PyParameter> parameters = PyUtil.getParameters(markedCallee.getCallable(), context);
final List<PyParameter> explicitParameters = dropImplicitParameters(parameters, markedCallee.getImplicitOffset());
final List<PyExpression> arguments = Arrays.asList(argumentList.getArguments());
final ArgumentMappingResults mappingResults = analyzeArguments(arguments, explicitParameters);
result.add(new PyCallExpression.PyArgumentsMapping(callExpression,
markedCallee,
mappingResults.getMappedParameters(),
mappingResults.getUnmappedParameters(),
mappingResults.getUnmappedArguments(),
mappingResults.getParametersMappedToVariadicPositionalArguments(),
mappingResults.getParametersMappedToVariadicKeywordArguments(),
mappingResults.getMappedTupleParameters()));
@NotNull
public static PyCallExpression.PyArgumentsMapping mapArguments(@NotNull PyCallExpression callExpression,
@NotNull PyCallExpression.PyMarkedCallee markedCallee,
@NotNull TypeEvalContext context) {
final PyArgumentList argumentList = callExpression.getArgumentList();
if (argumentList == null) {
return PyCallExpression.PyArgumentsMapping.empty(callExpression);
}
return result;
return mapArguments(callExpression, argumentList, markedCallee, context);
}
@NotNull
private static PyCallExpression.PyArgumentsMapping mapArguments(@NotNull PyCallExpression callExpression,
@NotNull PyArgumentList argumentList,
@NotNull PyCallExpression.PyMarkedCallee markedCallee,
@NotNull TypeEvalContext context) {
final List<PyParameter> parameters = PyUtil.getParameters(markedCallee.getCallable(), context);
final List<PyParameter> explicitParameters = dropImplicitParameters(parameters, markedCallee.getImplicitOffset());
final List<PyExpression> arguments = Arrays.asList(argumentList.getArguments());
final ArgumentMappingResults mappingResults = analyzeArguments(arguments, explicitParameters);
return new PyCallExpression.PyArgumentsMapping(callExpression,
markedCallee,
mappingResults.getMappedParameters(),
mappingResults.getUnmappedParameters(),
mappingResults.getUnmappedArguments(),
mappingResults.getParametersMappedToVariadicPositionalArguments(),
mappingResults.getParametersMappedToVariadicKeywordArguments(),
mappingResults.getMappedTupleParameters());
}
@NotNull

View File

@@ -0,0 +1,18 @@
class C1:
def foo(self, x):
return self
class C2:
def foo(self, x, y: str):
return self
def f():
"""
:rtype: C1 | C2
"""
pass
f().foo(1, <arg2>2)

View File

@@ -21,12 +21,12 @@ import com.intellij.lang.parameterInfo.ParameterInfoUIContextEx;
import com.intellij.lang.parameterInfo.UpdateParameterInfoContext;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.util.ArrayUtil;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.HashSet;
import com.jetbrains.python.fixtures.LightMarkedTestCase;
import com.jetbrains.python.psi.LanguageLevel;
@@ -36,9 +36,8 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.awt.*;
import java.util.EnumSet;
import java.util.Map;
import java.util.Set;
import java.util.*;
import java.util.List;
/**
* Tests parameter info available via ^P at call sites.
@@ -430,22 +429,45 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
);
}
// PY-22004
public void testMultiResolved() {
myFixture.copyDirectoryToProject("typing", "");
runWithLanguageLevel(
LanguageLevel.PYTHON35,
() -> {
final int offset = loadTest(1).get("<arg2>").getTextOffset();
final List<String> texts = Arrays.asList("self: C1, x", "self: C2, x, y: str");
final List<String[]> highlighted = Arrays.asList(ArrayUtil.EMPTY_STRING_ARRAY, new String[]{"y: str"});
final List<String[]> disabled = Arrays.asList(new String[]{"self: C1, "}, new String[]{"self: C2, "});
feignCtrlP(offset).check(texts, highlighted, disabled);
}
);
}
/**
* Imitates pressing of Ctrl+P; fails if results are not as expected.
* @param offset offset of 'cursor' where ^P is pressed.
* @param offset offset of 'cursor' where Ctrl+P is pressed.
* @return a {@link Collector} with collected hint info.
* @throws Exception if it fails
*/
@NotNull
private Collector feignCtrlP(int offset) {
Collector collector = new Collector(myFixture.getProject(), myFixture.getFile(), offset);
PyParameterInfoHandler handler = new PyParameterInfoHandler();
final PyArgumentList parameterOwner = handler.findElementForParameterInfo(collector);
collector.setParameterOwner(parameterOwner); // finds arglist, sets items to show
final PyParameterInfoHandler handler = new PyParameterInfoHandler();
final Collector collector = new Collector(myFixture.getFile(), offset);
collector.setParameterOwner(handler.findElementForParameterInfo(collector));
if (collector.getParameterOwner() != null) {
assertEquals("Collected one analysis result", 1, collector.myItems.length);
handler.updateParameterInfo((PyArgumentList)collector.getParameterOwner(), collector); // moves offset to correct parameter
handler.updateUI((PyCallExpression.PyArgumentsMapping)collector.getItemsToShow()[0], collector); // sets hint text and flags
handler.updateParameterInfo((PyArgumentList)collector.getParameterOwner(), collector);
for (Object itemToShow : collector.getItemsToShow()) {
//noinspection unchecked
handler.updateUI((Pair<PyCallExpression, PyCallExpression.PyMarkedCallee>)itemToShow, collector);
}
}
return collector;
}
@@ -454,28 +476,38 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
*/
private static class Collector implements ParameterInfoUIContextEx, CreateParameterInfoContext, UpdateParameterInfoContext {
@NotNull
private final PsiFile myFile;
private final int myOffset;
private int myIndex;
private Object[] myItems;
private final Project myProject;
private final Editor myEditor;
private PyArgumentList myParamOwner;
private String[] myTexts;
private EnumSet<Flag>[] myFlags;
private Collector(Project project, PsiFile file, int offset) {
myProject = project;
myEditor = null;
@NotNull
private final List<String[]> myListOfTexts;
@NotNull
private final List<EnumSet<Flag>[]> myListOfFlags;
@Nullable
private PyArgumentList myParameterOwner;
@NotNull
private Object[] myItemsToShow;
private int myIndex;
private Collector(@NotNull PsiFile file, int offset) {
myFile = file;
myOffset = offset;
myListOfTexts = new ArrayList<>();
myListOfFlags = new ArrayList<>();
myItemsToShow = ArrayUtil.EMPTY_OBJECT_ARRAY;
}
@Override
public String setupUIComponentPresentation(String[] texts, EnumSet<Flag>[] flags, Color background) {
assert texts.length == flags.length;
myTexts = texts;
myFlags = flags;
@NotNull
public String setupUIComponentPresentation(@NotNull String[] texts, @NotNull EnumSet<Flag>[] flags, @NotNull Color background) {
assertEquals(texts.length, flags.length);
myListOfTexts.add(texts);
myListOfFlags.add(flags);
return StringUtil.join(texts, "");
}
@@ -504,7 +536,7 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
public void setUIComponentEnabled(boolean enabled) { }
@Override
public void setUIComponentEnabled(int index, boolean b) { }
public void setUIComponentEnabled(int index, boolean enabled) { }
@Override
public int getCurrentParameterIndex() {
@@ -515,14 +547,15 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
public void removeHint() { }
@Override
public void setParameterOwner(PsiElement o) {
assertTrue("Found element is a python arglist", o == null || o instanceof PyArgumentList);
myParamOwner = (PyArgumentList)o;
public void setParameterOwner(@Nullable PsiElement o) {
assertTrue("Found element is not `null` and not " + PyArgumentList.class.getName(), o == null || o instanceof PyArgumentList);
myParameterOwner = (PyArgumentList)o;
}
@Override
@Nullable
public PsiElement getParameterOwner() {
return myParamOwner;
return myParameterOwner;
}
@Override
@@ -536,18 +569,20 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
}
@Override
@NotNull
public Color getDefaultParameterColor() {
return java.awt.Color.BLACK;
}
@Override
@NotNull
public Object[] getItemsToShow() {
return myItems;
return myItemsToShow;
}
@Override
public void setItemsToShow(Object[] items) {
myItems = items;
public void setItemsToShow(@NotNull Object[] items) {
myItemsToShow = items;
}
@Override
@@ -575,10 +610,11 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
@Override
public Project getProject() {
return myProject;
throw new UnsupportedOperationException();
}
@Override
@NotNull
public PsiFile getFile() {
return myFile;
}
@@ -591,54 +627,73 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
@Override
@NotNull
public Editor getEditor() {
return myEditor;
throw new UnsupportedOperationException();
}
/**
* Checks if hint data look as expected.
* @param text expected text of the hint, without formatting
* @param highlighted expected highlighted substrings of hint
* @param disabled expected disabled substrings of hint
*/
public void check(String text, String[] highlighted, String[] disabled) {
assertEquals("Signature", text, StringUtil.join(myTexts, ""));
StringBuilder wrongs = new StringBuilder();
// see if highlighted matches
Set<String> highlightSet = new HashSet<>();
ContainerUtil.addAll(highlightSet, highlighted);
for (int i = 0; i < myTexts.length; i += 1) {
if (myFlags[i].contains(Flag.HIGHLIGHT) && !highlightSet.contains(myTexts[i])) {
wrongs.append("Highlighted unexpected '").append(myTexts[i]).append("'. ");
}
}
for (int i = 0; i < myTexts.length; i += 1) {
if (!myFlags[i].contains(Flag.HIGHLIGHT) && highlightSet.contains(myTexts[i])) {
wrongs.append("Not highlighted expected '").append(myTexts[i]).append("'. ");
}
}
// see if disabled matches
Set<String> disabledSet = new HashSet<>();
ContainerUtil.addAll(disabledSet, disabled);
for (int i = 0; i < myTexts.length; i += 1) {
if (myFlags[i].contains(Flag.DISABLE) && !disabledSet.contains(myTexts[i])) {
wrongs.append("Highlighted a disabled '").append(myTexts[i]).append("'. ");
}
}
for (int i = 0; i < myTexts.length; i += 1) {
if (!myFlags[i].contains(Flag.DISABLE) && disabledSet.contains(myTexts[i])) {
wrongs.append("Not disabled expected '").append(myTexts[i]).append("'. ");
}
}
//
if (wrongs.length() > 0) fail(wrongs.toString());
}
public void check(String text, String[] highlighted) {
private void check(@NotNull String text, @NotNull String[] highlighted) {
check(text, highlighted, ArrayUtil.EMPTY_STRING_ARRAY);
}
public void assertNotFound() {
assertNull(myParamOwner);
private void check(@NotNull String text, @NotNull String[] highlighted, @NotNull String[] disabled) {
assertEquals("Number of collected hints is wrong", 1, myItemsToShow.length);
check(text, highlighted, disabled, 0);
}
private void check(@NotNull List<String> texts, @NotNull List<String[]> highlighted, @NotNull List<String[]> disabled) {
assertEquals("Number of collected hints is wrong", texts.size(), myItemsToShow.length);
for (int i = 0; i < texts.size(); i++) {
check(texts.get(i), highlighted.get(i), disabled.get(i), i);
}
}
/**
* Checks if hint data looks as expected.
*
* @param text expected text of the hint, without formatting
* @param highlighted expected highlighted substrings of hint
* @param disabled expected disabled substrings of hint
* @param index hint index
*/
private void check(@NotNull String text, @NotNull String[] highlighted, @NotNull String[] disabled, int index) {
final String[] hintText = myListOfTexts.get(index);
final EnumSet<Flag>[] hintFlags = myListOfFlags.get(index);
assertEquals("Signature", text, StringUtil.join(hintText, ""));
final StringBuilder wrongs = new StringBuilder();
// see if highlighted matches
final Set<String> highlightSet = new HashSet<>(Arrays.asList(highlighted));
for (int i = 0; i < hintText.length; i++) {
if (hintFlags[i].contains(Flag.HIGHLIGHT) && !highlightSet.contains(hintText[i])) {
wrongs.append("Highlighted unexpected '").append(hintText[i]).append("'. ");
}
}
for (int i = 0; i < hintText.length; i++) {
if (!hintFlags[i].contains(Flag.HIGHLIGHT) && highlightSet.contains(hintText[i])) {
wrongs.append("Not highlighted expected '").append(hintText[i]).append("'. ");
}
}
// see if disabled matches
final Set<String> disabledSet = new HashSet<>(Arrays.asList(disabled));
for (int i = 0; i < hintText.length; i++) {
if (hintFlags[i].contains(Flag.DISABLE) && !disabledSet.contains(hintText[i])) {
wrongs.append("Highlighted a disabled '").append(hintText[i]).append("'. ");
}
}
for (int i = 0; i < hintText.length; i++) {
if (!hintFlags[i].contains(Flag.DISABLE) && disabledSet.contains(hintText[i])) {
wrongs.append("Not disabled expected '").append(hintText[i]).append("'. ");
}
}
//
if (wrongs.length() > 0) fail(wrongs.toString());
}
private void assertNotFound() {
assertNull(myParameterOwner);
}
}
}