mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-08 15:09:39 +07:00
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:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
18
python/testData/paramInfo/MultiResolved.py
Normal file
18
python/testData/paramInfo/MultiResolved.py
Normal 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)
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user