diff --git a/python/src/META-INF/python-plugin-common.xml b/python/src/META-INF/python-plugin-common.xml
index 82301fd97120..7b759d20e299 100644
--- a/python/src/META-INF/python-plugin-common.xml
+++ b/python/src/META-INF/python-plugin-common.xml
@@ -53,8 +53,6 @@
-
diff --git a/python/src/com/jetbrains/python/PythonDosStringFinder.java b/python/src/com/jetbrains/python/PythonDocStringFinder.java
similarity index 93%
rename from python/src/com/jetbrains/python/PythonDosStringFinder.java
rename to python/src/com/jetbrains/python/PythonDocStringFinder.java
index 6d3d03f4e780..f9a25a7cadfb 100644
--- a/python/src/com/jetbrains/python/PythonDosStringFinder.java
+++ b/python/src/com/jetbrains/python/PythonDocStringFinder.java
@@ -13,8 +13,8 @@ import org.jetbrains.annotations.Nullable;
* User: dcheryasov
* Date: Jun 7, 2009 5:06:12 AM
*/
-public class PythonDosStringFinder {
- private PythonDosStringFinder() {}
+public class PythonDocStringFinder {
+ private PythonDocStringFinder() {}
/**
* Looks for a doc string under given parent.
diff --git a/python/src/com/jetbrains/python/PythonDocumentationProvider.java b/python/src/com/jetbrains/python/PythonDocumentationProvider.java
index cb26042cd1cc..0681154a8c86 100644
--- a/python/src/com/jetbrains/python/PythonDocumentationProvider.java
+++ b/python/src/com/jetbrains/python/PythonDocumentationProvider.java
@@ -166,7 +166,43 @@ public class PythonDocumentationProvider extends QuickDocumentationProvider {
cat.add(prolog_cat).addWith(TagCode, doc_cat).add(epilog_cat); // pre-assemble; then add stuff to individual cats as needed
cat = wrapInTag("html", wrapInTag("body", cat));
+ element = resolveToDocStringOwner(element, originalElement, prolog_cat);
+ // now element may contain a doc string
+ if (element instanceof PyDocStringOwner) {
+ String docString = null;
+ PyStringLiteralExpression doc_expr = ((PyDocStringOwner) element).getDocStringExpression();
+ if (doc_expr != null) docString = doc_expr.getStringValue();
+ // doc of what?
+ if (element instanceof PyClass) {
+ PyClass cls = (PyClass)element;
+ doc_cat.addWith(TagSmall, describeClass(cls, TagBold));
+ }
+ else if (element instanceof PyFunction) {
+ PyFunction fun = (PyFunction)element;
+ PyClass cls = fun.getContainingClass();
+ if (cls != null) doc_cat.addWith(TagSmall, $("class ", cls.getName(), BR));
+ doc_cat.add(describeFunction(fun, TagItalic, BR, TagBold, LCombUp));
+ if (docString == null) {
+ addInheritedDocString(fun, cls, doc_cat, epilog_cat);
+ }
+ }
+ else if (element instanceof PyFile) {
+ // what to prepend to a module description??
+ }
+ else { // not a func, not a class
+ doc_cat.add(combUp(PyUtil.getReadableRepr(element, false)));
+ }
+ if (docString != null) {
+ doc_cat.add(BR).add(combUpDocString(docString));
+ }
+ else if (prolog_cat.isEmpty() && doc_cat.isEmpty() && epilog_cat.isEmpty()) return null; // got nothing to say!
+ return cat.toString();
+ }
+ return null;
+ }
+
+ private static PsiElement resolveToDocStringOwner(PsiElement element, PsiElement originalElement, ChainIterable prolog_cat) {
// here the ^Q target is already resolved; the resolved element may point to intermediate assignments
boolean reassignment_marked = false;
if (element instanceof PyTargetExpression) {
@@ -199,84 +235,66 @@ public class PythonDocumentationProvider extends QuickDocumentationProvider {
}
}
- // now element may contain a doc string
- if (element instanceof PyDocStringOwner) {
- String docString = null;
- PyStringLiteralExpression doc_expr = ((PyDocStringOwner) element).getDocStringExpression();
- if (doc_expr != null) docString = doc_expr.getStringValue();
- // doc of what?
- if (element instanceof PyClass) {
- PyClass cls = (PyClass)element;
- doc_cat.addWith(TagSmall, describeClass(cls, TagBold));
+ if (element instanceof PyFunction && PyNames.INIT.equals(((PyFunction)element).getName())) {
+ final PyStringLiteralExpression expression = ((PyFunction)element).getDocStringExpression();
+ if (expression == null) {
+ PyClass containingClass = ((PyFunction) element).getContainingClass();
+ if (containingClass != null) {
+ element = containingClass;
+ }
}
- else if (element instanceof PyFunction) {
- PyFunction fun = (PyFunction)element;
- PyClass cls = fun.getContainingClass();
- if (cls != null) doc_cat.addWith(TagSmall, $("class ", cls.getName(), BR));
- doc_cat.add(describeFunction(fun, TagItalic, BR, TagBold, LCombUp));
- boolean not_found = true;
- if (docString == null) {
- String meth_name = fun.getName();
- if (cls != null && meth_name != null ) {
- // look for inherited and its doc
- for (PyClass ancestor : cls.iterateAncestors()) {
- PyFunction inherited = ancestor.findMethodByName(meth_name, false);
- if (inherited != null) {
- PyStringLiteralExpression doc_elt = inherited.getDocStringExpression();
- if (doc_elt != null) {
- String inherited_doc = doc_elt.getStringValue();
- if (inherited_doc.length() > 1) {
- epilog_cat
- .add(BR).add(BR)
- .add(PyBundle.message("QDOC.copied.from.$0.$1", ancestor.getName(), meth_name))
- .add(BR).add(BR)
- .addWith(TagCode, $(inherited_doc))
- ;
- not_found = false;
- break;
- }
- }
- }
- }
+ }
+ return element;
+ }
- if (not_found) {
- // above could have not worked because inheritance is not searched down to 'object'.
- // for well-known methods, copy built-in doc string.
- // TODO: also handle predefined __xxx__ that are not part of 'object'.
- if (PyNames.UnderscoredAttributes.contains(meth_name)) {
- PyClassType objtype = PyBuiltinCache.getInstance(fun).getObjectType(); // old- and new-style classes share the __xxx__ stuff
- if (objtype != null) {
- PyClass objcls = objtype.getPyClass();
- if (objcls != null) {
- PyFunction obj_underscored = objcls.findMethodByName(meth_name, false);
- if (obj_underscored != null) {
- PyStringLiteralExpression predefined_doc_expr = obj_underscored.getDocStringExpression();
- String predefined_doc = predefined_doc_expr != null? predefined_doc_expr.getStringValue() : null;
- if (predefined_doc != null && predefined_doc.length() > 1) { // only a real-looking doc string counts
- doc_cat.add(predefined_doc);
- epilog_cat.add(BR).add(BR).add(PyBundle.message("QDOC.copied.from.builtin"));
- }
- }
- }
+ private static void addInheritedDocString(PyFunction fun, PyClass cls, ChainIterable doc_cat, ChainIterable epilog_cat) {
+ boolean not_found = true;
+ String meth_name = fun.getName();
+ if (cls != null && meth_name != null ) {
+ // look for inherited and its doc
+ for (PyClass ancestor : cls.iterateAncestors()) {
+ PyFunction inherited = ancestor.findMethodByName(meth_name, false);
+ if (inherited != null) {
+ PyStringLiteralExpression doc_elt = inherited.getDocStringExpression();
+ if (doc_elt != null) {
+ String inherited_doc = doc_elt.getStringValue();
+ if (inherited_doc.length() > 1) {
+ epilog_cat
+ .add(BR).add(BR)
+ .add(PyBundle.message("QDOC.copied.from.$0.$1", ancestor.getName(), meth_name))
+ .add(BR).add(BR)
+ .addWith(TagCode, $(inherited_doc))
+ ;
+ not_found = false;
+ break;
+ }
+ }
+ }
+ }
+
+ if (not_found) {
+ // above could have not worked because inheritance is not searched down to 'object'.
+ // for well-known methods, copy built-in doc string.
+ // TODO: also handle predefined __xxx__ that are not part of 'object'.
+ if (PyNames.UnderscoredAttributes.contains(meth_name)) {
+ PyClassType objtype = PyBuiltinCache.getInstance(fun).getObjectType(); // old- and new-style classes share the __xxx__ stuff
+ if (objtype != null) {
+ PyClass objcls = objtype.getPyClass();
+ if (objcls != null) {
+ PyFunction obj_underscored = objcls.findMethodByName(meth_name, false);
+ if (obj_underscored != null) {
+ PyStringLiteralExpression predefined_doc_expr = obj_underscored.getDocStringExpression();
+ String predefined_doc = predefined_doc_expr != null? predefined_doc_expr.getStringValue() : null;
+ if (predefined_doc != null && predefined_doc.length() > 1) { // only a real-looking doc string counts
+ doc_cat.add(predefined_doc);
+ epilog_cat.add(BR).add(BR).add(PyBundle.message("QDOC.copied.from.builtin"));
}
}
}
}
}
}
- else if (element instanceof PyFile) {
- // what to prepend to a module description??
- }
- else { // not a func, not a class
- doc_cat.add(combUp(PyUtil.getReadableRepr(element, false)));
- }
- if (docString != null) {
- doc_cat.add(BR).add(combUpDocString(docString));
- }
- else if (prolog_cat.isEmpty() && doc_cat.isEmpty() && epilog_cat.isEmpty()) return null; // got nothing to say!
- return cat.toString();
}
- return null;
}
private static FP.Lambda1 LCombUp = new FP.Lambda1() {
@@ -332,22 +350,6 @@ public class PythonDocumentationProvider extends QuickDocumentationProvider {
}
};
- /*
- private static StringBuilder join(String delimiter, Iterable list, StringBuilder cat) {
- boolean is_next = false;
- for (Object item : list) {
- if (is_next) cat.append(delimiter);
- else is_next = true;
- cat.append(item.toString());
- }
- return cat;
- }
-
- private static String join(String delimiter, List list) {
- return join(delimiter, list, new StringBuilder()).toString();
- }
- */
-
private static Iterable interleave(Iterable source, T filler) {
List ret = new LinkedList();
boolean is_next = false;
diff --git a/python/src/com/jetbrains/python/actions/AddFieldQuickFix.java b/python/src/com/jetbrains/python/actions/AddFieldQuickFix.java
index 4355e2de8689..e54eeba3c4dc 100644
--- a/python/src/com/jetbrains/python/actions/AddFieldQuickFix.java
+++ b/python/src/com/jetbrains/python/actions/AddFieldQuickFix.java
@@ -7,6 +7,7 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.MessageType;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiWhiteSpace;
+import com.intellij.util.Function;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.PythonLanguage;
@@ -39,7 +40,7 @@ public class AddFieldQuickFix implements LocalQuickFix {
return PyBundle.message("INSP.GROUP.python");
}
- private static PsiElement appendToInit(PyFunction init, String item_name, PyElementGenerator generator, Project project) {
+ private static PsiElement appendToInit(PyFunction init, Function callback) {
// add this field as the last stmt of the constructor
final PyStatementList stmt_list = init.getStatementList();
PyStatement[] stmts = stmt_list.getStatements(); // NOTE: rather wasteful, consider iterable stmt list
@@ -51,7 +52,7 @@ public class AddFieldQuickFix implements LocalQuickFix {
if (params.length > 0) {
self_name = params[0].getName();
}
- PyStatement new_stmt = generator.createFromText(project, PyStatement.class, self_name + "." +item_name + " = None");
+ PyStatement new_stmt = callback.fun(self_name);
PyUtil.ensureWritable(stmt_list);
return stmt_list.addAfter(new_stmt, last_stmt);
}
@@ -60,15 +61,24 @@ public class AddFieldQuickFix implements LocalQuickFix {
// expect the descriptor to point to the unresolved identifier.
PyClass cls = myQualifierClass;
String item_name = myIdentifier;
+ if (cls != null) {
+ if (addFieldToInit(project, cls, item_name, new CreateFieldCallback(project, item_name))) return;
+ }
+ // somehow we failed. tell about this
+ PyUtil.showBalloon(project, PyBundle.message("QFIX.failed.to.add.field"), MessageType.ERROR);
+ }
+
+ public static boolean addFieldToInit(Project project, PyClass cls, String item_name, FieldCallback callback) {
if (cls != null && item_name != null) {
PyFunction init = cls.findMethodByName(PyNames.INIT, false);
Language language = cls.getLanguage();
if (language instanceof PythonLanguage) {
PythonLanguage pythonLanguage = (PythonLanguage)language;
PyElementGenerator generator = pythonLanguage.getElementGenerator();
+ callback.setGenerator(generator);
if (init != null) {
- appendToInit(init, item_name, generator, project);
- return;
+ appendToInit(init, callback);
+ return true;
}
else { // no init! boldly copy ancestor's.
for (PyClass ancestor : cls.iterateAncestors()) {
@@ -77,7 +87,7 @@ public class AddFieldQuickFix implements LocalQuickFix {
}
PyFunction new_init = createInitMethod(project, cls, init, generator);
- appendToInit(new_init, item_name, generator, project);
+ appendToInit(new_init, callback);
new_init.add(generator.createFromText(project, PsiWhiteSpace.class, "\n\n")); // after the last line
PsiElement add_anchor = null;
@@ -87,13 +97,12 @@ public class AddFieldQuickFix implements LocalQuickFix {
cls_content.addAfter(new_init, add_anchor);
PyUtil.showBalloon(project, PyBundle.message("QFIX.added.constructor.$0.for.field.$1", cls.getName(), item_name), MessageType.INFO);
- return;
+ return true;
//else // well, that can't be
}
}
}
- // somehow we failed. tell about this
- PyUtil.showBalloon(project, PyBundle.message("QFIX.failed.to.add.field"), MessageType.ERROR);
+ return false;
}
private static PyFunction createInitMethod(Project project, PyClass cls, @Nullable PyFunction ancestorInit, PyElementGenerator generator) {
@@ -125,4 +134,27 @@ public class AddFieldQuickFix implements LocalQuickFix {
}
return new_init;
}
+
+ public abstract static class FieldCallback implements Function {
+ protected PyElementGenerator myGenerator;
+
+ public void setGenerator(PyElementGenerator generator) {
+ myGenerator = generator;
+ }
+ }
+
+ private static class CreateFieldCallback extends FieldCallback {
+ private Project myProject;
+ private String myItemName;
+
+
+ private CreateFieldCallback(Project project, String itemName) {
+ myProject = project;
+ myItemName = itemName;
+ }
+
+ public PyStatement fun(String self_name) {
+ return myGenerator.createFromText(myProject, PyStatement.class, self_name + "." + myItemName + " = None");
+ }
+ }
}
diff --git a/python/src/com/jetbrains/python/actions/AddImportHelper.java b/python/src/com/jetbrains/python/actions/AddImportHelper.java
index e85caf071f5d..0b149cc83127 100644
--- a/python/src/com/jetbrains/python/actions/AddImportHelper.java
+++ b/python/src/com/jetbrains/python/actions/AddImportHelper.java
@@ -7,7 +7,7 @@ import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiWhiteSpace;
import com.intellij.util.IncorrectOperationException;
-import com.jetbrains.python.PythonDosStringFinder;
+import com.jetbrains.python.PythonDocStringFinder;
import com.jetbrains.python.PythonLanguage;
import com.jetbrains.python.psi.*;
import org.jetbrains.annotations.Nullable;
@@ -40,7 +40,7 @@ class AddImportHelper {
}
// maybe we arrived at the doc comment stmt; skip over it, too
else if (!skipped_over_imports && ! skipped_over_doc && file instanceof PyFile) {
- PsiElement doc_elt = PythonDosStringFinder.find((PyElement)file); // this gives the literal; its parent is the expr seeker may have encountered
+ PsiElement doc_elt = PythonDocStringFinder.find((PyElement)file); // this gives the literal; its parent is the expr seeker may have encountered
if (doc_elt != null && doc_elt.getParent() == seeker) {
feeler = seeker.getNextSibling();
seeker = feeler; // skip over doc even if there's nothing below it
diff --git a/python/src/com/jetbrains/python/actions/AddMethodQuickFix.java b/python/src/com/jetbrains/python/actions/AddMethodQuickFix.java
index 8bdd0bafbc02..869ec58be292 100644
--- a/python/src/com/jetbrains/python/actions/AddMethodQuickFix.java
+++ b/python/src/com/jetbrains/python/actions/AddMethodQuickFix.java
@@ -14,12 +14,13 @@ import com.intellij.util.IncorrectOperationException;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.PythonLanguage;
import com.jetbrains.python.psi.*;
-import static com.jetbrains.python.psi.PyUtil.sure;
import com.jetbrains.python.psi.impl.ParamHelper;
import com.jetbrains.python.psi.types.PyClassType;
import com.jetbrains.python.psi.types.PyType;
import org.jetbrains.annotations.NotNull;
+import static com.jetbrains.python.psi.PyUtil.sure;
+
/**
* Adds a method foo to class X if X.foo() is unresolved.
* User: dcheryasov
@@ -73,7 +74,7 @@ public class AddMethodQuickFix implements LocalQuickFix {
if (call_by_class) {
if (args.length > 0) {
PyType first_arg_type = args[0].getType();
- if (first_arg_type instanceof PyClassType && ((PyClassType)first_arg_type).getPyClass().isSublclass(cls)) {
+ if (first_arg_type instanceof PyClassType && ((PyClassType)first_arg_type).getPyClass().isSubclass(cls)) {
// class, first arg ok: instance method
param_buf.append("self"); // NOTE: might use a name other than 'self', according to code style.
made_instance = true;
diff --git a/python/src/com/jetbrains/python/psi/PyClass.java b/python/src/com/jetbrains/python/psi/PyClass.java
index 86d7c50ea2d9..3e9b30d603c0 100644
--- a/python/src/com/jetbrains/python/psi/PyClass.java
+++ b/python/src/com/jetbrains/python/psi/PyClass.java
@@ -19,7 +19,7 @@ public interface PyClass extends PsiNamedElement, PyStatement, NameDefiner, PyDo
@NotNull
PyExpression[] getSuperClassExpressions();
- @Nullable
+ @NotNull
PsiElement[] getSuperClassElements();
@NotNull
@@ -56,7 +56,7 @@ public interface PyClass extends PsiNamedElement, PyStatement, NameDefiner, PyDo
* @param parent
* @return True iff this and parent are the same or parent is one of our superclasses.
*/
- boolean isSublclass(PyClass parent);
+ boolean isSubclass(PyClass parent);
@Nullable
PyDecoratorList getDecoratorList();
diff --git a/python/src/com/jetbrains/python/psi/impl/PyBuiltinCache.java b/python/src/com/jetbrains/python/psi/impl/PyBuiltinCache.java
index 6e989c34acc7..c725738696a5 100644
--- a/python/src/com/jetbrains/python/psi/impl/PyBuiltinCache.java
+++ b/python/src/com/jetbrains/python/psi/impl/PyBuiltinCache.java
@@ -1,6 +1,7 @@
package com.jetbrains.python.psi.impl;
import com.intellij.openapi.components.ComponentManager;
+import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtil;
import com.intellij.openapi.project.Project;
@@ -29,7 +30,6 @@ import java.util.Map;
* Provides access to Python builtins via skeletons.
*/
public class PyBuiltinCache {
-
public static final @NonNls String BUILTIN_FILE = "__builtin__.py";
@NotNull
@@ -59,7 +59,7 @@ public class PyBuiltinCache {
}
if (sdk != null) {
// dig out the builtins file, create an instance based on it
- final String[] urls = sdk.getRootProvider().getUrls(PythonSdkType.BUITLIN_ROOT_TYPE);
+ final String[] urls = sdk.getRootProvider().getUrls(PythonSdkType.BUILTIN_ROOT_TYPE);
for (String url : urls) {
if (url.contains(PythonSdkType.SKELETON_DIR_NAME)) {
final String builtins_url = url + "/" + ((PythonSdkType)sdk.getSdkType()).getBuiltinsFileName(sdk);
@@ -89,11 +89,9 @@ public class PyBuiltinCache {
*/
private static final Map ourInstanceCache = new HashMap();
- private Project myProject;
private PyFile myBuiltinsFile;
- public PyBuiltinCache(final Project project) {
- myProject = project;
+ public PyBuiltinCache() {
}
private PyBuiltinCache(@Nullable final PyFile builtins) {
diff --git a/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java b/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java
index d140d9e10761..67c631aa089a 100644
--- a/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java
+++ b/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java
@@ -114,7 +114,14 @@ public class PyCallExpressionHelper {
}
// decorators?
PyFunction method = (PyFunction)resolved; // constructor call?
- if (PyNames.INIT.equals(method.getName())) implicit_offset += 1;
+ if (PyNames.INIT.equals(method.getName())) {
+ String refName = us.getCallee() instanceof PyReferenceExpression
+ ? ((PyReferenceExpression) us.getCallee()).getReferencedName()
+ : null;
+ if (!PyNames.INIT.equals(refName)) { // PY-312
+ implicit_offset += 1;
+ }
+ }
// look for closest decorator
PyDecoratorList decolist = method.getDecoratorList();
if (decolist != null) {
diff --git a/python/src/com/jetbrains/python/psi/impl/PyCallExpressionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyCallExpressionImpl.java
index 09a3a1639344..e954bc57a04d 100644
--- a/python/src/com/jetbrains/python/psi/impl/PyCallExpressionImpl.java
+++ b/python/src/com/jetbrains/python/psi/impl/PyCallExpressionImpl.java
@@ -104,7 +104,7 @@ public class PyCallExpressionImpl extends PyElementImpl implements PyCallExpress
// imitate isinstance(second_arg, possible_class)
PyClass second_class = ((PyClassType)second_type).getPyClass();
assert second_class != null;
- if (second_class.isSublclass(first_class)) {
+ if (second_class.isSubclass(first_class)) {
// TODO: super(Foo, Bar) is a superclass of Foo directly preceding Bar in MRO
return new PyClassType(first_class, false); // super(Foo, self) has type of Foo, modulo __get__()
}
diff --git a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java
index 01bf4d804b8c..cc21c2da389d 100644
--- a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java
+++ b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java
@@ -12,7 +12,7 @@ import com.intellij.util.IncorrectOperationException;
import com.jetbrains.python.PyElementTypes;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.PyTokenTypes;
-import com.jetbrains.python.PythonDosStringFinder;
+import com.jetbrains.python.PythonDocStringFinder;
import com.jetbrains.python.codeInsight.dataflow.scope.Scope;
import com.jetbrains.python.codeInsight.dataflow.scope.impl.ScopeImpl;
import com.jetbrains.python.psi.*;
@@ -90,6 +90,7 @@ public class PyClassImpl extends PyPresentableElementImpl implement
return PyExpression.EMPTY_ARRAY;
}
+ @NotNull
public PsiElement[] getSuperClassElements() {
final PyExpression[] superExpressions = getSuperClassExpressions();
List superClasses = new ArrayList();
@@ -109,60 +110,10 @@ public class PyClassImpl extends PyPresentableElementImpl implement
It must be more efficient on deep and wide hierarchies, but it was more fun than efficiency that produced it.
*/
public Iterable iterateAncestors() {
- return new Iterable() {
- public Iterator iterator() {
- return new Iterator() {
- List pending = new LinkedList();
- Set seen = new HashSet();
- Iterator percolator = getSuperClassesList().iterator();
- PyClass prefetch = null;
-
- public boolean hasNext() {
- // due to already-seen filtering, there's no way but to try and see.
- if (prefetch != null) return true;
- try {
- prefetch = next();
- return true;
- }
- catch (NoSuchElementException e) {
- return false;
- }
- }
-
- public PyClass next() {
- iterations:
- while (true) {
- if (prefetch != null) {
- PyClass ret = prefetch;
- prefetch = null;
- return ret;
- }
- if (percolator.hasNext()) {
- PyClass it = percolator.next();
- if (seen.contains(it)) continue iterations; // loop back is equivalent to return next();
- pending.add(it);
- seen.add(it);
- return it;
- }
- else if (pending.size() > 0) {
- PyClass it = pending.get(0);
- pending.remove(0); // t, ts* = pending
- percolator = it.iterateAncestors().iterator();
- // loop back is equivalent to return next();
- }
- else throw new NoSuchElementException();
- }
- }
-
- public void remove() {
- throw new UnsupportedOperationException();
- }
- };
- }
- };
+ return new AncestorsIterable(this);
}
- public boolean isSublclass(PyClass parent) {
+ public boolean isSubclass(PyClass parent) {
if (this == parent) return true;
for (PyClass superclass : iterateAncestors()) {
if (parent == superclass) return true;
@@ -175,32 +126,35 @@ public class PyClassImpl extends PyPresentableElementImpl implement
}
protected List getSuperClassesList() {
+ if (PyNames.FAKE_OLD_BASE.equals(getName())) {
+ return Collections.emptyList();
+ }
PsiElement[] superClassElements = getSuperClassElements();
- if (superClassElements != null) {
+ if (superClassElements.length > 0) {
List result = new ArrayList();
// maybe a bare old-style class?
// TODO: depend on language version: py3k does not do old style classes
PsiElement paren = PsiTreeUtil.getChildOfType(this, PyArgumentList.class).getFirstChild(); // no NPE, we always have the par expr
if (paren != null && "(".equals(paren.getText())) { // "()" after class name, it's new style
- for(PsiElement element: superClassElements) {
+ for (PsiElement element : superClassElements) {
if (element instanceof PyClass) {
- result.add((PyClass) element);
+ result.add((PyClass)element);
}
}
}
- else if (! PyBuiltinCache.BUILTIN_FILE.equals(getContainingFile().getName())) { // old-style *and* not builtin object()
+ else if (!PyBuiltinCache.BUILTIN_FILE.equals(getContainingFile().getName())) { // old-style *and* not builtin object()
PyClass oldstyler = PyBuiltinCache.getInstance(this).getClass(PyNames.FAKE_OLD_BASE);
if (oldstyler != null) result.add(oldstyler);
}
return result;
}
- return new ArrayList(0);
+ return Collections.emptyList();
}
@NotNull
public PyClass[] getSuperClasses() {
PsiElement[] superClassElements = getSuperClassElements();
- if (superClassElements != null) {
+ if (superClassElements .length > 0) {
List result = new ArrayList();
for(PsiElement element: superClassElements) {
if (element instanceof PyClass) {
@@ -331,7 +285,7 @@ public class PyClassImpl extends PyPresentableElementImpl implement
}
public PyStringLiteralExpression getDocStringExpression() {
- return PythonDosStringFinder.find(getStatementList());
+ return PythonDocStringFinder.find(getStatementList());
}
public String toString() {
@@ -388,4 +342,76 @@ public class PyClassImpl extends PyPresentableElementImpl implement
private static T getRefValue(final SoftReference reference){
return reference != null ? reference.get() : null;
}
+
+ private static class AncestorsIterable implements Iterable {
+ private PyClassImpl myClass;
+
+ public AncestorsIterable(final PyClassImpl pyClass) {
+ myClass = pyClass;
+ }
+
+ public Iterator iterator() {
+ return new AncestorsIterator(myClass);
+ }
+ }
+
+ private static class AncestorsIterator implements Iterator {
+ List pending = new LinkedList();
+ Set seen = new HashSet();
+ Iterator percolator;
+ PyClass prefetch = null;
+ private PyClassImpl myAClass;
+
+ public AncestorsIterator(PyClassImpl aClass) {
+ myAClass = aClass;
+ percolator = myAClass.getSuperClassesList().iterator();
+ }
+
+ public boolean hasNext() {
+ // due to already-seen filtering, there's no way but to try and see.
+ if (prefetch != null) return true;
+ prefetch = getNext();
+ return prefetch != null;
+ }
+
+ public PyClass next() {
+ final PyClass nextClass = getNext();
+ if (nextClass == null) throw new NoSuchElementException();
+ return nextClass;
+ }
+
+ @Nullable
+ private PyClass getNext() {
+ iterations:
+ while (true) {
+ if (prefetch != null) {
+ PyClass ret = prefetch;
+ prefetch = null;
+ return ret;
+ }
+ if (percolator.hasNext()) {
+ PyClass it = percolator.next();
+ if (seen.contains(it)) {
+ continue iterations; // loop back is equivalent to return next();
+ }
+ pending.add(it);
+ seen.add(it);
+ return it;
+ }
+ else if (pending.size() > 0) {
+ PyClass it = pending.get(0);
+ pending.remove(0); // t, ts* = pending
+ percolator = it.iterateAncestors().iterator();
+ // loop back is equivalent to return next();
+ }
+ else return null;
+ }
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+
}
diff --git a/python/src/com/jetbrains/python/psi/impl/PyFileImpl.java b/python/src/com/jetbrains/python/psi/impl/PyFileImpl.java
index bb02e373c51f..a4adc88198c4 100644
--- a/python/src/com/jetbrains/python/psi/impl/PyFileImpl.java
+++ b/python/src/com/jetbrains/python/psi/impl/PyFileImpl.java
@@ -14,7 +14,7 @@ import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;
import com.intellij.util.indexing.FileBasedIndex;
import com.jetbrains.python.PyElementTypes;
-import com.jetbrains.python.PythonDosStringFinder;
+import com.jetbrains.python.PythonDocStringFinder;
import com.jetbrains.python.PythonFileType;
import com.jetbrains.python.PythonLanguage;
import com.jetbrains.python.codeInsight.dataflow.scope.Scope;
@@ -250,7 +250,7 @@ public class PyFileImpl extends PsiFileBase implements PyFile, PyExpression {
}
public PyStringLiteralExpression getDocStringExpression() {
- return PythonDosStringFinder.find(this);
+ return PythonDocStringFinder.find(this);
}
public void subtreeChanged() {
diff --git a/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java
index e449da049135..a90015d22c41 100644
--- a/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java
+++ b/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java
@@ -27,11 +27,11 @@ import com.intellij.util.Icons;
import com.intellij.util.IncorrectOperationException;
import com.jetbrains.python.PyElementTypes;
import com.jetbrains.python.PyTokenTypes;
-import com.jetbrains.python.PythonDosStringFinder;
+import com.jetbrains.python.PythonDocStringFinder;
+import com.jetbrains.python.codeInsight.controlflow.PyControlFlowBuilder;
import com.jetbrains.python.codeInsight.dataflow.scope.Scope;
import com.jetbrains.python.codeInsight.dataflow.scope.impl.ScopeImpl;
import com.jetbrains.python.psi.*;
-import com.jetbrains.python.codeInsight.controlflow.PyControlFlowBuilder;
import com.jetbrains.python.psi.stubs.PyClassStub;
import com.jetbrains.python.psi.stubs.PyFunctionStub;
import com.jetbrains.python.toolbox.SingleIterable;
@@ -149,7 +149,7 @@ public class PyFunctionImpl extends PyPresentableElementImpl imp
}
public PyStringLiteralExpression getDocStringExpression() {
- return PythonDosStringFinder.find(getStatementList());
+ return PythonDocStringFinder.find(getStatementList());
}
protected String getElementLocation() {
diff --git a/python/src/com/jetbrains/python/psi/resolve/ResolveImportUtil.java b/python/src/com/jetbrains/python/psi/resolve/ResolveImportUtil.java
index 54b113f52807..79f6effdeb0f 100644
--- a/python/src/com/jetbrains/python/psi/resolve/ResolveImportUtil.java
+++ b/python/src/com/jetbrains/python/psi/resolve/ResolveImportUtil.java
@@ -5,6 +5,7 @@ import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.*;
+import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
@@ -580,7 +581,10 @@ public class ResolveImportUtil {
) {
if (referencedName == null) return null;
final PsiFile file = dir.findFile(referencedName + PyNames.DOT_PY);
- if (file != null) return file;
+ // findFile() does case-insensitive search, and we need exactly matching case (see PY-381)
+ if (file != null && FileUtil.getNameWithoutExtension(file.getName()).equals(referencedName)) {
+ return file;
+ }
final PsiDirectory subdir = dir.findSubdirectory(referencedName);
if (subdir != null) return subdir;
else if (! isFileOnly) {
diff --git a/python/src/com/jetbrains/python/psi/search/PyDefinitionsSearch.java b/python/src/com/jetbrains/python/psi/search/PyDefinitionsSearch.java
index fc3beca03807..26c952fe32c4 100644
--- a/python/src/com/jetbrains/python/psi/search/PyDefinitionsSearch.java
+++ b/python/src/com/jetbrains/python/psi/search/PyDefinitionsSearch.java
@@ -4,8 +4,10 @@ import com.intellij.psi.PsiElement;
import com.intellij.util.Processor;
import com.intellij.util.Query;
import com.intellij.util.QueryExecutor;
+import com.jetbrains.python.psi.PyAssignmentStatement;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyFunction;
+import com.jetbrains.python.psi.PyTargetExpression;
/**
* @author yole
@@ -28,6 +30,12 @@ public class PyDefinitionsSearch implements QueryExecutor occurrences;
@@ -119,21 +125,32 @@ abstract public class IntroduceHandler implements RefactoringActionHandler {
occurrences = Collections.emptyList();
}
String[] possibleNames = getSuggestedNames(expression);
+ boolean initInConstructor = false;
if (name == null) {
- PyIntroduceDialog dialog = new PyIntroduceDialog(project, expression, myDialogTitle, myValidator, occurrences.size(), possibleNames);
+ PyIntroduceDialog dialog = new PyIntroduceDialog(project, expression, myDialogTitle, myValidator, occurrences.size(), possibleNames, getHelpId(), hasConstructor);
dialog.show();
if (!dialog.isOK()) {
return;
}
name = dialog.getName();
replaceAll = dialog.doReplaceAllOccurrences();
+ initInConstructor = dialog.initInConstructor();
}
String assignmentText = name + " = " + expression.getText();
- final PyAssignmentStatement declaration =
- PythonLanguage.getInstance().getElementGenerator().createFromText(project, PyAssignmentStatement.class, assignmentText);
+ final PyAssignmentStatement declaration = createDeclaration(project, assignmentText);
assert name != null;
- performReplace(project, declaration, expression, occurrences, name, replaceAll);
+ performReplace(project, declaration, expression, occurrences, name, replaceAll, initInConstructor);
+ }
+
+ protected abstract String getHelpId();
+
+ protected PyAssignmentStatement createDeclaration(Project project, String assignmentText) {
+ return PythonLanguage.getInstance().getElementGenerator().createFromText(project, PyAssignmentStatement.class, assignmentText);
+ }
+
+ protected boolean checkEnabled(Project project, Editor editor, PsiElement element1, String dialogTitle) {
+ return true;
}
private static List getOccurrences(@NotNull final PyExpression expression) {
@@ -152,19 +169,19 @@ abstract public class IntroduceHandler implements RefactoringActionHandler {
@NotNull final PsiElement expression,
@NotNull final List occurrences,
@NotNull final String name,
- final boolean replaceAll) {
+ final boolean replaceAll,
+ final boolean initInConstructor) {
new WriteCommandAction(project, expression.getContainingFile()) {
protected void run(final Result result) throws Throwable {
final Pair data = expression.getUserData(PyPsiUtils.SELECTION_BREAKS_AST_NODE);
if (data == null) {
- addDeclaration(expression, declaration, occurrences, replaceAll);
+ addDeclaration(expression, declaration, occurrences, replaceAll, initInConstructor);
}
else {
- addDeclaration(data.first, declaration, occurrences, replaceAll);
+ addDeclaration(data.first, declaration, occurrences, replaceAll, initInConstructor);
}
- PyExpressionStatement newExpression =
- PythonLanguage.getInstance().getElementGenerator().createFromText(project, PyExpressionStatement.class, name);
+ PyExpressionStatement newExpression = createExpression(project, name, declaration);
if (replaceAll) {
for (PsiElement occurrence : occurrences) {
@@ -178,8 +195,14 @@ abstract public class IntroduceHandler implements RefactoringActionHandler {
}.execute();
}
+ protected PyExpressionStatement createExpression(Project project, String name, PyAssignmentStatement declaration) {
+ return PythonLanguage.getInstance().getElementGenerator().createFromText(project, PyExpressionStatement.class, name);
+ }
+
+ @Nullable
protected abstract PsiElement addDeclaration(@NotNull final PsiElement expression,
@NotNull final PsiElement declaration,
@NotNull final List occurrences,
- final boolean replaceAll);
+ final boolean replaceAll,
+ boolean initInConstructor);
}
diff --git a/python/src/com/jetbrains/python/refactoring/introduce/PyIntroduceDialog.form b/python/src/com/jetbrains/python/refactoring/introduce/PyIntroduceDialog.form
index 508979c86873..202c4f8ac632 100644
--- a/python/src/com/jetbrains/python/refactoring/introduce/PyIntroduceDialog.form
+++ b/python/src/com/jetbrains/python/refactoring/introduce/PyIntroduceDialog.form
@@ -43,7 +43,7 @@
-
+
@@ -51,13 +51,21 @@
-
+
+
+
+
+
+
+
+
+
+
-
-
+
diff --git a/python/src/com/jetbrains/python/refactoring/introduce/PyIntroduceDialog.java b/python/src/com/jetbrains/python/refactoring/introduce/PyIntroduceDialog.java
index f8c590f93df9..3546e262767f 100644
--- a/python/src/com/jetbrains/python/refactoring/introduce/PyIntroduceDialog.java
+++ b/python/src/com/jetbrains/python/refactoring/introduce/PyIntroduceDialog.java
@@ -5,6 +5,7 @@ import com.intellij.openapi.editor.event.DocumentListener;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.ComboBox;
import com.intellij.openapi.ui.DialogWrapper;
+import com.intellij.refactoring.RefactoringBundle;
import com.intellij.ui.EditorComboBoxEditor;
import com.intellij.ui.EditorComboBoxRenderer;
import com.intellij.ui.EditorTextField;
@@ -28,33 +29,43 @@ public class PyIntroduceDialog extends DialogWrapper implements PyIntroduceSetti
private JPanel myContentPane;
private JLabel myNameLabel;
private ComboBox myNameComboBox;
- private JCheckBox myCheckBox;
+ private JCheckBox myReplaceAll;
+ private JCheckBox myInitialzeInConstructor;
private final Project myProject;
private final int myOccurrencesCount;
private final IntroduceValidator myValidator;
private final PyExpression myExpression;
+ private final String myHelpId;
public PyIntroduceDialog(@NotNull final Project project,
@NotNull PyExpression expression,
@NotNull final String caption,
@NotNull final IntroduceValidator validator,
final int occurrencesCount,
- final String[] possibleNames) {
+ final String[] possibleNames,
+ final String helpId,
+ boolean hasConstructor) {
super(project, true);
myOccurrencesCount = occurrencesCount;
myValidator = validator;
myProject = project;
myExpression = expression;
+ myHelpId = helpId;
setUpNameComboBox(possibleNames);
setModal(true);
setTitle(caption);
init();
- setupDialog();
+ setupDialog(hasConstructor);
updateControls();
}
+ @Override
+ protected String getHelpId() {
+ return myHelpId;
+ }
+
private void setUpNameComboBox(String[] possibleNames) {
final EditorComboBoxEditor comboEditor = new StringComboboxEditor(myProject, PythonFileType.INSTANCE, myNameComboBox);
@@ -89,19 +100,21 @@ public class PyIntroduceDialog extends DialogWrapper implements PyIntroduceSetti
}
}
- private void setupDialog() {
- myCheckBox.setMnemonic(KeyEvent.VK_A);
+ private void setupDialog(boolean hasConstructor) {
+ myReplaceAll.setMnemonic(KeyEvent.VK_A);
myNameLabel.setLabelFor(myNameComboBox);
+ myInitialzeInConstructor.setText(RefactoringBundle.message("initialize.in.border.title") + " " + RefactoringBundle.message("class.constructors.radio"));
+ myInitialzeInConstructor.setVisible(hasConstructor);
// Replace occurences check box setup
if (myOccurrencesCount > 1) {
- myCheckBox.setSelected(false);
- myCheckBox.setEnabled(true);
- myCheckBox.setText(myCheckBox.getText() + " (" + myOccurrencesCount + " occurrences)");
+ myReplaceAll.setSelected(false);
+ myReplaceAll.setEnabled(true);
+ myReplaceAll.setText(myReplaceAll.getText() + " (" + myOccurrencesCount + " occurrences)");
}
else {
- myCheckBox.setSelected(false);
- myCheckBox.setEnabled(false);
+ myReplaceAll.setSelected(false);
+ myReplaceAll.setEnabled(false);
}
}
@@ -131,16 +144,16 @@ public class PyIntroduceDialog extends DialogWrapper implements PyIntroduceSetti
}
public boolean doReplaceAllOccurrences() {
- return myCheckBox.isSelected();
+ return myReplaceAll.isSelected();
}
private void updateControls() {
final boolean nameValid = myValidator.isNameValid(this);
setOKActionEnabled(nameValid);
- if (!nameValid) {
- setErrorText(PyBundle.message("refactoring.introduce.name.error"));
- return;
- }
- setErrorText(myValidator.check(this));
+ setErrorText(!nameValid ? PyBundle.message("refactoring.introduce.name.error") : null);
+ }
+
+ public boolean initInConstructor() {
+ return myInitialzeInConstructor.isSelected();
}
}
diff --git a/python/src/com/jetbrains/python/refactoring/introduce/constant/ConstantIntroduceHandler.java b/python/src/com/jetbrains/python/refactoring/introduce/constant/ConstantIntroduceHandler.java
index c1f7b6d469a4..1a55ddcd7e3d 100644
--- a/python/src/com/jetbrains/python/refactoring/introduce/constant/ConstantIntroduceHandler.java
+++ b/python/src/com/jetbrains/python/refactoring/introduce/constant/ConstantIntroduceHandler.java
@@ -26,7 +26,8 @@ public class ConstantIntroduceHandler extends IntroduceHandler {
protected PsiElement addDeclaration(@NotNull final PsiElement expression,
@NotNull final PsiElement declaration,
@NotNull final List occurrences,
- final boolean replaceAll) {
+ final boolean replaceAll,
+ boolean initInConstructor) {
PsiElement anchor;
anchor = expression.getContainingFile();
assert anchor instanceof PyFile;
@@ -40,4 +41,9 @@ public class ConstantIntroduceHandler extends IntroduceHandler {
}
return ArrayUtil.toStringArray(names);
}
+
+ @Override
+ protected String getHelpId() {
+ return "refactoring.introduceConstant";
+ }
}
diff --git a/python/src/com/jetbrains/python/refactoring/introduce/field/FieldIntroduceHandler.java b/python/src/com/jetbrains/python/refactoring/introduce/field/FieldIntroduceHandler.java
new file mode 100644
index 000000000000..83595e262459
--- /dev/null
+++ b/python/src/com/jetbrains/python/refactoring/introduce/field/FieldIntroduceHandler.java
@@ -0,0 +1,96 @@
+package com.jetbrains.python.refactoring.introduce.field;
+
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.refactoring.RefactoringBundle;
+import com.intellij.refactoring.util.CommonRefactoringUtil;
+import com.jetbrains.python.PyNames;
+import com.jetbrains.python.PythonLanguage;
+import com.jetbrains.python.actions.AddFieldQuickFix;
+import com.jetbrains.python.psi.*;
+import com.jetbrains.python.refactoring.introduce.IntroduceHandler;
+import com.jetbrains.python.refactoring.introduce.variable.VariableIntroduceHandler;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+
+/**
+ * @author Dennis.Ushakov
+ */
+public class FieldIntroduceHandler extends IntroduceHandler {
+
+ public FieldIntroduceHandler() {
+ super(new IntroduceFieldValidator(), RefactoringBundle.message("introduce.field.title"));
+ }
+
+ public void invoke(@NotNull Project project, Editor editor, PsiFile file, DataContext dataContext) {
+ performAction(project, editor, file, null, false, true);
+ }
+
+ @Override
+ protected boolean checkEnabled(Project project, Editor editor, PsiElement element1, String dialogTitle) {
+ if (PyUtil.getContainingClassOrSelf(element1) == null) {
+ CommonRefactoringUtil.showErrorHint(project, editor, "Cannot introduce field: not in class", dialogTitle,
+ "refactoring.extractMethod");
+ return false;
+ }
+ return true;
+ }
+
+ @Nullable
+ @Override
+ protected PsiElement addDeclaration(@NotNull PsiElement expression, @NotNull PsiElement declaration, @NotNull List occurrences,
+ boolean replaceAll, boolean initInConstructor) {
+ final PsiElement expr = expression instanceof PyClass ? expression : expression.getParent();
+ PsiElement anchor = PyUtil.getContainingClassOrSelf(expr);
+ assert anchor instanceof PyClass;
+ if (initInConstructor) {
+ final Project project = anchor.getProject();
+ final PyClass clazz = (PyClass)anchor;
+ AddFieldQuickFix.addFieldToInit(project, clazz, "", new AddFieldDeclaration(project, declaration));
+ final PyFunction init = clazz.findMethodByName(PyNames.INIT, false);
+ final PyStatementList statements = init != null ? init.getStatementList() : null;
+ return statements != null ? statements.getLastChild() : null;
+ }
+ return VariableIntroduceHandler.doIntroduceVariable(expression, declaration, occurrences, replaceAll);
+ }
+
+ @Override
+ protected PyExpressionStatement createExpression(Project project, String name, PyAssignmentStatement declaration) {
+ final String text = declaration.getText();
+ final String self_name = text.substring(0, text.indexOf('.'));
+ return PythonLanguage.getInstance().getElementGenerator().createFromText(project, PyExpressionStatement.class, self_name + "." + name);
+ }
+
+ @Override
+ protected PyAssignmentStatement createDeclaration(Project project, String assignmentText) {
+ return PythonLanguage.getInstance().getElementGenerator().createFromText(project, PyAssignmentStatement.class, PyNames.CANONICAL_SELF + "." + assignmentText);
+ }
+
+ @Override
+ protected String getHelpId() {
+ return "refactoring.introduceField";
+ }
+
+ private static class AddFieldDeclaration extends AddFieldQuickFix.FieldCallback {
+ private final Project myProject;
+ private final PsiElement myDeclaration;
+
+ private AddFieldDeclaration(Project project, PsiElement declaration) {
+ myProject = project;
+ myDeclaration = declaration;
+ }
+
+ public PyStatement fun(String self_name) {
+ if (PyNames.CANONICAL_SELF.equals(self_name)) {
+ return (PyStatement)myDeclaration;
+ }
+ final String text = myDeclaration.getText();
+ return myGenerator.createFromText(myProject, PyStatement.class, text.replaceFirst(PyNames.CANONICAL_SELF + "\\.", self_name + "."));
+ }
+ }
+}
diff --git a/python/src/com/jetbrains/python/refactoring/introduce/field/IntroduceFieldValidator.java b/python/src/com/jetbrains/python/refactoring/introduce/field/IntroduceFieldValidator.java
new file mode 100644
index 000000000000..3e9a7ce2a1a1
--- /dev/null
+++ b/python/src/com/jetbrains/python/refactoring/introduce/field/IntroduceFieldValidator.java
@@ -0,0 +1,37 @@
+package com.jetbrains.python.refactoring.introduce.field;
+
+import com.intellij.openapi.util.Pair;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.util.PsiTreeUtil;
+import com.jetbrains.python.PyBundle;
+import com.jetbrains.python.psi.PyClass;
+import com.jetbrains.python.psi.PyFunction;
+import com.jetbrains.python.psi.impl.PyPsiUtils;
+import com.jetbrains.python.refactoring.PyRefactoringUtil;
+import com.jetbrains.python.refactoring.introduce.IntroduceValidator;
+
+/**
+ * @author Dennis.Ushakov
+ */
+public class IntroduceFieldValidator extends IntroduceValidator {
+ @Override
+ protected String simpleCheck(String name, PsiElement psiElement) {
+ if (psiElement.getUserData(PyPsiUtils.SELECTION_BREAKS_AST_NODE) != null) {
+ final Pair data = psiElement.getUserData(PyPsiUtils.SELECTION_BREAKS_AST_NODE);
+ psiElement = data.first;
+ }
+ PsiElement context = PsiTreeUtil.getParentOfType(psiElement, PyFunction.class);
+ if (context == null) {
+ context = PsiTreeUtil.getParentOfType(psiElement, PyClass.class);
+ }
+ if (context == null) {
+ context = psiElement.getContainingFile();
+ }
+
+ if (PyRefactoringUtil.collectScopeVariables(context).contains(name)) {
+ return PyBundle.message("refactoring.introduce.constant.scope.error");
+ }
+ return null;
+ }
+}
diff --git a/python/src/com/jetbrains/python/refactoring/introduce/variable/VariableIntroduceHandler.java b/python/src/com/jetbrains/python/refactoring/introduce/variable/VariableIntroduceHandler.java
index cdaca71ea201..7d843ca4c95d 100644
--- a/python/src/com/jetbrains/python/refactoring/introduce/variable/VariableIntroduceHandler.java
+++ b/python/src/com/jetbrains/python/refactoring/introduce/variable/VariableIntroduceHandler.java
@@ -25,7 +25,12 @@ public class VariableIntroduceHandler extends IntroduceHandler {
protected PsiElement addDeclaration(@NotNull final PsiElement expression,
@NotNull final PsiElement declaration,
@NotNull final List occurrences,
- final boolean replaceAll) {
+ final boolean replaceAll,
+ boolean initInConstructor) {
+ return doIntroduceVariable(expression, declaration, occurrences, replaceAll);
+ }
+
+ public static PsiElement doIntroduceVariable(PsiElement expression, PsiElement declaration, List occurrences, boolean replaceAll) {
PyStatement anchorStatement;
if (replaceAll) {
final PsiElement parent = PsiTreeUtil.findCommonParent(occurrences.toArray(new PsiElement[occurrences.size()]));
@@ -39,4 +44,9 @@ public class VariableIntroduceHandler extends IntroduceHandler {
anchorStatement.getParent().addBefore(declaration, anchorStatement);
return anchorStatement.getParent().getParent();
}
+
+ @Override
+ protected String getHelpId() {
+ return "refactoring.introduceVariable";
+ }
}
diff --git a/python/src/com/jetbrains/python/run/PyCommonOptionsForm.java b/python/src/com/jetbrains/python/run/PyCommonOptionsForm.java
index f608d9730666..c326252a170a 100644
--- a/python/src/com/jetbrains/python/run/PyCommonOptionsForm.java
+++ b/python/src/com/jetbrains/python/run/PyCommonOptionsForm.java
@@ -7,7 +7,6 @@ import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.projectRoots.impl.SdkListCellRenderer;
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
import com.intellij.openapi.util.io.FileUtil;
-import static com.intellij.openapi.util.io.FileUtil.toSystemIndependentName;
import com.intellij.ui.CollectionComboBoxModel;
import com.intellij.ui.ColoredListCellRenderer;
import com.intellij.ui.RawCommandLineEditor;
@@ -16,6 +15,8 @@ import com.jetbrains.python.sdk.PythonSdkType;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -23,7 +24,7 @@ import java.util.Map;
/**
* @author yole
*/
-public class PyCommonOptionsForm implements AbstractPythonRunConfigurationParams {
+ public class PyCommonOptionsForm implements AbstractPythonRunConfigurationParams {
private TextFieldWithBrowseButton myWorkingDirectoryTextField;
private EnvironmentVariablesComponent myEnvsComponent;
private RawCommandLineEditor myInterpreterOptionsTextField;
@@ -56,6 +57,18 @@ public class PyCommonOptionsForm implements AbstractPythonRunConfigurationParams
myWorkingDirectoryTextField.addBrowseFolderListener("Select Working Directory", "", configuration.getProject(),
new FileChooserDescriptor(false, true, false, false, false, false));
+ ActionListener listener = new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ updateControls();
+ }
+ };
+ myUseSpecifiedSdkRadioButton.addActionListener(listener);
+ myUseModuleSdkRadioButton.addActionListener(listener);
+ }
+
+ private void updateControls() {
+ myModuleComboBox.setEnabled(myUseModuleSdkRadioButton.isSelected());
+ myInterpreterComboBox.setEnabled(myUseSpecifiedSdkRadioButton.isSelected());
}
public JPanel getMainPanel() {
@@ -75,7 +88,7 @@ public class PyCommonOptionsForm implements AbstractPythonRunConfigurationParams
}
public String getWorkingDirectory() {
- return toSystemIndependentName(myWorkingDirectoryTextField.getText().trim());
+ return FileUtil.toSystemIndependentName(myWorkingDirectoryTextField.getText().trim());
}
public void setWorkingDirectory(String workingDirectory) {
@@ -122,6 +135,7 @@ public class PyCommonOptionsForm implements AbstractPythonRunConfigurationParams
else {
myUseSpecifiedSdkRadioButton.setSelected(true);
}
+ updateControls();
}
public boolean isPassParentEnvs() {
diff --git a/python/src/com/jetbrains/python/sdk/PythonSdkType.java b/python/src/com/jetbrains/python/sdk/PythonSdkType.java
index 6c1d8963d588..cd1bc404c714 100644
--- a/python/src/com/jetbrains/python/sdk/PythonSdkType.java
+++ b/python/src/com/jetbrains/python/sdk/PythonSdkType.java
@@ -79,7 +79,7 @@ public class PythonSdkType extends SdkType {
@NotNull
@NonNls
public String getBuiltinsFileName(Sdk sdk) {
- final String version = getVersionString(sdk);
+ final String version = sdk.getVersionString();
if (version != null && version.startsWith("Python 3")) {
return "builtins.py";
}
@@ -218,7 +218,7 @@ public class PythonSdkType extends SdkType {
@Override
public SdkAdditionalData loadAdditionalData(final Sdk currentSdk, final Element additional) {
- final String[] urls = currentSdk.getRootProvider().getUrls(OrderRootType.SOURCES);
+ final String[] urls = currentSdk.getRootProvider().getUrls(BUILTIN_ROOT_TYPE);
for (String url : urls) {
if (url.contains(SKELETON_DIR_NAME)) {
final String path = VfsUtil.urlToPath(url);
@@ -266,7 +266,7 @@ public class PythonSdkType extends SdkType {
/**
* In which root type built-in skeletons are put.
*/
- public static final OrderRootType BUITLIN_ROOT_TYPE = OrderRootType.CLASSES;
+ public static final OrderRootType BUILTIN_ROOT_TYPE = OrderRootType.CLASSES;
public static void setupSdkPaths(SdkModificator sdkModificator, ProgressIndicator indicator) {
String sdk_path = sdkModificator.getHomePath();
@@ -305,7 +305,7 @@ public class PythonSdkType extends SdkType {
indicator.setText2("");
}
generateBuiltinStubs(sdk_path, stubs_path);
- sdkModificator.addRoot(LocalFileSystem.getInstance().refreshAndFindFileByPath(stubs_path), BUITLIN_ROOT_TYPE);
+ sdkModificator.addRoot(LocalFileSystem.getInstance().refreshAndFindFileByPath(stubs_path), BUILTIN_ROOT_TYPE);
}
generateBinaryStubs(sdk_path, stubs_path, indicator);
}
diff --git a/python/src/com/jetbrains/python/validation/DocStringAnnotator.java b/python/src/com/jetbrains/python/validation/DocStringAnnotator.java
index e9840e1d53d3..e3bb9b7335e0 100644
--- a/python/src/com/jetbrains/python/validation/DocStringAnnotator.java
+++ b/python/src/com/jetbrains/python/validation/DocStringAnnotator.java
@@ -18,7 +18,7 @@ package com.jetbrains.python.validation;
import com.intellij.lang.annotation.Annotation;
import com.jetbrains.python.PyHighlighter;
-import com.jetbrains.python.PythonDosStringFinder;
+import com.jetbrains.python.PythonDocStringFinder;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyFile;
import com.jetbrains.python.psi.PyFunction;
@@ -31,18 +31,18 @@ public class DocStringAnnotator extends PyAnnotator {
@Override
public void visitPyFile(PyFile node) {
- annotateDocStringStmt(PythonDosStringFinder.find(node));
+ annotateDocStringStmt(PythonDocStringFinder.find(node));
}
@Override
public void visitPyFunction(PyFunction node) {
- annotateDocStringStmt(PythonDosStringFinder.find(node.getStatementList()));
+ annotateDocStringStmt(PythonDocStringFinder.find(node.getStatementList()));
}
@Override
public void visitPyClass(PyClass node) {
- annotateDocStringStmt(PythonDosStringFinder.find(node.getStatementList()));
+ annotateDocStringStmt(PythonDocStringFinder.find(node.getStatementList()));
}
private void annotateDocStringStmt(PyStringLiteralExpression stmt) {
diff --git a/python/testData/quickdoc/ClassUndocumentedConstructor.html b/python/testData/quickdoc/ClassUndocumentedConstructor.html
new file mode 100644
index 000000000000..8991500da1d8
--- /dev/null
+++ b/python/testData/quickdoc/ClassUndocumentedConstructor.html
@@ -0,0 +1 @@
+class Foo(object)
Doc of Foo.
diff --git a/python/testData/quickdoc/ClassUndocumentedConstructor.py b/python/testData/quickdoc/ClassUndocumentedConstructor.py
new file mode 100644
index 000000000000..6ad68e58ac2b
--- /dev/null
+++ b/python/testData/quickdoc/ClassUndocumentedConstructor.py
@@ -0,0 +1,7 @@
+# direct class doc
+class Foo(object):
+ "Doc of Foo."
+ def __init__(self):
+ pass
+
+Foo()
diff --git a/python/testData/resolve/multiFile/directoryVsClass/DirectoryVsClass.py b/python/testData/resolve/multiFile/directoryVsClass/DirectoryVsClass.py
new file mode 100644
index 000000000000..292aff1d1111
--- /dev/null
+++ b/python/testData/resolve/multiFile/directoryVsClass/DirectoryVsClass.py
@@ -0,0 +1,3 @@
+from foo import Context
+c = Context()
+# [
diff --git a/python/testData/resolve/multiFile/directoryVsClass/foo/__init__.py b/python/testData/resolve/multiFile/directoryVsClass/foo/__init__.py
new file mode 100644
index 000000000000..927f901d4d1e
--- /dev/null
+++ b/python/testData/resolve/multiFile/directoryVsClass/foo/__init__.py
@@ -0,0 +1,3 @@
+__all__ = ['Context']
+
+from foo.context import Context
diff --git a/python/testData/resolve/multiFile/directoryVsClass/foo/context.py b/python/testData/resolve/multiFile/directoryVsClass/foo/context.py
new file mode 100644
index 000000000000..4f2b7dad98e5
--- /dev/null
+++ b/python/testData/resolve/multiFile/directoryVsClass/foo/context.py
@@ -0,0 +1 @@
+class Context(object): pass
diff --git a/python/testSrc/com/jetbrains/python/PyMultiFileResolveTest.java b/python/testSrc/com/jetbrains/python/PyMultiFileResolveTest.java
index d7303047fabb..6d81ad89be44 100644
--- a/python/testSrc/com/jetbrains/python/PyMultiFileResolveTest.java
+++ b/python/testSrc/com/jetbrains/python/PyMultiFileResolveTest.java
@@ -128,6 +128,12 @@ public class PyMultiFileResolveTest extends PyLightFixtureTestCase {
assertEquals("silicate", ((PyStringLiteralExpression)value).getStringValue());
}
+ public void testDirectoryVsClass() throws Exception {
+ PsiElement element = doResolve();
+ assertTrue(element instanceof PyClass);
+ assertEquals("Context", ((PyClass) element).getName());
+ }
+
private PsiFile prepareFile() throws Exception {
String testName = getTestName(true);
String fileName = getTestName(false) + ".py";
diff --git a/python/testSrc/com/jetbrains/python/PyQuickDocTest.java b/python/testSrc/com/jetbrains/python/PyQuickDocTest.java
index 511ae3acce9a..5f3a6fb0b7d3 100644
--- a/python/testSrc/com/jetbrains/python/PyQuickDocTest.java
+++ b/python/testSrc/com/jetbrains/python/PyQuickDocTest.java
@@ -28,21 +28,22 @@ public class PyQuickDocTest extends LightMarkedTestCase {
myProvider = new PythonDocumentationProvider();
}
- protected String getTestDataPath() {
- return PythonTestUtil.getTestDataPath() + "/quickdoc/";
- }
-
private void checkByHTML(String text) throws Exception {
assertNotNull(text);
- String filePath = getTestName(false) + ".html";
+ String filePath = "/quickdoc/" + getTestName(false) + ".html";
final String fullPath = getTestDataPath() + filePath;
final VirtualFile vFile = LocalFileSystem.getInstance().findFileByPath(fullPath.replace(File.separatorChar, '/'));
- assertNotNull("file " + filePath + " not found", vFile);
+ assertNotNull("file " + fullPath + " not found", vFile);
String fileText = StringUtil.convertLineSeparators(VfsUtil.loadText(vFile), "\n");
assertEquals(fileText.trim(), text.trim());
}
+ @Override
+ protected Map loadTest() throws Exception {
+ return configureByFile("/quickdoc/" + getTestName(false) + ".py");
+ }
+
private void processRefDocPair() throws Exception {
Map marks = loadTest();
assertEquals(2, marks.size());
@@ -53,7 +54,7 @@ public class PyQuickDocTest extends LightMarkedTestCase {
PsiElement ref_elt = marks.get("").getParent(); // ident -> expr
final PyDocStringOwner doc_owner = (PyDocStringOwner)((PyReferenceExpression)ref_elt).resolve();
- assertEquals(doc_owner.getDocStringExpression(), doc_elt);
+ assertEquals(doc_elt, doc_owner.getDocStringExpression());
checkByHTML(myProvider.generateDoc(doc_owner, null));
}
@@ -70,6 +71,13 @@ public class PyQuickDocTest extends LightMarkedTestCase {
processRefDocPair();
}
+ public void testClassUndocumentedConstructor() throws Exception {
+ Map marks = loadTest();
+ PsiElement ref_elt = marks.get("").getParent(); // ident -> expr
+ final PyDocStringOwner doc_owner = (PyDocStringOwner)((PyReferenceExpression)ref_elt).resolve();
+ checkByHTML(myProvider.generateDoc(doc_owner, null));
+ }
+
public void testCallFunc() throws Exception {
processRefDocPair();
}
diff --git a/python/testSrc/com/jetbrains/python/fixtures/LightMarkedTestCase.java b/python/testSrc/com/jetbrains/python/fixtures/LightMarkedTestCase.java
index 888b51e6bf5b..479e1474c7be 100644
--- a/python/testSrc/com/jetbrains/python/fixtures/LightMarkedTestCase.java
+++ b/python/testSrc/com/jetbrains/python/fixtures/LightMarkedTestCase.java
@@ -7,6 +7,7 @@ import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
+import com.intellij.testFramework.TestDataFile;
import com.intellij.util.containers.HashMap;
import org.jetbrains.annotations.NonNls;
@@ -47,12 +48,12 @@ public abstract class LightMarkedTestCase extends PyLightFixtureTestCase {
* @return a mapping of markers to PSI elements
* @throws Exception
*/
- protected Map configureByFile(@NonNls String filePath, @NonNls String markerRegexp)
+ protected Map configureByFile(@TestDataFile @NonNls String filePath, @NonNls String markerRegexp)
throws Exception
{
final String fullPath = getTestDataPath() + filePath;
final VirtualFile vFile = LocalFileSystem.getInstance().findFileByPath(fullPath.replace(File.separatorChar, '/'));
- assertNotNull("file " + filePath + " not found", vFile);
+ assertNotNull("file " + fullPath + " not found", vFile);
String fileText = StringUtil.convertLineSeparators(VfsUtil.loadText(vFile), "\n");
@@ -111,6 +112,4 @@ public abstract class LightMarkedTestCase extends PyLightFixtureTestCase {
String fname = getTestName(false) + ".py";
return configureByFile(fname);
}
-
- protected abstract String getTestDataPath();
}
\ No newline at end of file
]