diff --git a/python/src/com/jetbrains/python/PyParameterInfoHandler.java b/python/src/com/jetbrains/python/PyParameterInfoHandler.java index 6680b54078cc..950ccc798840 100644 --- a/python/src/com/jetbrains/python/PyParameterInfoHandler.java +++ b/python/src/com/jetbrains/python/PyParameterInfoHandler.java @@ -100,9 +100,9 @@ public class PyParameterInfoHandler implements ParameterInfoHandler n_param_list = new ArrayList(raw_params.length); - final List hint_texts = new ArrayList(raw_params.length); + final List raw_params = Arrays.asList(callable.getParameterList().getParameters()); + final List n_param_list = new ArrayList(raw_params.size()); + final List hint_texts = new ArrayList(raw_params.size()); // param -> hint index. indexes are not contiguous, because some hints are parentheses. final Map param_indexes = new HashMap(); @@ -153,50 +153,60 @@ public class PyParameterInfoHandler implements ParameterInfoHandler args = PyUtil.flattenedParensAndLists(arglist.getArguments()); - for (PyExpression arg : args) { - if (arg.getTextRange().contains(current_param_offset)) { - PsiElement seeker = arg; - while (seeker != arglist && seeker != null && !result.getPlainMappedParams().containsKey(seeker)) { - seeker = seeker.getParent(); // flattener may have flattened a tuple arg that is mapped to a plain param; find it. - } - if (seeker instanceof PyExpression) { - PyNamedParameter param = result.getPlainMappedParams().get((PyExpression)seeker); - if (param != null) { - final Integer param_index = param_indexes.get(param); - if (param_index < hint_flags.size()) { - hint_flags.get(param_index).add(ParameterInfoUIContextEx.Flag.HIGHLIGHT); - } + // match params to available args, highlight current param(s) + boolean can_offer_next = true; // can we highlight next unfilled parameter + int last_param_index = marked.getImplicitOffset(); + final List flat_args = PyUtil.flattenedParensAndLists(arglist.getArguments()); + for (PyExpression arg : flat_args) { + can_offer_next &= !(arg instanceof PyKeywordArgument); + final boolean must_highlight = arg.getTextRange().contains(current_param_offset); + PsiElement seeker = arg; + while (seeker != arglist && seeker != null && !result.getPlainMappedParams().containsKey(seeker)) { + seeker = seeker.getParent(); // flattener may have flattened a tuple arg that is mapped to a plain param; find it. + } + if (seeker instanceof PyExpression) { + PyNamedParameter param = result.getPlainMappedParams().get((PyExpression)seeker); + last_param_index = Math.max(last_param_index, raw_params.indexOf(param)); + if (must_highlight && param != null) { + final Integer param_index = param_indexes.get(param); + if (param_index < hint_flags.size()) { + hint_flags.get(param_index).add(ParameterInfoUIContextEx.Flag.HIGHLIGHT); + can_offer_next = false; } } - else if (arg == result.getTupleArg()) { - // mark all params that map to *arg - for (PyNamedParameter tpar : result.getTupleMappedParams()) { + } + else if (arg == result.getTupleArg()) { + // mark all params that map to *arg + for (PyNamedParameter tpar : result.getTupleMappedParams()) { + last_param_index = Math.max(last_param_index, raw_params.indexOf(tpar)); + final Integer param_index = param_indexes.get(tpar); + if (must_highlight && param_index != null && param_index < hint_flags.size()) { + hint_flags.get(param_index).add(ParameterInfoUIContextEx.Flag.HIGHLIGHT); + can_offer_next = false; + } + } + } + else if (arg == result.getKwdArg()) { + // mark all n_params that map to **arg + for (PyNamedParameter tpar : result.getKwdMappedParams()) { + last_param_index = Math.max(last_param_index, raw_params.indexOf(tpar)); + final Integer param_index = param_indexes.get(tpar); + if (must_highlight && param_index != null && param_index < hint_flags.size()) { + hint_flags.get(param_index).add(ParameterInfoUIContextEx.Flag.HIGHLIGHT); + can_offer_next = false; + } + } + } + else { + // maybe it's mapped to a nested tuple? + List nparams = result.getNestedMappedParams().get(arg); + if (nparams != null) { + for (PyNamedParameter tpar : nparams) { + last_param_index = Math.max(last_param_index, raw_params.indexOf(tpar)); final Integer param_index = param_indexes.get(tpar); - if (param_index != null && param_index < hint_flags.size()) { + if (must_highlight && param_index != null && param_index < hint_flags.size()) { hint_flags.get(param_index).add(ParameterInfoUIContextEx.Flag.HIGHLIGHT); - } - } - } - else if (arg == result.getKwdArg()) { - // mark all n_params that map to **arg - for (PyNamedParameter tpar : result.getKwdMappedParams()) { - final Integer param_index = param_indexes.get(tpar); - if (param_index != null && param_index < hint_flags.size()) { - hint_flags.get(param_index).add(ParameterInfoUIContextEx.Flag.HIGHLIGHT); - } - } - } - else { - // maybe it's mapped to a nested tuple? - List nparams = result.getNestedMappedParams().get(arg); - if (nparams != null) { - for (PyNamedParameter tpar : nparams) { - final Integer param_index = param_indexes.get(tpar); - if (param_index != null && param_index < hint_flags.size()) { - hint_flags.get(param_index).add(ParameterInfoUIContextEx.Flag.HIGHLIGHT); - } + can_offer_next = false; } } } @@ -204,9 +214,9 @@ public class PyParameterInfoHandler implements ParameterInfoHandlerd=10, ) diff --git a/python/testData/paramInfo/PartialSimple.py b/python/testData/paramInfo/PartialSimple.py new file mode 100644 index 000000000000..88845b9a0bb3 --- /dev/null +++ b/python/testData/paramInfo/PartialSimple.py @@ -0,0 +1,4 @@ +def foo(a, b, c): + pass + +foo(1, 2, ) diff --git a/python/testData/paramInfo/PartialWithList.py b/python/testData/paramInfo/PartialWithList.py new file mode 100644 index 000000000000..b7741e6f88ae --- /dev/null +++ b/python/testData/paramInfo/PartialWithList.py @@ -0,0 +1,4 @@ +def f(a, b, c, **kwargs): + pass + +f(a, [1, 2, 3], ) diff --git a/python/testSrc/com/jetbrains/python/PyParameterInfoTest.java b/python/testSrc/com/jetbrains/python/PyParameterInfoTest.java index cecd11d02f17..8d1eef938157 100644 --- a/python/testSrc/com/jetbrains/python/PyParameterInfoTest.java +++ b/python/testSrc/com/jetbrains/python/PyParameterInfoTest.java @@ -32,9 +32,14 @@ public class PyParameterInfoTest extends LightMarkedTestCase { return configureByFile(fname); } - public void testSimpleFunction() { + protected Map loadTest(int expected_marks) { Map marks = loadTest(); - assertEquals("Test data sanity", marks.size(), 3); + assertEquals("Test data sanity", marks.size(), expected_marks); + return marks; + } + + public void testSimpleFunction() { + Map marks = loadTest(3); PsiElement arg1 = marks.get(""); feignCtrlP(arg1.getTextOffset()).check("a, b, c", new String[]{"a, "}); @@ -54,8 +59,7 @@ public class PyParameterInfoTest extends LightMarkedTestCase { } public void testStarredFunction() { - Map marks = loadTest(); - assertEquals("Test data sanity", marks.size(), 4); + Map marks = loadTest(4); PsiElement arg1 = marks.get(""); feignCtrlP(arg1.getTextOffset()).check("a, b, *c", new String[]{"a, "}); @@ -76,8 +80,7 @@ public class PyParameterInfoTest extends LightMarkedTestCase { } public void testKwdFunction() { - Map marks = loadTest(); - assertEquals("Test data sanity", marks.size(), 4); + Map marks = loadTest(4); PsiElement arg1 = marks.get(""); feignCtrlP(arg1.getTextOffset()).check("a, b, **c", new String[]{"a, "}); @@ -98,8 +101,7 @@ public class PyParameterInfoTest extends LightMarkedTestCase { } public void testKwdOutOfOrder() { - Map marks = loadTest(); - assertEquals("Test data sanity", marks.size(), 4); + Map marks = loadTest(4); feignCtrlP(marks.get("").getTextOffset()).check("a, b, **c", new String[]{"**c"}); @@ -111,8 +113,7 @@ public class PyParameterInfoTest extends LightMarkedTestCase { } public void testStarArg() { - Map marks = loadTest(); - assertEquals("Test data sanity", marks.size(), 3); + Map marks = loadTest(3); feignCtrlP(marks.get("").getTextOffset()).check("a, b, c", new String[]{"a, "}); feignCtrlP(marks.get("").getTextOffset()).check("a, b, c", new String[]{"b, ","c"}); @@ -120,8 +121,7 @@ public class PyParameterInfoTest extends LightMarkedTestCase { } public void testKwdArg() { - Map marks = loadTest(); - assertEquals("Test data sanity", marks.size(), 3); + Map marks = loadTest(3); feignCtrlP(marks.get("").getTextOffset()).check("a, b, c", new String[]{"a, "}); feignCtrlP(marks.get("").getTextOffset()).check("a, b, c", new String[]{"b, ","c"}); @@ -129,8 +129,7 @@ public class PyParameterInfoTest extends LightMarkedTestCase { } public void testKwdArgOutOfOrder() { - Map marks = loadTest(); - assertEquals("Test data sanity", marks.size(), 3); + Map marks = loadTest(3); feignCtrlP(marks.get("").getTextOffset()).check("a, b, c", new String[]{"b, "}); feignCtrlP(marks.get("").getTextOffset()).check("a, b, c", new String[]{"a, ","c"}); @@ -138,8 +137,7 @@ public class PyParameterInfoTest extends LightMarkedTestCase { } public void testStarredAndKwdFunction() { - Map marks = loadTest(); - assertEquals("Test data sanity", marks.size(), 6); + Map marks = loadTest(6); feignCtrlP(marks.get("").getTextOffset()).check("a, b, *c, **d", new String[]{"a, "}); feignCtrlP(marks.get("").getTextOffset()).check("a, b, *c, **d", new String[]{"b, "}); @@ -150,8 +148,7 @@ public class PyParameterInfoTest extends LightMarkedTestCase { } public void testNestedArg() { - Map marks = loadTest(); - assertEquals("Test data sanity", marks.size(), 4); + Map marks = loadTest(4); feignCtrlP(marks.get("").getTextOffset()).check("a, (b, c), d", new String[]{"a, "}); feignCtrlP(marks.get("").getTextOffset()).check("a, (b, c), d", new String[]{"b, "}); @@ -162,8 +159,7 @@ public class PyParameterInfoTest extends LightMarkedTestCase { } public void testDoubleNestedArg() { - Map marks = loadTest(); - assertEquals("Test data sanity", marks.size(), 5); + Map marks = loadTest(5); feignCtrlP(marks.get("").getTextOffset()).check("a, (b, (c, d)), e", new String[]{"a, "}); feignCtrlP(marks.get("").getTextOffset()).check("a, (b, (c, d)), e", new String[]{"b, "}); @@ -173,8 +169,7 @@ public class PyParameterInfoTest extends LightMarkedTestCase { } public void testNestedMultiArg() { - Map marks = loadTest(); - assertEquals("Test data sanity", marks.size(), 3); + Map marks = loadTest(3); feignCtrlP(marks.get("").getTextOffset()).check("a, (b, c), d", new String[]{"a, "}); feignCtrlP(marks.get("").getTextOffset()).check("a, (b, c), d", new String[]{"b, ","c"}); @@ -182,16 +177,14 @@ public class PyParameterInfoTest extends LightMarkedTestCase { } public void testStarredParam() { - Map marks = loadTest(); - assertEquals("Test data sanity", marks.size(), 2); + Map marks = loadTest(2); feignCtrlP(marks.get("").getTextOffset()).check("a, b, c", new String[]{"a, "}); feignCtrlP(marks.get("").getTextOffset()).check("a, b, c", new String[]{"b, ","c"}); } public void testStarredParamAndArg() { - Map marks = loadTest(); - assertEquals("Test data sanity", marks.size(), 4); + Map marks = loadTest(4); feignCtrlP(marks.get("").getTextOffset()).check("a, b, *c", new String[]{"a, "}); feignCtrlP(marks.get("").getTextOffset()).check("a, b, *c", new String[]{"b, "}); @@ -201,31 +194,27 @@ public class PyParameterInfoTest extends LightMarkedTestCase { public void testSimpleMethod() { - Map marks = loadTest(); - assertEquals("Test data sanity", marks.size(), 1); + Map marks = loadTest(1); feignCtrlP(marks.get("").getTextOffset()).check("self, a", new String[]{"a"}, new String[]{"self, "}); } public void testSimpleClassFunction() { - Map marks = loadTest(); - assertEquals("Test data sanity", marks.size(), 2); + Map marks = loadTest(2); feignCtrlP(marks.get("").getTextOffset()).check("self, a", new String[]{"self, "}); feignCtrlP(marks.get("").getTextOffset()).check("self, a", new String[]{"a"}); } public void testReassignedFunction() { - Map marks = loadTest(); - assertEquals("Test data sanity", marks.size(), 2); + Map marks = loadTest(2); feignCtrlP(marks.get("").getTextOffset()).check("a, b", new String[]{"a, "}); feignCtrlP(marks.get("").getTextOffset()).check("a, b", new String[]{"b"}); } public void testReassignedInstanceMethod() { - Map marks = loadTest(); - assertEquals("Test data sanity", marks.size(), 3); + Map marks = loadTest(3); feignCtrlP(marks.get("").getTextOffset()).check("self, a, b, c", new String[]{"a, "}, new String[]{"self, "}); feignCtrlP(marks.get("").getTextOffset()).check("self, a, b, c", new String[]{"b, "}, new String[]{"self, "}); @@ -233,32 +222,28 @@ public class PyParameterInfoTest extends LightMarkedTestCase { } public void testReassignedClassInit() { - Map marks = loadTest(); - assertEquals("Test data sanity", marks.size(), 2); + Map marks = loadTest(2); feignCtrlP(marks.get("").getTextOffset()).check("self, a, b", new String[]{"a, "}, new String[]{"self, "}); feignCtrlP(marks.get("").getTextOffset()).check("self, a, b", new String[]{"b"}, new String[]{"self, "}); } public void testInheritedClassInit() { - Map marks = loadTest(); - assertEquals("Test data sanity", marks.size(), 2); + Map marks = loadTest(2); feignCtrlP(marks.get("").getTextOffset()).check("self, a, b", new String[]{"a, "}, new String[]{"self, "}); feignCtrlP(marks.get("").getTextOffset()).check("self, a, b", new String[]{"b"}, new String[]{"self, "}); } public void testRedefinedNewConstructorCall() { - Map marks = loadTest(); - assertEquals("Test data sanity", marks.size(), 2); + Map marks = loadTest(2); feignCtrlP(marks.get("").getTextOffset()).check("cls, a, b", new String[]{"a, "}, new String[]{"cls, "}); feignCtrlP(marks.get("").getTextOffset()).check("cls, a, b", new String[]{"b"}, new String[]{"cls, "}); } public void testRedefinedNewDirectCall() { - Map marks = loadTest(); - assertEquals("Test data sanity", marks.size(), 3); + Map marks = loadTest(3); feignCtrlP(marks.get("").getTextOffset()).check("cls, a, b", new String[]{"cls, "}); feignCtrlP(marks.get("").getTextOffset()).check("cls, a, b", new String[]{"a, "}); @@ -266,63 +251,55 @@ public class PyParameterInfoTest extends LightMarkedTestCase { } public void testIgnoreNewInOldStyleClass() { - Map marks = loadTest(); - assertEquals("Test data sanity", marks.size(), 1); + Map marks = loadTest(1); feignCtrlP(marks.get("").getTextOffset()).check("self, one", new String[]{"one"}, new String[]{"self, "}); } public void testBoundMethodSimple() { - Map marks = loadTest(); - assertEquals("Test data sanity", marks.size(), 2); + Map marks = loadTest(2); feignCtrlP(marks.get("").getTextOffset()).check("self, a, b", new String[]{"a, "}, new String[]{"self, "}); feignCtrlP(marks.get("").getTextOffset()).check("self, a, b", new String[]{"b"}, new String[]{"self, "}); } public void testBoundMethodReassigned() { - Map marks = loadTest(); - assertEquals("Test data sanity", marks.size(), 2); + Map marks = loadTest(2); feignCtrlP(marks.get("").getTextOffset()).check("self, a, b", new String[]{"a, "}, new String[]{"self, "}); feignCtrlP(marks.get("").getTextOffset()).check("self, a, b", new String[]{"b"}, new String[]{"self, "}); } public void testConstructorFactory() { - Map marks = loadTest(); - assertEquals("Test data sanity", marks.size(), 1); + Map marks = loadTest(1); feignCtrlP(marks.get("").getTextOffset()).check("self, color", new String[]{"color"}, new String[]{"self, "}); } public void testBoundMethodStatic() { - Map marks = loadTest(); - assertEquals("Test data sanity", marks.size(), 2); + Map marks = loadTest(2); feignCtrlP(marks.get("").getTextOffset()).check("a, b", new String[]{"a, "}); feignCtrlP(marks.get("").getTextOffset()).check("a, b", new String[]{"b"}); } public void testSimpleLambda() { - Map marks = loadTest(); - assertEquals("Test data sanity", marks.size(), 1); + Map marks = loadTest(1); feignCtrlP(marks.get("").getTextOffset()).check("x", new String[]{"x"}); } public void testReassignedLambda() { - Map marks = loadTest(); - assertEquals("Test data sanity", marks.size(), 2); + Map marks = loadTest(2); feignCtrlP(marks.get("").getTextOffset()).check("x, y", new String[]{"x, "}); feignCtrlP(marks.get("").getTextOffset()).check("x, y", new String[]{"y"}); } public void testLambdaVariousArgs() { - Map marks = loadTest(); - assertEquals("Test data sanity", marks.size(), 4); + Map marks = loadTest(4); feignCtrlP(marks.get("").getTextOffset()).check("x, y=1, *args, **kwargs", new String[]{"x, "}); feignCtrlP(marks.get("").getTextOffset()).check("x, y=1, *args, **kwargs", new String[]{"y=1, "}); @@ -332,8 +309,7 @@ public class PyParameterInfoTest extends LightMarkedTestCase { public void testTupleAndNamedArg1() { // PY-1268 - Map marks = loadTest(); - assertEquals("Test data sanity", marks.size(), 2); + Map marks = loadTest(2); feignCtrlP(marks.get("").getTextOffset()).check("a, b, c", new String[]{"c"}); feignCtrlP(marks.get("").getTextOffset()).check("a, b, c", new String[]{"a, ", "b, "}); @@ -341,29 +317,45 @@ public class PyParameterInfoTest extends LightMarkedTestCase { public void testTupleAndNamedArg2() { // PY-1268 - Map marks = loadTest(); - assertEquals("Test data sanity", marks.size(), 2); + Map marks = loadTest(2); feignCtrlP(marks.get("").getTextOffset()).check("a, b, c", new String[]{"a, ", "b, "}); feignCtrlP(marks.get("").getTextOffset()).check("a, b, c", new String[]{"c"}); } public void testTupleArgPlainParam() { - Map marks = loadTest(); - assertEquals("Test data sanity", marks.size(), 1); + Map marks = loadTest(1); feignCtrlP(marks.get("").getTextOffset()).check("a, b, c", new String[]{"b, "}); } - public void testStaticmethod() { - Map marks = loadTest(); - assertEquals("Test data sanity", marks.size(), 2); + Map marks = loadTest(2); feignCtrlP(marks.get("").getTextOffset()).check("a, b", new String[]{"a, "}); feignCtrlP(marks.get("").getTextOffset()).check("a, b", new String[]{"b"}); } + public void testPartialSimple() { + Map marks = loadTest(1); + + feignCtrlP(marks.get("").getTextOffset()).check("a, b, c", new String[]{"c"}); + } + + public void testPartialWithList() { // PY-3383 + Map marks = loadTest(2); + + feignCtrlP(marks.get("").getTextOffset()).check("a, b, c, **kwargs", new String[]{"b, "}); + feignCtrlP(marks.get("").getTextOffset()).check("a, b, c, **kwargs", new String[]{"c, "}); + } + + public void testPartialNamed() { + Map marks = loadTest(2); + + feignCtrlP(marks.get("").getTextOffset()).check("a, b, c=1, d=2, e=3", new String[]{"d=2, "}); + feignCtrlP(marks.get("").getTextOffset()).check("a, b, c=1, d=2, e=3", new String[]{}); // no logical next + } + /** * Imitates pressing of Ctrl+P; fails if results are not as expected. * @param offset offset of 'cursor' where ^P is pressed.