diff --git a/python/pluginSrc/com/jetbrains/python/module/PythonModuleType.java b/python/pluginSrc/com/jetbrains/python/module/PythonModuleType.java index 976985612984..98089001ec73 100644 --- a/python/pluginSrc/com/jetbrains/python/module/PythonModuleType.java +++ b/python/pluginSrc/com/jetbrains/python/module/PythonModuleType.java @@ -26,7 +26,7 @@ public class PythonModuleType extends PythonModuleTypeBase final PythonModuleBuilder moduleBuilder, final ModulesProvider modulesProvider) { ArrayList steps = new ArrayList(); - steps.add(new PythonSdkSelectStep(moduleBuilder, null, null, wizardContext.getProject())); + steps.add(new PythonSdkSelectStep(moduleBuilder, null, "reference.project.structure.sdk.python", wizardContext.getProject())); final List frameworkSupportProviderList = FrameworkSupportUtil.getProviders(getInstance()); if (!frameworkSupportProviderList.isEmpty()) { steps.add(new SupportForFrameworksStep(moduleBuilder, LibrariesContainerFactory.createContainer(wizardContext.getProject()))); diff --git a/python/src/com/jetbrains/python/PythonFoldingBuilder.java b/python/src/com/jetbrains/python/PythonFoldingBuilder.java index ada510dc311a..9b041fbc9912 100644 --- a/python/src/com/jetbrains/python/PythonFoldingBuilder.java +++ b/python/src/com/jetbrains/python/PythonFoldingBuilder.java @@ -7,10 +7,14 @@ import com.intellij.lang.folding.FoldingDescriptor; import com.intellij.openapi.editor.Document; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.util.text.LineTokenizer; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.TokenType; import com.intellij.psi.tree.IElementType; import com.jetbrains.python.psi.PyFileElementType; +import com.jetbrains.python.psi.PyStringLiteralExpression; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; @@ -49,25 +53,10 @@ public class PythonFoldingBuilder implements FoldingBuilder, DumbAware { } } else if (node.getElementType() == PyElementTypes.STATEMENT_LIST) { - IElementType elType = node.getTreeParent().getElementType(); - if (elType == PyElementTypes.FUNCTION_DECLARATION || elType == PyElementTypes.CLASS_DECLARATION) { - ASTNode colon = node.getTreeParent().findChildByType(PyTokenTypes.COLON); - if (colon != null && colon.getStartOffset() + 1 < node.getTextRange().getEndOffset() - 1) { - final CharSequence chars = node.getChars(); - int nodeStart = node.getTextRange().getStartOffset(); - int endOffset = node.getTextRange().getEndOffset(); - while(endOffset > colon.getStartOffset()+2 && endOffset > nodeStart && Character.isWhitespace(chars.charAt(endOffset - nodeStart - 1))) { - endOffset--; - } - descriptors.add(new FoldingDescriptor(node, new TextRange(colon.getStartOffset() + 1, endOffset))); - } - else { - TextRange range = node.getTextRange(); - if (range.getStartOffset() < range.getEndOffset() - 1) { // only for ranges at least 1 char wide - descriptors.add(new FoldingDescriptor(node, range)); - } - } - } + foldStatementList(node, descriptors); + } + else if (node.getElementType() == PyElementTypes.STRING_LITERAL_EXPRESSION) { + foldDocString(node, descriptors); } ASTNode child = node.getFirstChildNode(); @@ -77,6 +66,53 @@ public class PythonFoldingBuilder implements FoldingBuilder, DumbAware { } } + private static void foldStatementList(ASTNode node, List descriptors) { + IElementType elType = node.getTreeParent().getElementType(); + if (elType == PyElementTypes.FUNCTION_DECLARATION || elType == PyElementTypes.CLASS_DECLARATION) { + ASTNode colon = node.getTreeParent().findChildByType(PyTokenTypes.COLON); + if (colon != null && colon.getStartOffset() + 1 < node.getTextRange().getEndOffset() - 1) { + final CharSequence chars = node.getChars(); + int nodeStart = node.getTextRange().getStartOffset(); + int endOffset = node.getTextRange().getEndOffset(); + while(endOffset > colon.getStartOffset()+2 && endOffset > nodeStart && Character.isWhitespace(chars.charAt(endOffset - nodeStart - 1))) { + endOffset--; + } + descriptors.add(new FoldingDescriptor(node, new TextRange(colon.getStartOffset() + 1, endOffset))); + } + else { + TextRange range = node.getTextRange(); + if (range.getStartOffset() < range.getEndOffset() - 1) { // only for ranges at least 1 char wide + descriptors.add(new FoldingDescriptor(node, range)); + } + } + } + } + + private static void foldDocString(ASTNode node, List descriptors) { + if (getDocStringOwnerType(node) != null && StringUtil.countChars(node.getText(), '\n') > 1) { + descriptors.add(new FoldingDescriptor(node, node.getTextRange())); + } + } + + @Nullable + private static IElementType getDocStringOwnerType(ASTNode node) { + final ASTNode treeParent = node.getTreeParent(); + IElementType parentType = treeParent.getElementType(); + if (parentType == PyElementTypes.EXPRESSION_STATEMENT && treeParent.getTreeParent() != null) { + final ASTNode parent2 = treeParent.getTreeParent(); + if (parent2.getElementType() == PyElementTypes.STATEMENT_LIST && parent2.getTreeParent() != null && treeParent == parent2.getFirstChildNode()) { + final ASTNode parent3 = parent2.getTreeParent(); + if (parent3.getElementType() == PyElementTypes.FUNCTION_DECLARATION || parent3.getElementType() == PyElementTypes.CLASS_DECLARATION) { + return parent3.getElementType(); + } + } + else if (parent2.getElementType() instanceof PyFileElementType) { + return parent2.getElementType(); + } + } + return null; + } + private static boolean isImport(ASTNode node, boolean orWhitespace) { if (node == null) return false; IElementType elementType = node.getElementType(); @@ -90,6 +126,14 @@ public class PythonFoldingBuilder implements FoldingBuilder, DumbAware { if (isImport(node, false)) { return "import ..."; } + if (node.getElementType() == PyElementTypes.STRING_LITERAL_EXPRESSION) { + final String stringValue = ((PyStringLiteralExpression)node.getPsi()).getStringValue().trim(); + final String[] lines = LineTokenizer.tokenize(stringValue, true); + if (lines.length > 2 && lines[1].trim().length() == 0) { + return "\"\"\"" + lines [0].trim() + "...\"\"\""; + } + return "\"\"\"...\"\"\""; + } return "..."; } @@ -97,6 +141,16 @@ public class PythonFoldingBuilder implements FoldingBuilder, DumbAware { if (isImport(node, false)) { return CodeFoldingSettings.getInstance().COLLAPSE_IMPORTS; } + if (node.getElementType() == PyElementTypes.STRING_LITERAL_EXPRESSION) { + if (getDocStringOwnerType(node) == PyElementTypes.FUNCTION_DECLARATION && CodeFoldingSettings.getInstance().COLLAPSE_METHODS) { + // method will be collapsed, no need to also collapse docstring + return false; + } + return CodeFoldingSettings.getInstance().COLLAPSE_DOC_COMMENTS; + } + if (node.getElementType() == PyElementTypes.STATEMENT_LIST && node.getTreeParent().getElementType() == PyElementTypes.FUNCTION_DECLARATION) { + return CodeFoldingSettings.getInstance().COLLAPSE_METHODS; + } return false; } } diff --git a/python/src/com/jetbrains/python/buildout/BuildoutFacetConfiguration.java b/python/src/com/jetbrains/python/buildout/BuildoutFacetConfiguration.java index cfbaa1a46f0b..a97a987fb57e 100644 --- a/python/src/com/jetbrains/python/buildout/BuildoutFacetConfiguration.java +++ b/python/src/com/jetbrains/python/buildout/BuildoutFacetConfiguration.java @@ -109,6 +109,10 @@ public class BuildoutFacetConfiguration implements FacetConfiguration { @Override public void disposeUIResources() { } - } + @Override + public String getHelpTopic() { + return "reference-python-buildout"; + } + } } diff --git a/python/src/com/jetbrains/python/documentation/DocStringReferenceProvider.java b/python/src/com/jetbrains/python/documentation/DocStringReferenceProvider.java index 1a8e9433ea25..ebdb94f8b706 100644 --- a/python/src/com/jetbrains/python/documentation/DocStringReferenceProvider.java +++ b/python/src/com/jetbrains/python/documentation/DocStringReferenceProvider.java @@ -53,7 +53,8 @@ public class DocStringReferenceProvider extends PsiReferenceProvider { @Override public boolean matches(char c) { return Character.isLetterOrDigit(c) || c == '_'; }}.negate(); - if (docString.substring(tagRange.getStartOffset(), tagRange.getEndOffset()).startsWith(":")) { + final String tagName = docString.substring(tagRange.getStartOffset(), tagRange.getEndOffset()); + if (tagName.startsWith(":")) { int ws = CharMatcher.anyOf(" \t*").indexIn(docString, pos+1); if (ws != -1) { int next = CharMatcher.anyOf(" \t*").negate().indexIn(docString, ws); @@ -69,10 +70,9 @@ public class DocStringReferenceProvider extends PsiReferenceProvider { if (endPos < 0) { endPos = docString.length(); } - result.add(new DocStringParameterReference(element, new TextRange(pos, endPos))); - if (docString.substring(tagRange.getStartOffset(), tagRange.getEndOffset()).equals(":type") || - docString.substring(tagRange.getStartOffset(), tagRange.getEndOffset()).equals(":rtype")) { + result.add(new DocStringParameterReference(element, new TextRange(pos, endPos))); + if (tagName.equals(":type") || tagName.equals(":rtype") || tagName.equals("@type") || tagName.equals("@rtype")) { pos = CharMatcher.anyOf(" \t*").negate().indexIn(docString, endPos+1); endPos = CharMatcher.anyOf("\n\r").indexIn(docString, pos+1); if (endPos == -1) diff --git a/python/src/com/jetbrains/python/documentation/SphinxDocString.java b/python/src/com/jetbrains/python/documentation/SphinxDocString.java index 0839ea0cecae..f55b1dd009b5 100644 --- a/python/src/com/jetbrains/python/documentation/SphinxDocString.java +++ b/python/src/com/jetbrains/python/documentation/SphinxDocString.java @@ -85,7 +85,7 @@ public class SphinxDocString extends StructuredDocString { String tagValue = line.substring(tagEnd).trim(); tagValue = StringUtil.replace(tagValue, ":py:class:", ""); tagValue = StringUtil.replace(tagValue, ":class:", ""); - tagValue = tagValue.replaceAll("`(\\w+)`", "$1"); + tagValue = tagValue.replaceAll("`([^`]+)`", "$1"); int pos = tagValue.indexOf(':'); if (pos < 0) return index; String value = tagValue.substring(pos+1).trim(); diff --git a/python/src/com/jetbrains/python/documentation/StructuredDocString.java b/python/src/com/jetbrains/python/documentation/StructuredDocString.java index 6cec55415320..63c111de356b 100644 --- a/python/src/com/jetbrains/python/documentation/StructuredDocString.java +++ b/python/src/com/jetbrains/python/documentation/StructuredDocString.java @@ -23,7 +23,7 @@ public abstract class StructuredDocString { if (text == null) { return null; } - if (text.contains(":param ") || text.contains(":rtype") || text.contains(":type ")) { + if (text.contains(":param ") || text.contains(":rtype") || text.contains(":type")) { return new SphinxDocString(text); } return new EpydocString(text); diff --git a/python/src/com/jetbrains/python/formatter/PyBlock.java b/python/src/com/jetbrains/python/formatter/PyBlock.java index 40169cac6b31..dd61926a9bbc 100644 --- a/python/src/com/jetbrains/python/formatter/PyBlock.java +++ b/python/src/com/jetbrains/python/formatter/PyBlock.java @@ -177,7 +177,7 @@ public class PyBlock implements ASTBlock { childIndent = Indent.getNormalIndent(); } } - else if (parentType == PyElementTypes.PARENTHESIZED_EXPRESSION) { + else if (parentType == PyElementTypes.PARENTHESIZED_EXPRESSION && hasLineBreaksBefore(child, 1)) { childIndent = Indent.getNormalIndent(); } @@ -230,6 +230,9 @@ public class PyBlock implements ASTBlock { PyArgumentList argList = (PyArgumentList) _node.getPsi(); return argList != null && argList.getArguments().length > 1; } + if (child.getElementType() == PyTokenTypes.COMMA) { + return false; + } return true; } @@ -242,7 +245,8 @@ public class PyBlock implements ASTBlock { } private static boolean hasLineBreaksBefore(ASTNode child, int minCount) { - return isWhitespaceWithLineBreaks(TreeUtil.findLastLeaf(child.getTreePrev()), minCount) || + final ASTNode treePrev = child.getTreePrev(); + return (treePrev != null && isWhitespaceWithLineBreaks(TreeUtil.findLastLeaf(treePrev), minCount)) || isWhitespaceWithLineBreaks(child.getFirstChildNode(), minCount); } diff --git a/python/src/com/jetbrains/python/psi/impl/stubs/PyFileStubBuilder.java b/python/src/com/jetbrains/python/psi/impl/stubs/PyFileStubBuilder.java index dbbd8ce73b0a..1294d395497c 100644 --- a/python/src/com/jetbrains/python/psi/impl/stubs/PyFileStubBuilder.java +++ b/python/src/com/jetbrains/python/psi/impl/stubs/PyFileStubBuilder.java @@ -1,12 +1,15 @@ package com.jetbrains.python.psi.impl.stubs; +import com.intellij.lang.ASTNode; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.stubs.DefaultStubBuilder; import com.intellij.psi.stubs.StubElement; +import com.intellij.psi.tree.IElementType; import com.jetbrains.python.psi.PyFile; import com.jetbrains.python.psi.PyIfStatement; import com.jetbrains.python.psi.PyUtil; +import org.jetbrains.annotations.Nullable; /** * @author yole @@ -23,9 +26,17 @@ public class PyFileStubBuilder extends DefaultStubBuilder { @Override protected boolean skipChildProcessingWhenBuildingStubs(PsiElement element, PsiElement child) { - if (element instanceof PyIfStatement) { - return PyUtil.isIfNameEqualsMain((PyIfStatement)element); + return element instanceof PyIfStatement && PyUtil.isIfNameEqualsMain((PyIfStatement)element); + } + + @Override + public boolean skipChildProcessingWhenBuildingStubs(@Nullable ASTNode parent, IElementType childType) { + if (parent != null) { + final PsiElement psi = parent.getPsi(); + if (psi != null) { + return psi instanceof PyIfStatement && PyUtil.isIfNameEqualsMain((PyIfStatement)psi); + } } - return false; + return super.skipChildProcessingWhenBuildingStubs(parent, childType); } } diff --git a/python/src/com/jetbrains/python/refactoring/extractmethod/PyExtractMethodUtil.java b/python/src/com/jetbrains/python/refactoring/extractmethod/PyExtractMethodUtil.java index 4bf01f01988e..aebbc3700cd6 100644 --- a/python/src/com/jetbrains/python/refactoring/extractmethod/PyExtractMethodUtil.java +++ b/python/src/com/jetbrains/python/refactoring/extractmethod/PyExtractMethodUtil.java @@ -460,7 +460,12 @@ public class PyExtractMethodUtil { } }; - final AbstractExtractMethodDialog dialog = new AbstractExtractMethodDialog(project, "method_name", fragment, validator, decorator); + final AbstractExtractMethodDialog dialog = new AbstractExtractMethodDialog(project, "method_name", fragment, validator, decorator) { + @Override + protected String getHelpId() { + return "python.reference.extractMethod"; + } + }; dialog.show(); //return if don`t want to extract method diff --git a/python/src/com/jetbrains/python/sdk/IronPythonSdkFlavor.java b/python/src/com/jetbrains/python/sdk/IronPythonSdkFlavor.java index b6a1698a2054..6598c72b4907 100644 --- a/python/src/com/jetbrains/python/sdk/IronPythonSdkFlavor.java +++ b/python/src/com/jetbrains/python/sdk/IronPythonSdkFlavor.java @@ -4,10 +4,10 @@ import com.intellij.execution.configurations.GeneralCommandLine; import com.intellij.openapi.util.text.StringUtil; import java.io.File; -import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.List; +import java.util.Set; +import java.util.TreeSet; /** * @author yole @@ -20,7 +20,7 @@ public class IronPythonSdkFlavor extends PythonSdkFlavor { @Override public Collection suggestHomePaths() { - List result = new ArrayList(); + Set result = new TreeSet(); String root = System.getenv("ProgramFiles(x86)"); if (root == null) { root = System.getenv("ProgramFiles"); @@ -36,6 +36,8 @@ public class IronPythonSdkFlavor extends PythonSdkFlavor { } } } + WinPythonSdkFlavor.findInPath(result, "ipy.exe"); + WinPythonSdkFlavor.findInPath(result, "ipy64.exe"); return result; } diff --git a/python/src/com/jetbrains/python/sdk/PythonSdkFlavor.java b/python/src/com/jetbrains/python/sdk/PythonSdkFlavor.java index cd5417fdefbe..76b5a5678507 100644 --- a/python/src/com/jetbrains/python/sdk/PythonSdkFlavor.java +++ b/python/src/com/jetbrains/python/sdk/PythonSdkFlavor.java @@ -104,7 +104,7 @@ public abstract class PythonSdkFlavor { } public void initPythonPath(GeneralCommandLine cmd, Collection path) { - addToEnv(cmd, PYTHONPATH, StringUtil.join(path, File.pathSeparator)); + addToEnv(cmd, PYTHONPATH, appendSystemPythonPath(StringUtil.join(path, File.pathSeparator))); } public static void addToEnv(GeneralCommandLine cmd, final String key, String value) { diff --git a/python/src/com/jetbrains/python/structureView/PyFieldsFilter.java b/python/src/com/jetbrains/python/structureView/PyFieldsFilter.java new file mode 100644 index 000000000000..05393d646ba5 --- /dev/null +++ b/python/src/com/jetbrains/python/structureView/PyFieldsFilter.java @@ -0,0 +1,47 @@ +package com.jetbrains.python.structureView; + +import com.intellij.ide.IdeBundle; +import com.intellij.ide.util.treeView.smartTree.ActionPresentation; +import com.intellij.ide.util.treeView.smartTree.ActionPresentationData; +import com.intellij.ide.util.treeView.smartTree.Filter; +import com.intellij.ide.util.treeView.smartTree.TreeElement; +import com.intellij.util.PlatformIcons; +import org.jetbrains.annotations.NotNull; + +/** + * @author vlan + */ +public class PyFieldsFilter implements Filter { + private static final String ID = "SHOW_FIELDS"; + + @Override + public boolean isReverted() { + return true; + } + + @Override + public boolean isVisible(TreeElement treeNode) { + if (treeNode instanceof PyStructureViewElement) { + final PyStructureViewElement sve = (PyStructureViewElement)treeNode; + return !sve.isField(); + } + return true; + } + + @NotNull + @Override + public String getName() { + return ID; + } + + @Override + public String toString() { + return getName(); + } + + @NotNull + @Override + public ActionPresentation getPresentation() { + return new ActionPresentationData(IdeBundle.message("action.structureview.show.fields"), null, PlatformIcons.FIELD_ICON); + } +} diff --git a/python/src/com/jetbrains/python/structureView/PyInheritedMembersFilter.java b/python/src/com/jetbrains/python/structureView/PyInheritedMembersFilter.java new file mode 100644 index 000000000000..ae89e4c39316 --- /dev/null +++ b/python/src/com/jetbrains/python/structureView/PyInheritedMembersFilter.java @@ -0,0 +1,50 @@ +package com.jetbrains.python.structureView; + +import com.intellij.ide.IdeBundle; +import com.intellij.ide.util.treeView.smartTree.ActionPresentation; +import com.intellij.ide.util.treeView.smartTree.ActionPresentationData; +import com.intellij.ide.util.treeView.smartTree.Filter; +import com.intellij.ide.util.treeView.smartTree.TreeElement; +import com.intellij.openapi.util.IconLoader; +import com.jetbrains.python.psi.PyElement; +import org.jetbrains.annotations.NotNull; + +/** + * @author vlan + */ +public class PyInheritedMembersFilter implements Filter { + private static final String ID = "SHOW_INHERITED"; + + @Override + public boolean isReverted() { + return true; + } + + @Override + public boolean isVisible(TreeElement treeNode) { + if (treeNode instanceof PyStructureViewElement) { + final PyStructureViewElement sve = (PyStructureViewElement)treeNode; + return !sve.isInherited(); + } + return true; + } + + @NotNull + @Override + public String getName() { + return ID; + } + + @Override + public String toString() { + return getName(); + } + + @NotNull + @Override + public ActionPresentation getPresentation() { + return new ActionPresentationData(IdeBundle.message("action.structureview.show.inherited"), + null, + IconLoader.getIcon("/hierarchy/supertypes.png")); + } +} diff --git a/python/src/com/jetbrains/python/structureView/PyStructureViewElement.java b/python/src/com/jetbrains/python/structureView/PyStructureViewElement.java index a285357192c4..98b873a81ddf 100644 --- a/python/src/com/jetbrains/python/structureView/PyStructureViewElement.java +++ b/python/src/com/jetbrains/python/structureView/PyStructureViewElement.java @@ -2,18 +2,17 @@ package com.jetbrains.python.structureView; import com.intellij.ide.structureView.StructureViewTreeElement; import com.intellij.navigation.ItemPresentation; +import com.intellij.openapi.editor.colors.CodeInsightColors; import com.intellij.openapi.editor.colors.TextAttributesKey; import com.intellij.openapi.util.Iconable; import com.intellij.psi.PsiElement; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.ui.LayeredIcon; import com.intellij.util.Function; -import com.intellij.util.PlatformIcons; import com.jetbrains.python.PyIcons; import com.jetbrains.python.PyNames; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.impl.ParamHelper; -import com.jetbrains.python.psi.impl.PyBuiltinCache; import org.jetbrains.annotations.Nullable; import javax.swing.*; @@ -32,26 +31,39 @@ public class PyStructureViewElement implements StructureViewTreeElement { NORMAL, // visible INVISIBLE, // not visible: e.g. local to function PRIVATE, // "__foo" in a class + PROTECTED, // "_foo" PREDEFINED // like "__init__"; only if really visible } private PyElement myElement; private Visibility myVisibility; private Icon myIcon; + private boolean myInherited; + private boolean myField; - public PyStructureViewElement(PyElement element, Visibility vis) { + public PyStructureViewElement(PyElement element, Visibility vis, boolean inherited, boolean field) { myElement = element; myVisibility = vis; + myInherited = inherited; + myField = field; } public PyStructureViewElement(PyElement element) { - this(element, Visibility.NORMAL); + this(element, Visibility.NORMAL, false, false); } public PyElement getValue() { return myElement; } + public boolean isInherited() { + return myInherited; + } + + public boolean isField() { + return myField; + } + public void navigate(boolean requestFocus) { myElement.navigate(requestFocus); } @@ -68,46 +80,69 @@ public class PyStructureViewElement implements StructureViewTreeElement { myIcon = icon; } + @Override + public boolean equals(Object o) { + if (o instanceof StructureViewTreeElement) { + final Object value = ((StructureViewTreeElement)o).getValue(); + final String name = myElement.getName(); + if (value instanceof PyElement && name != null) { + return name.equals(((PyElement)value).getName()); + } + } + return false; + } + + @Override + public int hashCode() { + final String name = myElement.getName(); + return name != null ? name.hashCode() : 0; + } + public StructureViewTreeElement[] getChildren() { - final Set childrenElements = new LinkedHashSet(); - myElement.acceptChildren(new PyElementVisitor() { - @Override - public void visitElement(PsiElement element) { - if (element instanceof PyClass || element instanceof PyFunction || - (!(myElement instanceof PyClass) && isWorthyItem(element))) { - childrenElements.add((PyElement)element); - } - else { - element.acceptChildren(this); - } - } - }); - final Collection children = new ArrayList(); - for (PyElement element : childrenElements) { - final Visibility vis; - if (PsiTreeUtil.getParentOfType(element, PyFunction.class) != null) { - // whatever is defined inside a def, is hidden - vis = Visibility.INVISIBLE; - } - else { - vis = getVisibilityByName(element.getName()); - } - final PyStructureViewElement e = new PyStructureViewElement(element, vis); - children.add(e); - if (element instanceof PyClass && element.isValid()) { - PyClass the_exception = PyBuiltinCache.getInstance(element).getClass("Exception"); - final PyClass cls = (PyClass)element; - for (PyClass anc : cls.iterateAncestorClasses()) { - if (anc == the_exception) { - e.setIcon(PlatformIcons.EXCEPTION_CLASS_ICON); - break; - } + final Collection children = new LinkedHashSet(); + for (PyElement e : getElementChildren(myElement)) { + children.add(new PyStructureViewElement(e, getElementVisibility(e), false, elementIsField(e))); + } + if (myElement instanceof PyClass) { + for (PyClass c : ((PyClass)myElement).iterateAncestorClasses()) { + for (PyElement e: getElementChildren(c)) { + children.add(new PyStructureViewElement(e, getElementVisibility(e), true, elementIsField(e))); } } } + return children.toArray(new StructureViewTreeElement[children.size()]); + } + + private static boolean elementIsField(PyElement element) { + return element instanceof PyTargetExpression && PsiTreeUtil.getParentOfType(element, PyClass.class) != null; + } + + private static Visibility getElementVisibility(PyElement element) { + if (!(element instanceof PyTargetExpression) && PsiTreeUtil.getParentOfType(element, PyFunction.class) != null) { + return Visibility.INVISIBLE; + } + else { + return getVisibilityByName(element.getName()); + } + } + + private static Collection getElementChildren(final PyElement element) { + final Collection children = new ArrayList(); + element.acceptChildren(new PyElementVisitor() { + @Override + public void visitElement(PsiElement e) { + if (e instanceof PyClass || e instanceof PyFunction || + (!(element instanceof PyClass) && isWorthyItem(e))) { + children.add((PyElement)e); + } + else { + e.acceptChildren(this); + } + } + }); final Collection attrs = new ArrayList(); - if (myElement instanceof PyClass) { - final PyClass c = (PyClass)myElement; + if (element instanceof PyClass) { + final PyClass c = (PyClass)element; final Comparator comparator = new Comparator() { @Override public int compare(PyTargetExpression e1, PyTargetExpression e2) { @@ -125,19 +160,24 @@ public class PyStructureViewElement implements StructureViewTreeElement { } for (PyTargetExpression e : attrs) { if (e.isValid()) { - children.add(new PyStructureViewElement(e, getVisibilityByName(e.getName()))); + children.add(e); } } - return children.toArray(new StructureViewTreeElement[children.size()]); + return children; } private static Visibility getVisibilityByName(@Nullable String name) { - if (name != null && name.startsWith("__")) { - if (PyNames.UnderscoredAttributes.contains(name)) { - return Visibility.PREDEFINED; + if (name != null) { + if (name.startsWith("__")) { + if (PyNames.UnderscoredAttributes.contains(name)) { + return Visibility.PREDEFINED; + } + else { + return Visibility.PRIVATE; + } } - else { - return Visibility.PRIVATE; + else if (name.startsWith("_")) { + return Visibility.PROTECTED; } } return Visibility.NORMAL; @@ -193,6 +233,9 @@ public class PyStructureViewElement implements StructureViewTreeElement { @Nullable public TextAttributesKey getTextAttributesKey() { + if (isInherited()) { + return CodeInsightColors.NOT_USED_ELEMENT_ATTRIBUTES; + } return null; } @@ -211,13 +254,15 @@ public class PyStructureViewElement implements StructureViewTreeElement { LayeredIcon icon = new LayeredIcon(2); icon.setIcon(normal_icon, 0); Icon overlay = null; - if (myVisibility == Visibility.PRIVATE) { + if (myVisibility == Visibility.PRIVATE || myVisibility == Visibility.PROTECTED) { overlay = PyIcons.PRIVATE; } else if (myVisibility == Visibility.PREDEFINED) { overlay = PyIcons.PREDEFINED; } - else if (myVisibility == Visibility.INVISIBLE) overlay = PyIcons.INVISIBLE; + else if (myVisibility == Visibility.INVISIBLE) { + overlay = PyIcons.INVISIBLE; + } if (overlay != null) { icon.setIcon(overlay, 1); } diff --git a/python/src/com/jetbrains/python/structureView/PyStructureViewModel.java b/python/src/com/jetbrains/python/structureView/PyStructureViewModel.java index 01d2970dbab7..d894abbb2a91 100644 --- a/python/src/com/jetbrains/python/structureView/PyStructureViewModel.java +++ b/python/src/com/jetbrains/python/structureView/PyStructureViewModel.java @@ -3,6 +3,7 @@ package com.jetbrains.python.structureView; import com.intellij.ide.structureView.StructureViewModel; import com.intellij.ide.structureView.StructureViewModelBase; import com.intellij.ide.structureView.StructureViewTreeElement; +import com.intellij.ide.util.treeView.smartTree.Filter; import com.intellij.ide.util.treeView.smartTree.Sorter; import com.intellij.psi.PsiFile; import com.jetbrains.python.psi.*; @@ -34,6 +35,15 @@ public class PyStructureViewModel extends StructureViewModelBase implements Stru return element instanceof PyClass; } + @NotNull + @Override + public Filter[] getFilters() { + return new Filter[] { + new PyInheritedMembersFilter(), + new PyFieldsFilter(), + }; + } + @Override public boolean isAutoExpand(StructureViewTreeElement element) { return element.getValue() instanceof PsiFile; diff --git a/python/testData/folding/docString.py b/python/testData/folding/docString.py new file mode 100644 index 000000000000..05cd8ca5e9c2 --- /dev/null +++ b/python/testData/folding/docString.py @@ -0,0 +1,7 @@ +def foo(): + """ + This is a docstring. + + It spans several lines. + """: + pass \ No newline at end of file diff --git a/python/testData/formatter/tupleAssignment.py b/python/testData/formatter/tupleAssignment.py new file mode 100644 index 000000000000..3218dc78e424 --- /dev/null +++ b/python/testData/formatter/tupleAssignment.py @@ -0,0 +1,8 @@ +def bad_autoformat_example(): + a = 5 + b = 10 + print a, b + (a, b) = b, a + print a, b + a, b = b, a + print a, b \ No newline at end of file diff --git a/python/testData/formatter/tupleAssignment_after.py b/python/testData/formatter/tupleAssignment_after.py new file mode 100644 index 000000000000..3218dc78e424 --- /dev/null +++ b/python/testData/formatter/tupleAssignment_after.py @@ -0,0 +1,8 @@ +def bad_autoformat_example(): + a = 5 + b = 10 + print a, b + (a, b) = b, a + print a, b + a, b = b, a + print a, b \ No newline at end of file diff --git a/python/testData/refactoring/rename/epydocRenameType.py b/python/testData/refactoring/rename/epydocRenameType.py new file mode 100644 index 000000000000..f65ea72b70ab --- /dev/null +++ b/python/testData/refactoring/rename/epydocRenameType.py @@ -0,0 +1,9 @@ +class Xyzzy: + pass + +def foo(p): + """ + @param p: the magic word + @type p: Xyzzy + @return: + """ \ No newline at end of file diff --git a/python/testData/refactoring/rename/epydocRenameType_after.py b/python/testData/refactoring/rename/epydocRenameType_after.py new file mode 100644 index 000000000000..cbe7dee49e0e --- /dev/null +++ b/python/testData/refactoring/rename/epydocRenameType_after.py @@ -0,0 +1,9 @@ +class Shazam: + pass + +def foo(p): + """ + @param p: the magic word + @type p: Shazam + @return: + """ \ No newline at end of file diff --git a/python/testData/structureView/inherited.py b/python/testData/structureView/inherited.py new file mode 100644 index 000000000000..4d456332117c --- /dev/null +++ b/python/testData/structureView/inherited.py @@ -0,0 +1,6 @@ +class C(object): + def f(self, x): + self.x = x + + def __str__(self): + return self.x \ No newline at end of file diff --git a/python/testData/stubs/IfNameMain.py b/python/testData/stubs/IfNameMain.py index 8f5d89eecafa..eee945a80e54 100644 --- a/python/testData/stubs/IfNameMain.py +++ b/python/testData/stubs/IfNameMain.py @@ -1,2 +1,3 @@ if __name__ == '__main__': + import sys xyzzy = None diff --git a/python/testSrc/com/jetbrains/python/PyFoldingTest.java b/python/testSrc/com/jetbrains/python/PyFoldingTest.java index 3d58fe97a467..fd7d2cab82e7 100644 --- a/python/testSrc/com/jetbrains/python/PyFoldingTest.java +++ b/python/testSrc/com/jetbrains/python/PyFoldingTest.java @@ -13,4 +13,8 @@ public class PyFoldingTest extends PyLightFixtureTestCase { public void testClassTrailingSpace() { // PY-2544 doTest(); } + + public void testDocString() { + doTest(); + } } diff --git a/python/testSrc/com/jetbrains/python/PyFormatterTest.java b/python/testSrc/com/jetbrains/python/PyFormatterTest.java index 80e4413d11cd..3bb0f3af12ed 100644 --- a/python/testSrc/com/jetbrains/python/PyFormatterTest.java +++ b/python/testSrc/com/jetbrains/python/PyFormatterTest.java @@ -113,6 +113,10 @@ public class PyFormatterTest extends PyLightFixtureTestCase { doTest(); } + public void testTupleAssignment() { // PY-4034 comment + doTest(); + } + public void testPsiFormatting() { // IDEA-69724 String initial = "def method_name(\n" + diff --git a/python/testSrc/com/jetbrains/python/PyIndentTest.java b/python/testSrc/com/jetbrains/python/PyIndentTest.java index 40f924bdabb7..6149e2775e8f 100644 --- a/python/testSrc/com/jetbrains/python/PyIndentTest.java +++ b/python/testSrc/com/jetbrains/python/PyIndentTest.java @@ -271,6 +271,20 @@ public class PyIndentTest extends PyLightFixtureTestCase { ""); } + public void testNestedLists() { // PY-4034 + doTest("mat = [\n" + + " [1, 2, 3, 4, 4.5],\n" + + " [5, 6, 7, 8],\n" + + " [9, 10, 11, 12],\n" + + "]", + "mat = [\n" + + " [1, 2, 3, 4, 4.5],\n" + + " [5, 6, 7, 8],\n" + + " [9, 10, 11, 12],\n" + + " \n" + + "]"); + } + /* TODO: formatter core problem? public void testAlignListBeforeEquals() throws Exception { diff --git a/python/testSrc/com/jetbrains/python/PyStructureViewTest.java b/python/testSrc/com/jetbrains/python/PyStructureViewTest.java index fac9f3eed1ee..f1c4292c0b51 100644 --- a/python/testSrc/com/jetbrains/python/PyStructureViewTest.java +++ b/python/testSrc/com/jetbrains/python/PyStructureViewTest.java @@ -24,10 +24,12 @@ public class PyStructureViewTest extends PyLightFixtureTestCase { " D1(C)\n" + " D2(C)\n" + " D3(lib1.C)\n" + - " D4(foo.bar.C)\n"); + " D4(foo.bar.C)\n", + false); } - public void testAttributes() { // PY-3371 + // PY-3371 + public void testAttributes() { myFixture.configureByFile(TEST_DIRECTORY + "attributes.py"); doTest("-attributes.py\n" + " -B(object)\n" + @@ -48,13 +50,38 @@ public class PyStructureViewTest extends PyLightFixtureTestCase { " i3\n" + " i4\n" + " i5\n" + - " g2\n"); + " g2\n", + false); } - private void doTest(final String expected) { + // PY-3936 + public void testInherited() { + myFixture.configureByFile(TEST_DIRECTORY + "inherited.py"); + doTest("-inherited.py\n" + + " -C(object)\n" + + " f(self, x)\n" + + " __str__(self)\n" + + " x\n" + + " __delattr__(self, name)\n" + + " __getattribute__(self, name)\n" + + " __hash__(self)\n" + + " __init__(self)\n" + + " __new__(cls, *more)\n" + + " __reduce_ex__(self, *args, **kwargs)\n" + + " __reduce__(self, *args, **kwargs)\n" + + " __repr__(self)\n" + + " __setattr__(self, name, value)\n" + + " __class__\n" + + " __dict__\n" + + " __doc__\n", + true); + } + + private void doTest(final String expected, final boolean inherited) { myFixture.testStructureView(new Consumer() { @Override public void consume(StructureViewComponent component) { + component.setActionActive("SHOW_INHERITED", !inherited); assertTreeEqual(component.getTree(), expected); } }); diff --git a/python/testSrc/com/jetbrains/python/PyStubsTest.java b/python/testSrc/com/jetbrains/python/PyStubsTest.java index 6c022b8477a1..2fbe2d86d478 100644 --- a/python/testSrc/com/jetbrains/python/PyStubsTest.java +++ b/python/testSrc/com/jetbrains/python/PyStubsTest.java @@ -1,6 +1,3 @@ -/* - * @author max - */ package com.jetbrains.python; import com.intellij.openapi.application.Result; @@ -26,6 +23,10 @@ import com.jetbrains.python.toolbox.Maybe; import java.util.Collection; import java.util.List; +/** + * @author max + * @author yole + */ @TestDataPath("$CONTENT_ROOT/../testData/stubs/") public class PyStubsTest extends PyLightFixtureTestCase { @@ -316,7 +317,11 @@ public class PyStubsTest extends PyLightFixtureTestCase { } public void testIfNameMain() { // PY-4008 + final PyFileImpl file = (PyFileImpl) getTestFile(); ensureVariableNotInIndex("xyzzy"); + assertNotParsed(file); + file.acceptChildren(new PyRecursiveElementVisitor()); // assert no error on switching from stub to AST + assertNotNull(file.getTreeElement()); } public void testVariableInComprehension() { // PY-4029 diff --git a/python/testSrc/com/jetbrains/python/refactoring/PyRenameTest.java b/python/testSrc/com/jetbrains/python/refactoring/PyRenameTest.java index 9cc62c0bcb29..4f90304979c0 100644 --- a/python/testSrc/com/jetbrains/python/refactoring/PyRenameTest.java +++ b/python/testSrc/com/jetbrains/python/refactoring/PyRenameTest.java @@ -66,6 +66,10 @@ public class PyRenameTest extends PyLightFixtureTestCase { doTest("bar"); } + public void testEpydocRenameType() { + doTest("Shazam"); + } + public void testRenameGlobalWithoutToplevel() { // PY-3547 doTest("bar"); }