Better Ctrl+P handler: PY-3383 and more.

This commit is contained in:
Dmitry Cheryasov
2011-04-21 17:11:45 +03:00
parent 23398cf6a6
commit 26351e23a0
5 changed files with 127 additions and 113 deletions
@@ -100,9 +100,9 @@ public class PyParameterInfoHandler implements ParameterInfoHandler<PyArgumentLi
final Callable callable = marked.getCallable();
if (callable == null) return; // resolution failed
PyParameter[] raw_params = callable.getParameterList().getParameters();
final List<PyNamedParameter> n_param_list = new ArrayList<PyNamedParameter>(raw_params.length);
final List<String> hint_texts = new ArrayList<String>(raw_params.length);
final List<PyParameter> raw_params = Arrays.asList(callable.getParameterList().getParameters());
final List<PyNamedParameter> n_param_list = new ArrayList<PyNamedParameter>(raw_params.size());
final List<String> hint_texts = new ArrayList<String>(raw_params.size());
// param -> hint index. indexes are not contiguous, because some hints are parentheses.
final Map<PyNamedParameter, Integer> param_indexes = new HashMap<PyNamedParameter, Integer>();
@@ -153,50 +153,60 @@ public class PyParameterInfoHandler implements ParameterInfoHandler<PyArgumentLi
hint_flags.get(param_indexes.get(n_param_list.get(i))).add(ParameterInfoUIContextEx.Flag.DISABLE); // show but mark as absent
}
// highlight current param(s)
final List<PyExpression> 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<PyExpression> 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<PyNamedParameter> 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<PyNamedParameter> 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 ParameterInfoHandler<PyArgumentLi
// else: stay unhilited
}
// highlight the parameter to be filled
if (current_param_offset < 0 && args.size() < n_param_list.size() - marked.getImplicitOffset()) {
hint_flags.get(param_indexes.get(n_param_list.get(args.size() + marked.getImplicitOffset()))).add(ParameterInfoUIContextEx.Flag.HIGHLIGHT);
// highlight the next parameter to be filled
if (can_offer_next && last_param_index < raw_params.size()-1) {
hint_flags.get(param_indexes.get(n_param_list.get(last_param_index+1))).add(ParameterInfoUIContextEx.Flag.HIGHLIGHT);
}
final String NO_PARAMS_MSG = "<No parameters>";
@@ -0,0 +1,4 @@
def f(a, b, c=1, d=2, e=3):
pass
f(1, 2, <arg1>d=10, <arg2>)
@@ -0,0 +1,4 @@
def foo(a, b, c):
pass
foo(1, 2, <arg1>)
@@ -0,0 +1,4 @@
def f(a, b, c, **kwargs):
pass
f(a, <arg1>[1, 2, 3], <arg2>)
@@ -32,9 +32,14 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
return configureByFile(fname);
}
public void testSimpleFunction() {
protected Map<String, PsiElement> loadTest(int expected_marks) {
Map<String, PsiElement> marks = loadTest();
assertEquals("Test data sanity", marks.size(), 3);
assertEquals("Test data sanity", marks.size(), expected_marks);
return marks;
}
public void testSimpleFunction() {
Map<String, PsiElement> marks = loadTest(3);
PsiElement arg1 = marks.get("<arg1>");
feignCtrlP(arg1.getTextOffset()).check("a, b, c", new String[]{"a, "});
@@ -54,8 +59,7 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
}
public void testStarredFunction() {
Map<String, PsiElement> marks = loadTest();
assertEquals("Test data sanity", marks.size(), 4);
Map<String, PsiElement> marks = loadTest(4);
PsiElement arg1 = marks.get("<arg1>");
feignCtrlP(arg1.getTextOffset()).check("a, b, *c", new String[]{"a, "});
@@ -76,8 +80,7 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
}
public void testKwdFunction() {
Map<String, PsiElement> marks = loadTest();
assertEquals("Test data sanity", marks.size(), 4);
Map<String, PsiElement> marks = loadTest(4);
PsiElement arg1 = marks.get("<arg1>");
feignCtrlP(arg1.getTextOffset()).check("a, b, **c", new String[]{"a, "});
@@ -98,8 +101,7 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
}
public void testKwdOutOfOrder() {
Map<String, PsiElement> marks = loadTest();
assertEquals("Test data sanity", marks.size(), 4);
Map<String, PsiElement> marks = loadTest(4);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("a, b, **c", new String[]{"**c"});
@@ -111,8 +113,7 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
}
public void testStarArg() {
Map<String, PsiElement> marks = loadTest();
assertEquals("Test data sanity", marks.size(), 3);
Map<String, PsiElement> marks = loadTest(3);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("a, b, c", new String[]{"a, "});
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("a, b, c", new String[]{"b, ","c"});
@@ -120,8 +121,7 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
}
public void testKwdArg() {
Map<String, PsiElement> marks = loadTest();
assertEquals("Test data sanity", marks.size(), 3);
Map<String, PsiElement> marks = loadTest(3);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("a, b, c", new String[]{"a, "});
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("a, b, c", new String[]{"b, ","c"});
@@ -129,8 +129,7 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
}
public void testKwdArgOutOfOrder() {
Map<String, PsiElement> marks = loadTest();
assertEquals("Test data sanity", marks.size(), 3);
Map<String, PsiElement> marks = loadTest(3);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("a, b, c", new String[]{"b, "});
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("a, b, c", new String[]{"a, ","c"});
@@ -138,8 +137,7 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
}
public void testStarredAndKwdFunction() {
Map<String, PsiElement> marks = loadTest();
assertEquals("Test data sanity", marks.size(), 6);
Map<String, PsiElement> marks = loadTest(6);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("a, b, *c, **d", new String[]{"a, "});
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("a, b, *c, **d", new String[]{"b, "});
@@ -150,8 +148,7 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
}
public void testNestedArg() {
Map<String, PsiElement> marks = loadTest();
assertEquals("Test data sanity", marks.size(), 4);
Map<String, PsiElement> marks = loadTest(4);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("a, (b, c), d", new String[]{"a, "});
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("a, (b, c), d", new String[]{"b, "});
@@ -162,8 +159,7 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
}
public void testDoubleNestedArg() {
Map<String, PsiElement> marks = loadTest();
assertEquals("Test data sanity", marks.size(), 5);
Map<String, PsiElement> marks = loadTest(5);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("a, (b, (c, d)), e", new String[]{"a, "});
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("a, (b, (c, d)), e", new String[]{"b, "});
@@ -173,8 +169,7 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
}
public void testNestedMultiArg() {
Map<String, PsiElement> marks = loadTest();
assertEquals("Test data sanity", marks.size(), 3);
Map<String, PsiElement> marks = loadTest(3);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("a, (b, c), d", new String[]{"a, "});
feignCtrlP(marks.get("<arg23>").getTextOffset()).check("a, (b, c), d", new String[]{"b, ","c"});
@@ -182,16 +177,14 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
}
public void testStarredParam() {
Map<String, PsiElement> marks = loadTest();
assertEquals("Test data sanity", marks.size(), 2);
Map<String, PsiElement> marks = loadTest(2);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("a, b, c", new String[]{"a, "});
feignCtrlP(marks.get("<arg23>").getTextOffset()).check("a, b, c", new String[]{"b, ","c"});
}
public void testStarredParamAndArg() {
Map<String, PsiElement> marks = loadTest();
assertEquals("Test data sanity", marks.size(), 4);
Map<String, PsiElement> marks = loadTest(4);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("a, b, *c", new String[]{"a, "});
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("a, b, *c", new String[]{"b, "});
@@ -201,31 +194,27 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
public void testSimpleMethod() {
Map<String, PsiElement> marks = loadTest();
assertEquals("Test data sanity", marks.size(), 1);
Map<String, PsiElement> marks = loadTest(1);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("self, a", new String[]{"a"}, new String[]{"self, "});
}
public void testSimpleClassFunction() {
Map<String, PsiElement> marks = loadTest();
assertEquals("Test data sanity", marks.size(), 2);
Map<String, PsiElement> marks = loadTest(2);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("self, a", new String[]{"self, "});
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("self, a", new String[]{"a"});
}
public void testReassignedFunction() {
Map<String, PsiElement> marks = loadTest();
assertEquals("Test data sanity", marks.size(), 2);
Map<String, PsiElement> marks = loadTest(2);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("a, b", new String[]{"a, "});
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("a, b", new String[]{"b"});
}
public void testReassignedInstanceMethod() {
Map<String, PsiElement> marks = loadTest();
assertEquals("Test data sanity", marks.size(), 3);
Map<String, PsiElement> marks = loadTest(3);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("self, a, b, c", new String[]{"a, "}, new String[]{"self, "});
feignCtrlP(marks.get("<arg2>").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<String, PsiElement> marks = loadTest();
assertEquals("Test data sanity", marks.size(), 2);
Map<String, PsiElement> marks = loadTest(2);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("self, a, b", new String[]{"a, "}, new String[]{"self, "});
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("self, a, b", new String[]{"b"}, new String[]{"self, "});
}
public void testInheritedClassInit() {
Map<String, PsiElement> marks = loadTest();
assertEquals("Test data sanity", marks.size(), 2);
Map<String, PsiElement> marks = loadTest(2);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("self, a, b", new String[]{"a, "}, new String[]{"self, "});
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("self, a, b", new String[]{"b"}, new String[]{"self, "});
}
public void testRedefinedNewConstructorCall() {
Map<String, PsiElement> marks = loadTest();
assertEquals("Test data sanity", marks.size(), 2);
Map<String, PsiElement> marks = loadTest(2);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("cls, a, b", new String[]{"a, "}, new String[]{"cls, "});
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("cls, a, b", new String[]{"b"}, new String[]{"cls, "});
}
public void testRedefinedNewDirectCall() {
Map<String, PsiElement> marks = loadTest();
assertEquals("Test data sanity", marks.size(), 3);
Map<String, PsiElement> marks = loadTest(3);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("cls, a, b", new String[]{"cls, "});
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("cls, a, b", new String[]{"a, "});
@@ -266,63 +251,55 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
}
public void testIgnoreNewInOldStyleClass() {
Map<String, PsiElement> marks = loadTest();
assertEquals("Test data sanity", marks.size(), 1);
Map<String, PsiElement> marks = loadTest(1);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("self, one", new String[]{"one"}, new String[]{"self, "});
}
public void testBoundMethodSimple() {
Map<String, PsiElement> marks = loadTest();
assertEquals("Test data sanity", marks.size(), 2);
Map<String, PsiElement> marks = loadTest(2);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("self, a, b", new String[]{"a, "}, new String[]{"self, "});
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("self, a, b", new String[]{"b"}, new String[]{"self, "});
}
public void testBoundMethodReassigned() {
Map<String, PsiElement> marks = loadTest();
assertEquals("Test data sanity", marks.size(), 2);
Map<String, PsiElement> marks = loadTest(2);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("self, a, b", new String[]{"a, "}, new String[]{"self, "});
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("self, a, b", new String[]{"b"}, new String[]{"self, "});
}
public void testConstructorFactory() {
Map<String, PsiElement> marks = loadTest();
assertEquals("Test data sanity", marks.size(), 1);
Map<String, PsiElement> marks = loadTest(1);
feignCtrlP(marks.get("<arg>").getTextOffset()).check("self, color", new String[]{"color"}, new String[]{"self, "});
}
public void testBoundMethodStatic() {
Map<String, PsiElement> marks = loadTest();
assertEquals("Test data sanity", marks.size(), 2);
Map<String, PsiElement> marks = loadTest(2);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("a, b", new String[]{"a, "});
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("a, b", new String[]{"b"});
}
public void testSimpleLambda() {
Map<String, PsiElement> marks = loadTest();
assertEquals("Test data sanity", marks.size(), 1);
Map<String, PsiElement> marks = loadTest(1);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("x", new String[]{"x"});
}
public void testReassignedLambda() {
Map<String, PsiElement> marks = loadTest();
assertEquals("Test data sanity", marks.size(), 2);
Map<String, PsiElement> marks = loadTest(2);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("x, y", new String[]{"x, "});
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("x, y", new String[]{"y"});
}
public void testLambdaVariousArgs() {
Map<String, PsiElement> marks = loadTest();
assertEquals("Test data sanity", marks.size(), 4);
Map<String, PsiElement> marks = loadTest(4);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("x, y=1, *args, **kwargs", new String[]{"x, "});
feignCtrlP(marks.get("<arg2>").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<String, PsiElement> marks = loadTest();
assertEquals("Test data sanity", marks.size(), 2);
Map<String, PsiElement> marks = loadTest(2);
feignCtrlP(marks.get("<arg_c>").getTextOffset()).check("a, b, c", new String[]{"c"});
feignCtrlP(marks.get("<arg_star>").getTextOffset()).check("a, b, c", new String[]{"a, ", "b, "});
@@ -341,29 +317,45 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
public void testTupleAndNamedArg2() {
// PY-1268
Map<String, PsiElement> marks = loadTest();
assertEquals("Test data sanity", marks.size(), 2);
Map<String, PsiElement> marks = loadTest(2);
feignCtrlP(marks.get("<arg_star>").getTextOffset()).check("a, b, c", new String[]{"a, ", "b, "});
feignCtrlP(marks.get("<arg_c>").getTextOffset()).check("a, b, c", new String[]{"c"});
}
public void testTupleArgPlainParam() {
Map<String, PsiElement> marks = loadTest();
assertEquals("Test data sanity", marks.size(), 1);
Map<String, PsiElement> marks = loadTest(1);
feignCtrlP(marks.get("<arg>").getTextOffset()).check("a, b, c", new String[]{"b, "});
}
public void testStaticmethod() {
Map<String, PsiElement> marks = loadTest();
assertEquals("Test data sanity", marks.size(), 2);
Map<String, PsiElement> marks = loadTest(2);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("a, b", new String[]{"a, "});
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("a, b", new String[]{"b"});
}
public void testPartialSimple() {
Map<String, PsiElement> marks = loadTest(1);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("a, b, c", new String[]{"c"});
}
public void testPartialWithList() { // PY-3383
Map<String, PsiElement> marks = loadTest(2);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("a, b, c, **kwargs", new String[]{"b, "});
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("a, b, c, **kwargs", new String[]{"c, "});
}
public void testPartialNamed() {
Map<String, PsiElement> marks = loadTest(2);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("a, b, c=1, d=2, e=3", new String[]{"d=2, "});
feignCtrlP(marks.get("<arg2>").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.