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