diff --git a/python/src/META-INF/python-plugin-common.xml b/python/src/META-INF/python-plugin-common.xml
index 4d0e8d9a30c7..8f51fa96adb4 100644
--- a/python/src/META-INF/python-plugin-common.xml
+++ b/python/src/META-INF/python-plugin-common.xml
@@ -253,6 +253,7 @@
order="before completionAutoPopup"/>
+
@@ -276,6 +277,8 @@
+
diff --git a/python/src/com/jetbrains/python/psi/PyElementGenerator.java b/python/src/com/jetbrains/python/psi/PyElementGenerator.java
index 54c420b919d0..69a21d476235 100644
--- a/python/src/com/jetbrains/python/psi/PyElementGenerator.java
+++ b/python/src/com/jetbrains/python/psi/PyElementGenerator.java
@@ -41,7 +41,9 @@ public abstract class PyElementGenerator {
public abstract PyBinaryExpression createBinaryExpression(String s, PyExpression expr, PyExpression listLiteral);
- public abstract PyExpression createExpressionFromText(String text);
+ public abstract PyExpression createExpressionFromText(String text);
+
+ public abstract PyExpression createExpressionFromText(final LanguageLevel languageLevel, String text);
public abstract PsiElement insertItemIntoList(PyElement list, @Nullable PyExpression afterThis, PyExpression toInsert)
throws IncorrectOperationException;
diff --git a/python/src/com/jetbrains/python/psi/impl/PyElementGeneratorImpl.java b/python/src/com/jetbrains/python/psi/impl/PyElementGeneratorImpl.java
index 3f6cbfd96fb4..49b38a1badf6 100644
--- a/python/src/com/jetbrains/python/psi/impl/PyElementGeneratorImpl.java
+++ b/python/src/com/jetbrains/python/psi/impl/PyElementGeneratorImpl.java
@@ -191,7 +191,11 @@ public class PyElementGeneratorImpl extends PyElementGenerator {
}
public PyExpression createExpressionFromText(final String text) {
- final PsiFile dummyFile = createDummyFile(LanguageLevel.getDefault(), text);
+ return createExpressionFromText(LanguageLevel.getDefault(), text);
+ }
+
+ public PyExpression createExpressionFromText(final LanguageLevel languageLevel, final String text) {
+ final PsiFile dummyFile = createDummyFile(languageLevel, text);
final PyExpressionStatement expressionStatement = (PyExpressionStatement)dummyFile.getFirstChild();
return expressionStatement.getExpression();
}
diff --git a/python/src/com/jetbrains/python/psi/impl/PyKeywordArgumentImpl.java b/python/src/com/jetbrains/python/psi/impl/PyKeywordArgumentImpl.java
index 27d0ddf3f9be..91c14a15db2c 100644
--- a/python/src/com/jetbrains/python/psi/impl/PyKeywordArgumentImpl.java
+++ b/python/src/com/jetbrains/python/psi/impl/PyKeywordArgumentImpl.java
@@ -1,6 +1,7 @@
package com.jetbrains.python.psi.impl;
import com.intellij.lang.ASTNode;
+import com.intellij.psi.PsiReference;
import com.intellij.psi.util.PsiTreeUtil;
import com.jetbrains.python.PyTokenTypes;
import com.jetbrains.python.psi.PyExpression;
@@ -43,4 +44,13 @@ public class PyKeywordArgumentImpl extends PyElementImpl implements PyKeywordArg
final PyExpression e = getValueExpression();
return e != null ? e.getType(context) : null;
}
+
+ @Override
+ public PsiReference getReference() {
+ final ASTNode keywordNode = getKeywordNode();
+ if (keywordNode != null) {
+ return new PyKeywordArgumentReference(this, keywordNode.getTextRange().shiftRight(-getTextRange().getStartOffset()));
+ }
+ return null;
+ }
}
diff --git a/python/src/com/jetbrains/python/psi/impl/PyKeywordArgumentManipulator.java b/python/src/com/jetbrains/python/psi/impl/PyKeywordArgumentManipulator.java
new file mode 100644
index 000000000000..9dbc90cb0883
--- /dev/null
+++ b/python/src/com/jetbrains/python/psi/impl/PyKeywordArgumentManipulator.java
@@ -0,0 +1,29 @@
+package com.jetbrains.python.psi.impl;
+
+import com.intellij.lang.ASTNode;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.psi.AbstractElementManipulator;
+import com.intellij.util.IncorrectOperationException;
+import com.jetbrains.python.psi.LanguageLevel;
+import com.jetbrains.python.psi.PyCallExpression;
+import com.jetbrains.python.psi.PyElementGenerator;
+import com.jetbrains.python.psi.PyKeywordArgument;
+
+/**
+ * @author yole
+ */
+public class PyKeywordArgumentManipulator extends AbstractElementManipulator {
+ @Override
+ public PyKeywordArgument handleContentChange(PyKeywordArgument element, TextRange range, String newContent) throws IncorrectOperationException {
+ final ASTNode keywordNode = element.getKeywordNode();
+ if (keywordNode != null && keywordNode.getTextRange().shiftRight(-element.getTextRange().getStartOffset()).equals(range)) {
+ final LanguageLevel langLevel = LanguageLevel.forElement(element);
+ final PyElementGenerator generator = PyElementGenerator.getInstance(element.getProject());
+ final PyCallExpression callExpression = (PyCallExpression) generator.createExpressionFromText(langLevel, "foo(" + newContent + "=None)");
+ final PyKeywordArgument kwArg = callExpression.getArgumentList().getKeywordArgument(newContent);
+ element.getKeywordNode().getPsi().replace(kwArg.getKeywordNode().getPsi());
+ return element;
+ }
+ throw new IncorrectOperationException("unsupported manipulation on keyword argument");
+ }
+}
diff --git a/python/src/com/jetbrains/python/psi/impl/PyKeywordArgumentReference.java b/python/src/com/jetbrains/python/psi/impl/PyKeywordArgumentReference.java
new file mode 100644
index 000000000000..8da109b8affc
--- /dev/null
+++ b/python/src/com/jetbrains/python/psi/impl/PyKeywordArgumentReference.java
@@ -0,0 +1,68 @@
+package com.jetbrains.python.psi.impl;
+
+import com.intellij.openapi.util.TextRange;
+import com.intellij.psi.*;
+import com.intellij.psi.util.PsiTreeUtil;
+import com.jetbrains.python.psi.*;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author yole
+ */
+public class PyKeywordArgumentReference extends PsiReferenceBase.Poly {
+ public PyKeywordArgumentReference(@NotNull PyKeywordArgument element, TextRange textRange) {
+ super(element, textRange, true);
+ }
+
+ @NotNull
+ @Override
+ public ResolveResult[] multiResolve(boolean incompleteCode) {
+ PyCallExpression call = PsiTreeUtil.getParentOfType(myElement, PyCallExpression.class);
+ assert call != null;
+ final PyExpression callee = call.getCallee();
+ if (callee == null) return ResolveResult.EMPTY_ARRAY;
+ final PsiPolyVariantReference calleeReference = (PsiPolyVariantReference) callee.getReference();
+ if (calleeReference == null) return ResolveResult.EMPTY_ARRAY;
+ final ResolveResult[] calleeCandidates = calleeReference.multiResolve(incompleteCode);
+ List resultList = new ArrayList();
+ for (ResolveResult calleeCandidate : calleeCandidates) {
+ if (!calleeCandidate.isValidResult()) continue;
+ final PsiElement element = calleeCandidate.getElement();
+ if (element == null) continue;
+ final PyFunction calleeFunction = resolveToFunction(element);
+ if (calleeFunction != null) {
+ final PsiElement result = calleeFunction.getParameterList().getElementNamed(myElement.getKeyword());
+ if (result != null) {
+ resultList.add(new PsiElementResolveResult(result));
+ }
+ }
+ }
+ return resultList.toArray(new ResolveResult[resultList.size()]);
+ }
+
+ @Nullable
+ private static PyFunction resolveToFunction(PsiElement element) {
+ if (element instanceof PyFunction) {
+ return (PyFunction) element;
+ }
+ if (element instanceof PyTargetExpression) {
+ final PyExpression assignedValue = ((PyTargetExpression)element).findAssignedValue();
+ return resolveToFunction(assignedValue);
+ }
+ if (element instanceof PyReferenceExpression) {
+ final PsiElement resolveResult = ((PyReferenceExpression)element).getReference().resolve();
+ return resolveToFunction(resolveResult);
+ }
+ return null;
+ }
+
+ @NotNull
+ @Override
+ public Object[] getVariants() {
+ return new Object[0]; //To change body of implemented methods use File | Settings | File Templates.
+ }
+}
diff --git a/python/src/com/jetbrains/python/psi/search/PyKeywordArgumentSearchExecutor.java b/python/src/com/jetbrains/python/psi/search/PyKeywordArgumentSearchExecutor.java
new file mode 100644
index 000000000000..fba0d68c573b
--- /dev/null
+++ b/python/src/com/jetbrains/python/psi/search/PyKeywordArgumentSearchExecutor.java
@@ -0,0 +1,44 @@
+package com.jetbrains.python.psi.search;
+
+import com.intellij.openapi.application.QueryExecutorBase;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiReference;
+import com.intellij.psi.search.searches.ReferencesSearch;
+import com.intellij.psi.util.PsiTreeUtil;
+import com.intellij.util.Processor;
+import com.jetbrains.python.psi.*;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author yole
+ */
+public class PyKeywordArgumentSearchExecutor extends QueryExecutorBase {
+ @Override
+ public void processQuery(@NotNull ReferencesSearch.SearchParameters queryParameters, @NotNull final Processor consumer) {
+ final PsiElement element = queryParameters.getElementToSearch();
+ if (!(element instanceof PyNamedParameter)) {
+ return;
+ }
+ PyFunction owner = PsiTreeUtil.getParentOfType(element, PyFunction.class);
+ if (owner == null) {
+ return;
+ }
+ ReferencesSearch.search(owner, queryParameters.getScope()).forEach(new Processor() {
+ @Override
+ public boolean process(PsiReference reference) {
+ final PsiElement refElement = reference.getElement();
+ final PyCallExpression call = PsiTreeUtil.getParentOfType(refElement, PyCallExpression.class);
+ if (call != null && PsiTreeUtil.isAncestor(call.getCallee(), refElement, false)) {
+ final PyArgumentList argumentList = call.getArgumentList();
+ if (argumentList != null) {
+ final PyKeywordArgument keywordArgument = argumentList.getKeywordArgument(((PyNamedParameter)element).getName());
+ if (keywordArgument != null) {
+ return consumer.process(keywordArgument.getReference());
+ }
+ }
+ }
+ return true;
+ }
+ });
+ }
+}
diff --git a/python/src/com/jetbrains/python/refactoring/RefactoringProvider.java b/python/src/com/jetbrains/python/refactoring/RefactoringProvider.java
index 47bf49641deb..1d96a3126d58 100644
--- a/python/src/com/jetbrains/python/refactoring/RefactoringProvider.java
+++ b/python/src/com/jetbrains/python/refactoring/RefactoringProvider.java
@@ -6,7 +6,6 @@ import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.refactoring.RefactoringActionHandler;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyFunction;
-import com.jetbrains.python.psi.PyNamedParameter;
import com.jetbrains.python.psi.PyTargetExpression;
import com.jetbrains.python.refactoring.classes.extractSuperclass.PyExtractSuperclassHandler;
import com.jetbrains.python.refactoring.classes.pullUp.PyPullUpHandler;
@@ -62,9 +61,6 @@ public class RefactoringProvider extends RefactoringSupportProvider {
if (element instanceof PyTargetExpression || element instanceof PyFunction || element instanceof PyClass) {
return true;
}
- if (element instanceof PyNamedParameter) {
- return containingFunction.getContainingClass() == null;
- }
}
return false;
}
diff --git a/python/testData/refactoring/rename/renameKeywordArgument.py b/python/testData/refactoring/rename/renameKeywordArgument.py
new file mode 100644
index 000000000000..e063e60981b6
--- /dev/null
+++ b/python/testData/refactoring/rename/renameKeywordArgument.py
@@ -0,0 +1,4 @@
+def foo(bar=True):
+ pass
+
+foo(bar=False)
\ No newline at end of file
diff --git a/python/testData/refactoring/rename/renameKeywordArgument_after.py b/python/testData/refactoring/rename/renameKeywordArgument_after.py
new file mode 100644
index 000000000000..8cc1cc40a686
--- /dev/null
+++ b/python/testData/refactoring/rename/renameKeywordArgument_after.py
@@ -0,0 +1,4 @@
+def foo(baz=True):
+ pass
+
+foo(baz=False)
\ No newline at end of file
diff --git a/python/testData/resolve/KeywordArgument.py b/python/testData/resolve/KeywordArgument.py
new file mode 100644
index 000000000000..ee370714b131
--- /dev/null
+++ b/python/testData/resolve/KeywordArgument.py
@@ -0,0 +1,5 @@
+def foo(bar=True):
+ pass
+
+foo(bar=False)
+# [
diff --git a/python/testSrc/com/jetbrains/python/PyResolveTest.java b/python/testSrc/com/jetbrains/python/PyResolveTest.java
index e4a933e29aa4..5d5d0df7b060 100644
--- a/python/testSrc/com/jetbrains/python/PyResolveTest.java
+++ b/python/testSrc/com/jetbrains/python/PyResolveTest.java
@@ -417,4 +417,8 @@ public class PyResolveTest extends PyResolveTestCase {
public void testDontResolveTargetToBuiltins() { // PY-4256
assertResolvesTo(PyTargetExpression.class, "str");
}
+
+ public void testKeywordArgument() {
+ assertResolvesTo(PyNamedParameter.class, "bar");
+ }
}
\ No newline at end of file
diff --git a/python/testSrc/com/jetbrains/python/refactoring/PyRenameTest.java b/python/testSrc/com/jetbrains/python/refactoring/PyRenameTest.java
index 06abfec34864..3c92a6a60612 100644
--- a/python/testSrc/com/jetbrains/python/refactoring/PyRenameTest.java
+++ b/python/testSrc/com/jetbrains/python/refactoring/PyRenameTest.java
@@ -77,6 +77,10 @@ public class PyRenameTest extends PyLightFixtureTestCase {
public void testRenameSlots() { // PY-4195
doTest("bacon");
}
+
+ public void testRenameKeywordArgument() { // PY-3890
+ doTest("baz");
+ }
public void testClassNameConflict() { // PY-2390
doRenameConflictTest("Foo", "A class named 'Foo' is already defined in classNameConflict.py");
]