diff options
Diffstat (limited to 'python/src/com/jetbrains/python')
65 files changed, 1953 insertions, 1261 deletions
diff --git a/python/src/com/jetbrains/python/PyBundle.properties b/python/src/com/jetbrains/python/PyBundle.properties index 482daa88b66c..d82e9a6fde62 100644 --- a/python/src/com/jetbrains/python/PyBundle.properties +++ b/python/src/com/jetbrains/python/PyBundle.properties @@ -676,6 +676,9 @@ ANN.tuple.py3=tuple parameter unpacking is not supported in Python 3 ANN.star.import.at.top.only='import *' only allowed at module level +ANN.missing.closing.quote=Missing closing quote [{0}] +ANN.missing.closing.triple.quotes=Missing closing triple quotes + ANN.method.$0.removed.use.$1=Method ''{0}'' has been removed, use ''{1}'' instead ANN.method.$0.removed=Method ''{0}'' removed diff --git a/python/src/com/jetbrains/python/codeInsight/completion/PyKeywordCompletionContributor.java b/python/src/com/jetbrains/python/codeInsight/completion/PyKeywordCompletionContributor.java index 2ecf3b54a8d0..ab13a59ff0b8 100644 --- a/python/src/com/jetbrains/python/codeInsight/completion/PyKeywordCompletionContributor.java +++ b/python/src/com/jetbrains/python/codeInsight/completion/PyKeywordCompletionContributor.java @@ -278,7 +278,7 @@ public class PyKeywordCompletionContributor extends CompletionContributor { private static final PsiElementPattern.Capture<PsiElement> IN_IF_BODY = psiElement().inside(psiElement(PyStatementList.class).inside(psiElement(PyIfPart.class))); - private static final PsiElementPattern.Capture<PsiElement> IN_LOOP = + private static final PsiElementPattern.Capture<PsiElement> IN_LOOP = psiElement().inside(false, psiElement(PyLoopStatement.class), or(psiElement(PyFunction.class), psiElement(PyClass.class))); // not exactly a beauty @@ -645,6 +645,20 @@ public class PyKeywordCompletionContributor extends CompletionContributor { new PyKeywordCompletionProvider(PyNames.FROM)); } + private void addYieldExpression() { + extend(CompletionType.BASIC, + psiElement() + .withLanguage(PythonLanguage.getInstance()) + .andOr(psiElement() + .inside(false, psiElement(PyAssignmentStatement.class), psiElement(PyTargetExpression.class)) + .afterLeaf(psiElement().withElementType(PyTokenTypes.EQ)), + psiElement() + .inside(false, psiElement(PyAugAssignmentStatement.class), psiElement(PyTargetExpression.class)) + .afterLeaf(psiElement().withElementType(PyTokenTypes.AUG_ASSIGN_OPERATIONS)), + psiElement().inside(true, psiElement(PyParenthesizedExpression.class))), + new PyKeywordCompletionProvider(PyNames.YIELD)); + } + private void addYieldFrom() { extend(CompletionType.BASIC, psiElement() @@ -671,6 +685,7 @@ public class PyKeywordCompletionContributor extends CompletionContributor { //addExprIf(); addExprElse(); addRaiseFrom(); + addYieldExpression(); addYieldFrom(); addForToComprehensions(); addInToFor(); diff --git a/python/src/com/jetbrains/python/codeInsight/editorActions/moveUpDown/PyStatementMover.java b/python/src/com/jetbrains/python/codeInsight/editorActions/moveUpDown/PyStatementMover.java index 65cd5e1a79ce..89b6d9bc4a5d 100644 --- a/python/src/com/jetbrains/python/codeInsight/editorActions/moveUpDown/PyStatementMover.java +++ b/python/src/com/jetbrains/python/codeInsight/editorActions/moveUpDown/PyStatementMover.java @@ -108,7 +108,6 @@ public class PyStatementMover extends LineMover { if (moveOutsideFile(document, lineNumber)) return null; int lineEndOffset = document.getLineEndOffset(lineNumber); final int startOffset = document.getLineStartOffset(lineNumber); - lineEndOffset = startOffset != lineEndOffset ? lineEndOffset - 1 : lineEndOffset; final PyStatementList statementList = getStatementList(elementToMove); @@ -119,10 +118,6 @@ public class PyStatementMover extends LineMover { final int startLine = document.getLineNumber(start); final int endLine = document.getLineNumber(end); - if (elementToMove instanceof PsiComment && destination instanceof PsiComment) { - return new LineRange(lineNumber, lineNumber + 1); - } - if (elementToMove instanceof PyClass || elementToMove instanceof PyFunction) { PyElement scope = statementList == null ? (PyElement)elementToMove.getContainingFile() : statementList; if (destination != null) @@ -137,6 +132,11 @@ public class PyStatementMover extends LineMover { scopeRange = moveInto(elementToMove, file, editor, down, lineEndOffset); if (scopeRange != null) return scopeRange; + if (elementToMove instanceof PsiComment && ( PsiTreeUtil.isAncestor(destination, elementToMove, true)) || + destination instanceof PsiComment) { + return new LineRange(lineNumber, lineNumber + 1); + } + final PyElement scope = statementList == null ? (PyElement)elementToMove.getContainingFile() : statementList; if ((elementToMove instanceof PyClass) || (elementToMove instanceof PyFunction)) return new ScopeRange(scope, scope.getFirstChild(), !down, true); @@ -185,7 +185,6 @@ public class PyStatementMover extends LineMover { if (sibling != null) { final PyStatementList list = sibling.getStatementList(); - assert list != null; return new ScopeRange(list, down ? list.getFirstChild() : list.getLastChild(), !addBefore); } else { @@ -278,11 +277,23 @@ public class PyStatementMover extends LineMover { private static PsiElement getDestinationElement(@NotNull final PsiElement elementToMove, @NotNull final Document document, int lineEndOffset, boolean down) { - PsiElement destination = elementToMove.getContainingFile().findElementAt(lineEndOffset); - if (destination == null) return null; - if (destination instanceof PsiComment) return destination; + PsiElement destination = PyUtil.findPrevAtOffset(elementToMove.getContainingFile(), lineEndOffset, PsiWhiteSpace.class); PsiElement sibling = down ? PsiTreeUtil.getNextSiblingOfType(elementToMove, PyStatement.class) : - PsiTreeUtil.getPrevSiblingOfType(elementToMove, PyStatement.class); + PsiTreeUtil.getPrevSiblingOfType(elementToMove, PyStatement.class); + if (destination == null) { + if (elementToMove instanceof PyClass) { + destination = sibling; + } + else if (elementToMove instanceof PyFunction) { + if (!(sibling instanceof PyClass)) + destination = sibling; + else destination = null; + } + else { + return null; + } + } + if (destination instanceof PsiComment) return destination; if (elementToMove instanceof PyClass) { destination = sibling; } diff --git a/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyWithFixer.java b/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyWithFixer.java index ec236d9242f5..b2b71cb9e0d2 100644 --- a/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyWithFixer.java +++ b/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyWithFixer.java @@ -18,6 +18,7 @@ package com.jetbrains.python.codeInsight.editorActions.smartEnter.fixers; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.psi.PsiElement; +import com.intellij.util.ArrayUtil; import com.intellij.util.IncorrectOperationException; import com.jetbrains.python.PyTokenTypes; import com.jetbrains.python.codeInsight.editorActions.smartEnter.PySmartEnterProcessor; @@ -27,8 +28,6 @@ import com.jetbrains.python.psi.PyWithItem; import com.jetbrains.python.psi.PyWithStatement; import org.jetbrains.annotations.NotNull; -import static com.jetbrains.python.psi.PyUtil.sure; - /** * @author Mikhail Golubev */ @@ -42,11 +41,10 @@ public class PyWithFixer extends PyFixer<PyWithStatement> { final PsiElement colonToken = PyUtil.getFirstChildOfType(withStatement, PyTokenTypes.COLON); final PsiElement withToken = PyUtil.getFirstChildOfType(withStatement, PyTokenTypes.WITH_KEYWORD); final Document document = editor.getDocument(); - if (colonToken == null) { - int insertAt = sure(withToken).getTextRange().getEndOffset(); + if (colonToken == null && withToken != null) { + int insertAt = withToken.getTextRange().getEndOffset(); String textToInsert = ":"; - final PyWithItem[] withItems = withStatement.getWithItems(); - final PyWithItem lastItem = withItems.length != 0 ? withItems[withItems.length - 1] : null; + final PyWithItem lastItem = ArrayUtil.getLastElement(withStatement.getWithItems()); if (lastItem == null || lastItem.getExpression() == null) { textToInsert = " :"; processor.registerUnresolvedError(insertAt + 1); diff --git a/python/src/com/jetbrains/python/configuration/PyIntegratedToolsConfigurable.java b/python/src/com/jetbrains/python/configuration/PyIntegratedToolsConfigurable.java index fa869bfca359..e9585a5cd952 100644 --- a/python/src/com/jetbrains/python/configuration/PyIntegratedToolsConfigurable.java +++ b/python/src/com/jetbrains/python/configuration/PyIntegratedToolsConfigurable.java @@ -156,7 +156,7 @@ public class PyIntegratedToolsConfigurable implements SearchableConfigurable, No return new FacetConfigurationQuickFix() { @Override public void run(JComponent place) { - final PyPackageManagerImpl.UI ui = new PyPackageManagerImpl.UI(myProject, sdk, new PyPackageManagerImpl.UI.Listener() { + final PyPackageManagerUI ui = new PyPackageManagerUI(myProject, sdk, new PyPackageManagerUI.Listener() { @Override public void started() {} diff --git a/python/src/com/jetbrains/python/console/PydevConsoleCommunication.java b/python/src/com/jetbrains/python/console/PydevConsoleCommunication.java index 9c95b1cffda8..5bf06782d376 100644 --- a/python/src/com/jetbrains/python/console/PydevConsoleCommunication.java +++ b/python/src/com/jetbrains/python/console/PydevConsoleCommunication.java @@ -31,10 +31,7 @@ import com.intellij.util.Function; import com.intellij.xdebugger.frame.XValueChildrenList; import com.jetbrains.python.console.parsing.PythonConsoleData; import com.jetbrains.python.console.pydev.*; -import com.jetbrains.python.debugger.PyDebugValue; -import com.jetbrains.python.debugger.PyDebuggerException; -import com.jetbrains.python.debugger.PyFrameAccessor; -import com.jetbrains.python.debugger.PydevXmlUtils; +import com.jetbrains.python.debugger.*; import com.jetbrains.python.debugger.pydev.GetVariableCommand; import com.jetbrains.python.debugger.pydev.ProtocolParser; import org.apache.xmlrpc.WebServer; @@ -528,6 +525,11 @@ public class PydevConsoleCommunication extends AbstractConsoleCommunication impl } } + @Nullable + @Override + public PyReferrersLoader getReferrersLoader() { + return null; + } /** * Request that pydevconsole connect (with pydevd) to the specified port diff --git a/python/src/com/jetbrains/python/console/PythonDebugConsoleCommunication.java b/python/src/com/jetbrains/python/console/PythonDebugConsoleCommunication.java index da1ac53113ef..5f361b30228a 100644 --- a/python/src/com/jetbrains/python/console/PythonDebugConsoleCommunication.java +++ b/python/src/com/jetbrains/python/console/PythonDebugConsoleCommunication.java @@ -23,7 +23,7 @@ import com.jetbrains.python.console.pydev.InterpreterResponse; import com.jetbrains.python.console.pydev.PydevCompletionVariant; import com.jetbrains.python.debugger.PyDebugProcess; import com.jetbrains.python.debugger.PyDebuggerException; -import com.jetbrains.python.debugger.pydev.ProcessDebugger; +import com.jetbrains.python.debugger.pydev.PyDebugCallback; import org.jetbrains.annotations.NotNull; import java.util.List; @@ -63,8 +63,8 @@ public class PythonDebugConsoleCommunication extends AbstractConsoleCommunicatio return false; } - protected void exec(final ConsoleCodeFragment command, final ProcessDebugger.DebugCallback<Pair<String, Boolean>> callback) { - myDebugProcess.consoleExec(command.getText(), new ProcessDebugger.DebugCallback<String>() { + protected void exec(final ConsoleCodeFragment command, final PyDebugCallback<Pair<String, Boolean>> callback) { + myDebugProcess.consoleExec(command.getText(), new PyDebugCallback<String>() { @Override public void ok(String value) { callback.ok(parseExecResponseString(value)); @@ -79,7 +79,7 @@ public class PythonDebugConsoleCommunication extends AbstractConsoleCommunicatio public void execInterpreter(ConsoleCodeFragment code, final Function<InterpreterResponse, Object> callback) { myExpression.append(code.getText()); - exec(new ConsoleCodeFragment(myExpression.toString(), false), new ProcessDebugger.DebugCallback<Pair<String, Boolean>>() { + exec(new ConsoleCodeFragment(myExpression.toString(), false), new PyDebugCallback<Pair<String, Boolean>>() { @Override public void ok(Pair<String, Boolean> executed) { boolean more = executed.second; diff --git a/python/src/com/jetbrains/python/debugger/PyDebugProcess.java b/python/src/com/jetbrains/python/debugger/PyDebugProcess.java index cdea41298a4e..ea492e43cf2d 100644 --- a/python/src/com/jetbrains/python/debugger/PyDebugProcess.java +++ b/python/src/com/jetbrains/python/debugger/PyDebugProcess.java @@ -17,7 +17,6 @@ package com.jetbrains.python.debugger; import com.google.common.collect.Lists; import com.google.common.collect.Maps; -import com.intellij.execution.console.DuplexConsoleView; import com.intellij.execution.process.ProcessEvent; import com.intellij.execution.process.ProcessHandler; import com.intellij.execution.process.ProcessListener; @@ -88,6 +87,7 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr private boolean myWaitingForConnection = false; private PyStackFrame myStackFrameBeforeResume; private PyStackFrame myConsoleContextFrame = null; + private PyReferrersLoader myReferrersProvider; public PyDebugProcess(final @NotNull XDebugSession session, @NotNull final ServerSocket serverSocket, @@ -489,7 +489,7 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr return myDebugger.evaluate(frame.getThreadId(), frame.getFrameId(), expression, execute, trimResult); } - public void consoleExec(String command, ProcessDebugger.DebugCallback<String> callback) { + public void consoleExec(String command, PyDebugCallback<String> callback) { dropFrameCaches(); try { final PyStackFrame frame = currentFrame(); @@ -539,6 +539,17 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr } @Override + public void loadReferrers(PyReferringObjectsValue var, PyDebugCallback<XValueChildrenList> callback) { + try { + final PyStackFrame frame = currentFrame(); + myDebugger.loadReferrers(frame.getThreadId(), frame.getFrameId(), var, callback); + } + catch (PyDebuggerException e) { + callback.error(e); + } + } + + @Override public void changeVariable(final PyDebugValue var, final String value) throws PyDebuggerException { final PyStackFrame frame = currentFrame(); PyDebugValue newValue = myDebugger.changeVariable(frame.getThreadId(), frame.getFrameId(), var, value); @@ -546,14 +557,23 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr } @Nullable + @Override + public PyReferrersLoader getReferrersLoader() { + if (myReferrersProvider == null) { + myReferrersProvider = new PyReferrersLoader(this); + } + return myReferrersProvider; + } + + @Nullable public String loadSource(String path) { return myDebugger.loadSource(path); } @Override - public boolean isVariable(String name) { + public boolean canSaveToTemp(String name) { final Project project = getSession().getProject(); - return PyDebugSupportUtils.isVariable(project, name); + return PyDebugSupportUtils.canSaveToTemp(project, name); } private PyStackFrame currentFrame() throws PyDebuggerException { diff --git a/python/src/com/jetbrains/python/debugger/PyDebugSupportUtils.java b/python/src/com/jetbrains/python/debugger/PyDebugSupportUtils.java index d29259d559c6..d25596a13f2a 100644 --- a/python/src/com/jetbrains/python/debugger/PyDebugSupportUtils.java +++ b/python/src/com/jetbrains/python/debugger/PyDebugSupportUtils.java @@ -80,25 +80,29 @@ public class PyDebugSupportUtils { element instanceof PyNamedParameter; } - // is expression a variable reference + // is expression a variable reference and can be evaluated // todo: use patterns (?) - public static boolean isVariable(final Project project, final String expression) { + public static boolean canSaveToTemp(final Project project, final String expression) { return ApplicationManager.getApplication().runReadAction(new Computable<Boolean>() { public Boolean compute() { final PsiFile file = PyElementGenerator.getInstance(project).createDummyFile(LanguageLevel.getDefault(), expression); final PsiElement root = file.getFirstChild(); - return root instanceof PyExpressionStatement && - root.getFirstChild() instanceof PyReferenceExpression && - root.getFirstChild() == root.getLastChild() && - root.getFirstChild().getFirstChild() != null && - root.getFirstChild().getFirstChild().getNode().getElementType() == PyTokenTypes.IDENTIFIER && - root.getFirstChild().getFirstChild() == root.getFirstChild().getLastChild() && - root.getFirstChild().getFirstChild().getFirstChild() == null; + return !isVariable(root) && (root instanceof PyExpressionStatement); } }); } + private static Boolean isVariable(PsiElement root) { + return root instanceof PyExpressionStatement && + root.getFirstChild() instanceof PyReferenceExpression && + root.getFirstChild() == root.getLastChild() && + root.getFirstChild().getFirstChild() != null && + root.getFirstChild().getFirstChild().getNode().getElementType() == PyTokenTypes.IDENTIFIER && + root.getFirstChild().getFirstChild() == root.getFirstChild().getLastChild() && + root.getFirstChild().getFirstChild().getFirstChild() == null; + } + @Nullable private static String getLineText(@NotNull Document document, int line) { if (line > 0 && line < document.getLineCount()) { diff --git a/python/src/com/jetbrains/python/debugger/PyExceptionBreakpointProperties.java b/python/src/com/jetbrains/python/debugger/PyExceptionBreakpointProperties.java index d6bfd13f488c..93f9d89e386c 100644 --- a/python/src/com/jetbrains/python/debugger/PyExceptionBreakpointProperties.java +++ b/python/src/com/jetbrains/python/debugger/PyExceptionBreakpointProperties.java @@ -39,6 +39,7 @@ public class PyExceptionBreakpointProperties extends ExceptionBreakpointProperti public PyExceptionBreakpointProperties(@NotNull final String exception) { myException = exception; + myNotifyOnTerminate = true; } @Override @@ -78,6 +79,10 @@ public class PyExceptionBreakpointProperties extends ExceptionBreakpointProperti myNotifyOnlyOnFirst = notifyOnlyOnFirst; } + public String getException() { + return "python-" + myException; + } + @Override public ExceptionBreakpointCommand createAddCommand(RemoteDebugger debugger) { return ExceptionBreakpointCommand.addExceptionBreakpointCommand(debugger, getException(), diff --git a/python/src/com/jetbrains/python/documentation/PythonDocumentationProvider.java b/python/src/com/jetbrains/python/documentation/PythonDocumentationProvider.java index 913ed65f1e53..73a0cb604091 100644 --- a/python/src/com/jetbrains/python/documentation/PythonDocumentationProvider.java +++ b/python/src/com/jetbrains/python/documentation/PythonDocumentationProvider.java @@ -81,7 +81,16 @@ public class PythonDocumentationProvider extends AbstractDocumentationProvider i @NonNls private static final String EPYDOC_PREFIX = "@"; // provides ctrl+hover info - public String getQuickNavigateInfo(final PsiElement element, PsiElement originalElement) { + @Override + @Nullable + public String getQuickNavigateInfo(final PsiElement element, final PsiElement originalElement) { + for (final PythonDocumentationQuickInfoProvider point : PythonDocumentationQuickInfoProvider.EP_NAME.getExtensions()) { + String info = point.getQuickInfo(originalElement); + if (info != null) { + return info; + } + } + if (element instanceof PyFunction) { PyFunction func = (PyFunction)element; StringBuilder cat = new StringBuilder(); @@ -637,8 +646,9 @@ public class PythonDocumentationProvider extends AbstractDocumentationProvider i String raiseTarget = visitor.myRaiseTarget.getText(); if (visitor.myRaiseTarget instanceof PyCallExpression) { final PyExpression callee = ((PyCallExpression)visitor.myRaiseTarget).getCallee(); - if (callee != null) + if (callee != null) { raiseTarget = callee.getText(); + } } builder.append(" ").append(raiseTarget); } diff --git a/python/src/com/jetbrains/python/findUsages/PyFunctionFindUsagesHandler.java b/python/src/com/jetbrains/python/findUsages/PyFunctionFindUsagesHandler.java index 8c96916c9d80..fa7cf1b429c4 100644 --- a/python/src/com/jetbrains/python/findUsages/PyFunctionFindUsagesHandler.java +++ b/python/src/com/jetbrains/python/findUsages/PyFunctionFindUsagesHandler.java @@ -27,12 +27,12 @@ import java.util.List; public class PyFunctionFindUsagesHandler extends FindUsagesHandler { private final List<PsiElement> myAllElements; - protected PyFunctionFindUsagesHandler(@NotNull PsiElement psiElement) { + public PyFunctionFindUsagesHandler(@NotNull PsiElement psiElement) { super(psiElement); myAllElements = null; } - protected PyFunctionFindUsagesHandler(@NotNull PsiElement psiElement, List<PsiElement> allElements) { + public PyFunctionFindUsagesHandler(@NotNull PsiElement psiElement, List<PsiElement> allElements) { super(psiElement); myAllElements = allElements; } diff --git a/python/src/com/jetbrains/python/hierarchy/PyTypeHierarchyNodeDescriptor.java b/python/src/com/jetbrains/python/hierarchy/PyHierarchyNodeDescriptor.java index ea8c250b2e4f..c9720fb01af3 100644 --- a/python/src/com/jetbrains/python/hierarchy/PyTypeHierarchyNodeDescriptor.java +++ b/python/src/com/jetbrains/python/hierarchy/PyHierarchyNodeDescriptor.java @@ -19,15 +19,14 @@ import com.intellij.ide.IdeBundle; import com.intellij.ide.hierarchy.HierarchyNodeDescriptor; import com.intellij.ide.util.treeView.NodeDescriptor; import com.intellij.navigation.ItemPresentation; -import com.intellij.openapi.editor.markup.TextAttributes; import com.intellij.openapi.roots.ui.util.CompositeAppearance; import com.intellij.openapi.util.Comparing; import com.intellij.psi.NavigatablePsiElement; import com.intellij.psi.PsiElement; import com.jetbrains.python.psi.PyClass; +import com.jetbrains.python.psi.PyFunction; import org.jetbrains.annotations.NotNull; - -import java.awt.*; +import org.jetbrains.annotations.Nullable; /** * Created by IntelliJ IDEA. @@ -35,14 +34,14 @@ import java.awt.*; * Date: Jul 31, 2009 * Time: 6:26:37 PM */ -public class PyTypeHierarchyNodeDescriptor extends HierarchyNodeDescriptor { - - public PyTypeHierarchyNodeDescriptor(final NodeDescriptor parentDescriptor, @NotNull final PsiElement element, final boolean isBase) { +public class PyHierarchyNodeDescriptor extends HierarchyNodeDescriptor { + public PyHierarchyNodeDescriptor(final NodeDescriptor parentDescriptor, @NotNull final PsiElement element, final boolean isBase) { super(element.getProject(), parentDescriptor, element, isBase); } - public PyClass getClassElement() { - return (PyClass)myElement; + @Nullable + public PsiElement getPsiElement() { + return myElement; } public boolean isValid() { @@ -55,10 +54,6 @@ public class PyTypeHierarchyNodeDescriptor extends HierarchyNodeDescriptor { final CompositeAppearance oldText = myHighlightedText; myHighlightedText = new CompositeAppearance(); - TextAttributes classNameAttributes = null; - if (myColor != null) { - classNameAttributes = new TextAttributes(myColor, null, null, null, Font.PLAIN); - } NavigatablePsiElement element = (NavigatablePsiElement)myElement; if (element == null) { @@ -71,10 +66,14 @@ public class PyTypeHierarchyNodeDescriptor extends HierarchyNodeDescriptor { final ItemPresentation presentation = element.getPresentation(); if (presentation != null) { - final PyClass cl = getClassElement(); - myHighlightedText.getEnding().addText(cl.getName(), classNameAttributes); - myHighlightedText.getEnding() - .addText(" (" + cl.getContainingFile().getName() + ")", HierarchyNodeDescriptor.getPackageNameAttributes()); + if (element instanceof PyFunction) { + final PyClass cls = ((PyFunction)element).getContainingClass(); + if (cls != null) { + myHighlightedText.getEnding().addText(cls.getName() + "."); + } + } + myHighlightedText.getEnding().addText(presentation.getPresentableText()); + myHighlightedText.getEnding().addText(" " + presentation.getLocationString(), HierarchyNodeDescriptor.getPackageNameAttributes()); } myName = myHighlightedText.getText(); diff --git a/python/src/com/jetbrains/python/hierarchy/PyTypeHierarchyBrowser.java b/python/src/com/jetbrains/python/hierarchy/PyTypeHierarchyBrowser.java index 643440571618..817d462044fe 100644 --- a/python/src/com/jetbrains/python/hierarchy/PyTypeHierarchyBrowser.java +++ b/python/src/com/jetbrains/python/hierarchy/PyTypeHierarchyBrowser.java @@ -52,10 +52,10 @@ public class PyTypeHierarchyBrowser extends TypeHierarchyBrowserBase { @Nullable protected PsiElement getElementFromDescriptor(@NotNull HierarchyNodeDescriptor descriptor) { - if (!(descriptor instanceof PyTypeHierarchyNodeDescriptor)) { + if (!(descriptor instanceof PyHierarchyNodeDescriptor)) { return null; } - return ((PyTypeHierarchyNodeDescriptor)descriptor).getClassElement(); + return ((PyHierarchyNodeDescriptor)descriptor).getPsiElement(); } protected void createTrees(@NotNull Map<String, JTree> trees) { diff --git a/python/src/com/jetbrains/python/hierarchy/call/PyCallHierarchyBrowser.java b/python/src/com/jetbrains/python/hierarchy/call/PyCallHierarchyBrowser.java new file mode 100644 index 000000000000..c5224e617ec9 --- /dev/null +++ b/python/src/com/jetbrains/python/hierarchy/call/PyCallHierarchyBrowser.java @@ -0,0 +1,101 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jetbrains.python.hierarchy.call; + +import com.intellij.ide.hierarchy.CallHierarchyBrowserBase; +import com.intellij.ide.hierarchy.HierarchyNodeDescriptor; +import com.intellij.ide.hierarchy.HierarchyTreeStructure; +import com.intellij.ide.util.treeView.NodeDescriptor; +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.psi.PsiElement; +import com.intellij.ui.PopupHandler; +import com.jetbrains.python.hierarchy.PyHierarchyNodeDescriptor; +import com.jetbrains.python.hierarchy.PyHierarchyUtils; +import com.jetbrains.python.psi.PyClass; +import com.jetbrains.python.psi.PyFile; +import com.jetbrains.python.psi.PyFunction; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.util.Comparator; +import java.util.Map; + +/** + * @author novokrest + */ +public class PyCallHierarchyBrowser extends CallHierarchyBrowserBase { + private static final Logger LOG = Logger.getInstance("#com.jetbrains.python.hierarchy.call.PyCallHierarchyBrowser"); + private static final String GROUP_PY_CALL_HIERARCHY_POPUP = "PyCallHierarchyPopupMenu"; + + public PyCallHierarchyBrowser(PsiElement function) { + super(function.getProject(), function); + } + + @Nullable + @Override + protected PsiElement getElementFromDescriptor(@NotNull HierarchyNodeDescriptor descriptor) { + if (descriptor instanceof PyHierarchyNodeDescriptor) { + PyHierarchyNodeDescriptor pyDescriptor = (PyHierarchyNodeDescriptor)descriptor; + return pyDescriptor.getPsiElement(); + } + return null; + } + + @Override + protected void createTrees(@NotNull Map<String, JTree> type2TreeMap) { + final ActionGroup group = (ActionGroup)ActionManager.getInstance().getAction(GROUP_PY_CALL_HIERARCHY_POPUP); + + final JTree callerTree = createHierarchyTree(group); + final JTree calleeTree = createHierarchyTree(group); + + type2TreeMap.put(CALLER_TYPE, callerTree); + type2TreeMap.put(CALLEE_TYPE, calleeTree); + } + + private JTree createHierarchyTree(ActionGroup group) { + final JTree tree = createTree(false); + PopupHandler.installPopupHandler(tree, group, ActionPlaces.CALL_HIERARCHY_VIEW_POPUP, ActionManager.getInstance()); + return tree; + } + + @Override + protected boolean isApplicableElement(@NotNull PsiElement element) { + return element instanceof PyFunction || element instanceof PyClass || element instanceof PyFile; + } + + @Nullable + @Override + protected HierarchyTreeStructure createHierarchyTreeStructure(@NotNull String typeName, @NotNull PsiElement psiElement) { + if (CALLER_TYPE.equals(typeName)) { + return new PyCallerFunctionTreeStructure(myProject, psiElement, getCurrentScopeType()); + } + else if (CALLEE_TYPE.equals(typeName)) { + return new PyCalleeFunctionTreeStructure(myProject, psiElement, getCurrentScopeType()); + } + else { + LOG.error("unexpected type: " + typeName); + return null; + } + } + + @Nullable + @Override + protected Comparator<NodeDescriptor> getComparator() { + return PyHierarchyUtils.getComparator(myProject); + } +} diff --git a/python/src/com/jetbrains/python/hierarchy/call/PyCallHierarchyProvider.java b/python/src/com/jetbrains/python/hierarchy/call/PyCallHierarchyProvider.java new file mode 100644 index 000000000000..67c0d6ddfa0b --- /dev/null +++ b/python/src/com/jetbrains/python/hierarchy/call/PyCallHierarchyProvider.java @@ -0,0 +1,76 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jetbrains.python.hierarchy.call; + +import com.intellij.codeInsight.TargetElementUtilBase; +import com.intellij.ide.hierarchy.CallHierarchyBrowserBase; +import com.intellij.ide.hierarchy.HierarchyBrowser; +import com.intellij.ide.hierarchy.HierarchyProvider; +import com.intellij.openapi.actionSystem.CommonDataKeys; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.util.PsiTreeUtil; +import com.jetbrains.python.psi.PyClass; +import com.jetbrains.python.psi.PyFile; +import com.jetbrains.python.psi.PyFunction; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author novokrest + */ +public class PyCallHierarchyProvider implements HierarchyProvider { + @Nullable + @Override + public PsiElement getTarget(@NotNull DataContext dataContext) { + Project project = CommonDataKeys.PROJECT.getData(dataContext); + if (project == null) return null; + + PsiElement element = CommonDataKeys.PSI_ELEMENT.getData(dataContext); + if (element == null) { + Editor editor = CommonDataKeys.EDITOR.getData(dataContext); + if (editor != null) { + PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument()); + if (file == null) return null; + + element = TargetElementUtilBase.findTargetElement(editor, TargetElementUtilBase.ELEMENT_NAME_ACCEPTED | + TargetElementUtilBase.REFERENCED_ELEMENT_ACCEPTED | + TargetElementUtilBase.LOOKUP_ITEM_ACCEPTED); + if (element instanceof PyFunction || element instanceof PyClass || element instanceof PyFile) { + return element; + } + + element = file.findElementAt(editor.getCaretModel().getOffset()); + } + } + return PsiTreeUtil.getNonStrictParentOfType(element, PyFunction.class, PyClass.class, PyFile.class); + } + + @NotNull + @Override + public HierarchyBrowser createHierarchyBrowser(PsiElement target) { + return new PyCallHierarchyBrowser(target); + } + + @Override + public void browserActivated(@NotNull HierarchyBrowser hierarchyBrowser) { + ((PyCallHierarchyBrowser)hierarchyBrowser).changeView(CallHierarchyBrowserBase.CALLER_TYPE); + } +} diff --git a/python/src/com/jetbrains/python/hierarchy/call/PyCallHierarchyTreeStructureBase.java b/python/src/com/jetbrains/python/hierarchy/call/PyCallHierarchyTreeStructureBase.java new file mode 100644 index 000000000000..c5d60138299c --- /dev/null +++ b/python/src/com/jetbrains/python/hierarchy/call/PyCallHierarchyTreeStructureBase.java @@ -0,0 +1,85 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jetbrains.python.hierarchy.call; + +import com.intellij.ide.hierarchy.HierarchyNodeDescriptor; +import com.intellij.ide.hierarchy.HierarchyTreeStructure; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.intellij.util.ArrayUtil; +import com.jetbrains.python.hierarchy.PyHierarchyNodeDescriptor; +import com.jetbrains.python.psi.PyClass; +import com.jetbrains.python.psi.PyElement; +import com.jetbrains.python.psi.PyFile; +import com.jetbrains.python.psi.PyFunction; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +/** + * @author novokrest + */ +public abstract class PyCallHierarchyTreeStructureBase extends HierarchyTreeStructure { + private final String myScopeType; + + public PyCallHierarchyTreeStructureBase(Project project, PsiElement element, String currentScopeType) { + super(project, new PyHierarchyNodeDescriptor(null, element, true)); + myScopeType = currentScopeType; + } + + @NotNull + protected abstract List<PsiElement> getChildren(@NotNull PyElement element); + + @NotNull + @Override + protected Object[] buildChildren(@NotNull HierarchyNodeDescriptor descriptor) { + final List<PyHierarchyNodeDescriptor> descriptors = new ArrayList<PyHierarchyNodeDescriptor>(); + if (descriptor instanceof PyHierarchyNodeDescriptor) { + final PyHierarchyNodeDescriptor pyDescriptor = (PyHierarchyNodeDescriptor)descriptor; + final PsiElement element = pyDescriptor.getPsiElement(); + final boolean isCallable = element instanceof PyFunction || element instanceof PyClass || element instanceof PyFile; + HierarchyNodeDescriptor nodeDescriptor = getBaseDescriptor(); + if (!(element instanceof PyElement) || !isCallable || nodeDescriptor == null) { + return ArrayUtil.EMPTY_OBJECT_ARRAY; + } + + final List<PsiElement> children = getChildren((PyElement)element); + + final HashMap<PsiElement, PyHierarchyNodeDescriptor> callerToDescriptorMap = new HashMap<PsiElement, PyHierarchyNodeDescriptor>(); + PsiElement baseClass = element instanceof PyFunction ? ((PyFunction)element).getContainingClass() : null; + + for (PsiElement caller : children) { + if (isInScope(baseClass, caller, myScopeType)) { + PyHierarchyNodeDescriptor callerDescriptor = callerToDescriptorMap.get(caller); + if (callerDescriptor == null) { + callerDescriptor = new PyHierarchyNodeDescriptor(descriptor, caller, false); + callerToDescriptorMap.put(caller, callerDescriptor); + descriptors.add(callerDescriptor); + } + } + } + + } + return ArrayUtil.toObjectArray(descriptors); + } + + @Override + public boolean isAlwaysShowPlus() { + return true; + } +} diff --git a/python/src/com/jetbrains/python/hierarchy/call/PyCalleeFunctionTreeStructure.java b/python/src/com/jetbrains/python/hierarchy/call/PyCalleeFunctionTreeStructure.java new file mode 100644 index 000000000000..b0465083ef05 --- /dev/null +++ b/python/src/com/jetbrains/python/hierarchy/call/PyCalleeFunctionTreeStructure.java @@ -0,0 +1,42 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jetbrains.python.hierarchy.call; + +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.jetbrains.python.psi.PyElement; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author novokrest + */ +public class PyCalleeFunctionTreeStructure extends PyCallHierarchyTreeStructureBase { + public PyCalleeFunctionTreeStructure(Project project, PsiElement element, String currentScopeType) { + super(project, element, currentScopeType); + } + + @NotNull + @Override + protected List<PsiElement> getChildren(@NotNull PyElement element) { + final List<PsiElement> callees = new ArrayList<PsiElement>(); + // TODO: Add callees from the dynamic call data manager + callees.addAll(PyStaticCallHierarchyUtil.getCallees(element)); + return callees; + } +} diff --git a/python/src/com/jetbrains/python/hierarchy/call/PyCallerFunctionTreeStructure.java b/python/src/com/jetbrains/python/hierarchy/call/PyCallerFunctionTreeStructure.java new file mode 100644 index 000000000000..d45e79ce8d95 --- /dev/null +++ b/python/src/com/jetbrains/python/hierarchy/call/PyCallerFunctionTreeStructure.java @@ -0,0 +1,42 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jetbrains.python.hierarchy.call; + +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.jetbrains.python.psi.PyElement; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author novokrest + */ +public class PyCallerFunctionTreeStructure extends PyCallHierarchyTreeStructureBase { + public PyCallerFunctionTreeStructure(Project project, PsiElement element, String currentScopeType) { + super(project, element, currentScopeType); + } + + @NotNull + @Override + protected List<PsiElement> getChildren(@NotNull PyElement element) { + final List<PsiElement> callers = new ArrayList<PsiElement>(); + // TODO: Add callers from the dynamic call data manager + callers.addAll(PyStaticCallHierarchyUtil.getCallers(element)); + return callers; + } +} diff --git a/python/src/com/jetbrains/python/hierarchy/call/PyStaticCallHierarchyUtil.java b/python/src/com/jetbrains/python/hierarchy/call/PyStaticCallHierarchyUtil.java new file mode 100644 index 000000000000..5657dcb589e9 --- /dev/null +++ b/python/src/com/jetbrains/python/hierarchy/call/PyStaticCallHierarchyUtil.java @@ -0,0 +1,163 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jetbrains.python.hierarchy.call; + +import com.google.common.collect.Lists; +import com.intellij.find.findUsages.FindUsagesHandler; +import com.intellij.find.findUsages.FindUsagesOptions; +import com.intellij.psi.PsiElement; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.usageView.UsageInfo; +import com.intellij.util.ArrayUtil; +import com.intellij.util.CommonProcessors; +import com.jetbrains.python.PyNames; +import com.jetbrains.python.findUsages.PyClassFindUsagesHandler; +import com.jetbrains.python.findUsages.PyFunctionFindUsagesHandler; +import com.jetbrains.python.psi.*; +import com.jetbrains.python.psi.impl.PyBuiltinCache; +import com.jetbrains.python.psi.resolve.PyResolveContext; +import com.jetbrains.python.psi.search.PySuperMethodsSearch; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.List; + +/** + * @author novokrest + */ +public class PyStaticCallHierarchyUtil { + public static Collection<PsiElement> getCallees(@NotNull PyElement element) { + final List<PsiElement> callees = Lists.newArrayList(); + + final PyRecursiveElementVisitor visitor = new PyRecursiveElementVisitor() { + @Override + public void visitPyParameterList(PyParameterList node) { + } + + @Override + public void visitPyLambdaExpression(PyLambdaExpression node) { + } + + @Override + public void visitPyFunction(PyFunction innerFunction) { + for (PyParameter parameter : innerFunction.getParameterList().getParameters()) { + PsiElement defaultValue = parameter.getDefaultValue(); + if (defaultValue != null) { + defaultValue.accept(this); + } + } + } + + @Override + public void visitPyCallExpression(PyCallExpression callExpression) { + super.visitPyCallExpression(callExpression); + PsiElement calleeFunction = callExpression.resolveCalleeFunction(PyResolveContext.defaultContext()); + if (calleeFunction instanceof PyFunction) { + callees.add(calleeFunction); + } + } + }; + + visitor.visitElement(element); + + return callees; + } + + public static Collection<PsiElement> getCallers(@NotNull PyElement pyElement) { + final List<PsiElement> callers = Lists.newArrayList(); + final Collection<UsageInfo> usages = findUsages(pyElement); + + for (UsageInfo usage : usages) { + PsiElement element = usage.getElement(); + if (element == null) { + continue; + } + + element = element.getParent(); + while (element instanceof PyParenthesizedExpression) { + element = element.getParent(); + } + + if (element instanceof PyCallExpression) { + PsiElement caller = PsiTreeUtil.getParentOfType(element, PyParameterList.class, PyFunction.class); + if (caller instanceof PyFunction) { + callers.add(caller); + } + else if (caller instanceof PyParameterList) { + PsiElement innerFunction = PsiTreeUtil.getParentOfType(caller, PyFunction.class); + PsiElement outerFunction = PsiTreeUtil.getParentOfType(innerFunction, PyFunction.class); + if (innerFunction != null && outerFunction != null) { + callers.add(outerFunction); + } + } + } + } + + return callers; + } + + private static Collection<UsageInfo> findUsages(@NotNull final PsiElement element) { + final FindUsagesHandler handler = createFindUsageHandler(element); + if (handler == null) { + return Lists.newArrayList(); + } + final CommonProcessors.CollectProcessor<UsageInfo> processor = new CommonProcessors.CollectProcessor<UsageInfo>(); + final PsiElement[] psiElements = ArrayUtil.mergeArrays(handler.getPrimaryElements(), handler.getSecondaryElements()); + final FindUsagesOptions options = handler.getFindUsagesOptions(null); + for (PsiElement psiElement : psiElements) { + handler.processElementUsages(psiElement, processor, options); + } + return processor.getResults(); + } + + /** + * @see {@link com.jetbrains.python.findUsages.PyFindUsagesHandlerFactory#createFindUsagesHandler(com.intellij.psi.PsiElement, boolean) createFindUsagesHandler} + */ + @Nullable + private static FindUsagesHandler createFindUsageHandler(@NotNull final PsiElement element) { + if (element instanceof PyFunction) { + final Collection<PsiElement> superMethods = PySuperMethodsSearch.search((PyFunction)element, true).findAll(); + if (superMethods.size() > 0) { + final PsiElement next = superMethods.iterator().next(); + if (next instanceof PyFunction && !isInObject((PyFunction)next)) { + List<PsiElement> allMethods = Lists.newArrayList(); + allMethods.add(element); + allMethods.addAll(superMethods); + + return new PyFunctionFindUsagesHandler(element, allMethods); + } + } + return new PyFunctionFindUsagesHandler(element); + } + if (element instanceof PyClass) { + return new PyClassFindUsagesHandler((PyClass)element); + } + return null; + } + + /** + * @see {@link com.jetbrains.python.findUsages.PyFindUsagesHandlerFactory#isInObject(com.jetbrains.python.psi.PyFunction) isInObject} + */ + private static boolean isInObject(PyFunction fun) { + final PyClass containingClass = fun.getContainingClass(); + if (containingClass == null) { + return false; + } + return (PyNames.FAKE_OLD_BASE.equals(containingClass.getName()) || + (PyNames.OBJECT.equals(containingClass.getName()) && PyBuiltinCache.getInstance(fun).isBuiltin(containingClass))); + } +} diff --git a/python/src/com/jetbrains/python/hierarchy/treestructures/PySubTypesHierarchyTreeStructure.java b/python/src/com/jetbrains/python/hierarchy/treestructures/PySubTypesHierarchyTreeStructure.java index 7b43fe7329cf..f4287b28bd6c 100644 --- a/python/src/com/jetbrains/python/hierarchy/treestructures/PySubTypesHierarchyTreeStructure.java +++ b/python/src/com/jetbrains/python/hierarchy/treestructures/PySubTypesHierarchyTreeStructure.java @@ -18,9 +18,10 @@ package com.jetbrains.python.hierarchy.treestructures; import com.intellij.ide.hierarchy.HierarchyNodeDescriptor; import com.intellij.ide.hierarchy.HierarchyTreeStructure; import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; import com.intellij.util.ArrayUtil; import com.intellij.util.Query; -import com.jetbrains.python.hierarchy.PyTypeHierarchyNodeDescriptor; +import com.jetbrains.python.hierarchy.PyHierarchyNodeDescriptor; import com.jetbrains.python.psi.PyClass; import com.jetbrains.python.psi.search.PyClassInheritorsSearch; import org.jetbrains.annotations.NotNull; @@ -40,17 +41,20 @@ public class PySubTypesHierarchyTreeStructure extends HierarchyTreeStructure { } public PySubTypesHierarchyTreeStructure(@NotNull final PyClass cl) { - super(cl.getProject(), new PyTypeHierarchyNodeDescriptor(null, cl, true)); + super(cl.getProject(), new PyHierarchyNodeDescriptor(null, cl, true)); } @NotNull protected Object[] buildChildren(@NotNull HierarchyNodeDescriptor descriptor) { - final PyClass classElement = ((PyTypeHierarchyNodeDescriptor)descriptor).getClassElement(); - Query<PyClass> subClasses = PyClassInheritorsSearch.search(classElement, false); + final List<PyHierarchyNodeDescriptor> res = new ArrayList<PyHierarchyNodeDescriptor>(); + final PsiElement element = ((PyHierarchyNodeDescriptor)descriptor).getPsiElement(); + if (element instanceof PyClass) { + final PyClass cls = (PyClass)element; + Query<PyClass> subClasses = PyClassInheritorsSearch.search(cls, false); + for (PyClass subClass : subClasses) { + res.add(new PyHierarchyNodeDescriptor(descriptor, subClass, false)); + } - List<PyTypeHierarchyNodeDescriptor> res = new ArrayList<PyTypeHierarchyNodeDescriptor>(); - for (PyClass cl : subClasses) { - res.add(new PyTypeHierarchyNodeDescriptor(descriptor, cl, false)); } return ArrayUtil.toObjectArray(res); diff --git a/python/src/com/jetbrains/python/hierarchy/treestructures/PySuperTypesHierarchyTreeStructure.java b/python/src/com/jetbrains/python/hierarchy/treestructures/PySuperTypesHierarchyTreeStructure.java index 38b4c6ee7f76..8087767c61d8 100644 --- a/python/src/com/jetbrains/python/hierarchy/treestructures/PySuperTypesHierarchyTreeStructure.java +++ b/python/src/com/jetbrains/python/hierarchy/treestructures/PySuperTypesHierarchyTreeStructure.java @@ -17,8 +17,8 @@ package com.jetbrains.python.hierarchy.treestructures; import com.intellij.ide.hierarchy.HierarchyNodeDescriptor; import com.intellij.ide.hierarchy.HierarchyTreeStructure; -import com.intellij.openapi.project.Project; -import com.jetbrains.python.hierarchy.PyTypeHierarchyNodeDescriptor; +import com.intellij.psi.PsiElement; +import com.jetbrains.python.hierarchy.PyHierarchyNodeDescriptor; import com.jetbrains.python.psi.PyClass; import org.jetbrains.annotations.NotNull; @@ -32,20 +32,23 @@ import java.util.List; * Time: 7:04:07 PM */ public class PySuperTypesHierarchyTreeStructure extends HierarchyTreeStructure { - protected PySuperTypesHierarchyTreeStructure(final Project project, final HierarchyNodeDescriptor baseDescriptor) { - super(project, baseDescriptor); - } - public PySuperTypesHierarchyTreeStructure(@NotNull final PyClass cl) { - super(cl.getProject(), new PyTypeHierarchyNodeDescriptor(null, cl, true)); + super(cl.getProject(), new PyHierarchyNodeDescriptor(null, cl, true)); } @NotNull protected Object[] buildChildren(@NotNull HierarchyNodeDescriptor descriptor) { - final PyClass[] superClasses = ((PyTypeHierarchyNodeDescriptor)descriptor).getClassElement().getSuperClasses(); - List<PyTypeHierarchyNodeDescriptor> res = new ArrayList<PyTypeHierarchyNodeDescriptor>(); - for (PyClass superClass : superClasses) { - res.add(new PyTypeHierarchyNodeDescriptor(descriptor, superClass, false)); + final List<PyHierarchyNodeDescriptor> res = new ArrayList<PyHierarchyNodeDescriptor>(); + if (descriptor instanceof PyHierarchyNodeDescriptor) { + final PyHierarchyNodeDescriptor pyDescriptor = (PyHierarchyNodeDescriptor)descriptor; + final PsiElement element = pyDescriptor.getPsiElement(); + if (element instanceof PyClass) { + final PyClass cls = (PyClass)element; + final PyClass[] superClasses = cls.getSuperClasses(); + for (PyClass superClass : superClasses) { + res.add(new PyHierarchyNodeDescriptor(descriptor, superClass, false)); + } + } } return res.toArray(); } diff --git a/python/src/com/jetbrains/python/hierarchy/treestructures/PyTypeHierarchyTreeStructure.java b/python/src/com/jetbrains/python/hierarchy/treestructures/PyTypeHierarchyTreeStructure.java index 6707ac9b7e14..0eb505f4857b 100644 --- a/python/src/com/jetbrains/python/hierarchy/treestructures/PyTypeHierarchyTreeStructure.java +++ b/python/src/com/jetbrains/python/hierarchy/treestructures/PyTypeHierarchyTreeStructure.java @@ -16,8 +16,7 @@ package com.jetbrains.python.hierarchy.treestructures; import com.intellij.ide.hierarchy.HierarchyNodeDescriptor; -import com.intellij.openapi.project.Project; -import com.jetbrains.python.hierarchy.PyTypeHierarchyNodeDescriptor; +import com.jetbrains.python.hierarchy.PyHierarchyNodeDescriptor; import com.jetbrains.python.psi.PyClass; import com.jetbrains.python.psi.PyUtil; import org.jetbrains.annotations.NotNull; @@ -28,30 +27,26 @@ import java.util.List; * @author Alexey.Ivanov */ public class PyTypeHierarchyTreeStructure extends PySubTypesHierarchyTreeStructure { - private static PyTypeHierarchyNodeDescriptor buildHierarchyElement(@NotNull final PyClass cl) { - PyTypeHierarchyNodeDescriptor descriptor = null; + public PyTypeHierarchyTreeStructure(@NotNull final PyClass cl) { + super(cl.getProject(), buildHierarchyElement(cl)); + setBaseElement(myBaseDescriptor); + } + + private static PyHierarchyNodeDescriptor buildHierarchyElement(@NotNull final PyClass cl) { + PyHierarchyNodeDescriptor descriptor = null; List<PyClass> superClasses = PyUtil.getAllSuperClasses(cl); for (int i = superClasses.size() - 1; i >= 0; --i) { final PyClass superClass = superClasses.get(i); - final PyTypeHierarchyNodeDescriptor newDescriptor = new PyTypeHierarchyNodeDescriptor(descriptor, superClass, false); + final PyHierarchyNodeDescriptor newDescriptor = new PyHierarchyNodeDescriptor(descriptor, superClass, false); if (descriptor != null) { - descriptor.setCachedChildren(new PyTypeHierarchyNodeDescriptor[]{newDescriptor}); + descriptor.setCachedChildren(new PyHierarchyNodeDescriptor[]{newDescriptor}); } descriptor = newDescriptor; } - final PyTypeHierarchyNodeDescriptor newDescriptor = new PyTypeHierarchyNodeDescriptor(descriptor, cl, true); + final PyHierarchyNodeDescriptor newDescriptor = new PyHierarchyNodeDescriptor(descriptor, cl, true); if (descriptor != null) { descriptor.setCachedChildren(new HierarchyNodeDescriptor[]{newDescriptor}); } return newDescriptor; } - - protected PyTypeHierarchyTreeStructure(final Project project, final HierarchyNodeDescriptor baseDescriptor) { - super(project, baseDescriptor); - } - - public PyTypeHierarchyTreeStructure(@NotNull final PyClass cl) { - super(cl.getProject(), buildHierarchyElement(cl)); - setBaseElement(myBaseDescriptor); - } } diff --git a/python/src/com/jetbrains/python/inspections/PyPackageRequirementsInspection.java b/python/src/com/jetbrains/python/inspections/PyPackageRequirementsInspection.java index ec3761154f28..0b73adc3fa4c 100644 --- a/python/src/com/jetbrains/python/inspections/PyPackageRequirementsInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyPackageRequirementsInspection.java @@ -27,6 +27,7 @@ import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleUtilCore; import com.intellij.openapi.project.Project; import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.JDOMExternalizableStringList; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; @@ -42,6 +43,7 @@ import com.jetbrains.python.packaging.*; import com.jetbrains.python.packaging.ui.PyChooseRequirementsDialog; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.impl.PyPsiUtils; +import com.jetbrains.python.sdk.PySdkUtil; import com.jetbrains.python.sdk.PythonSdkType; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -111,9 +113,7 @@ public class PyPackageRequirementsInspection extends PyInspection { unsatisfiedNames.add(req.getName()); } final List<LocalQuickFix> quickFixes = new ArrayList<LocalQuickFix>(); - if (PyPackageManager.getInstance(sdk).hasPip()) { - quickFixes.add(new PyInstallRequirementsFix(null, module, sdk, unsatisfied)); - } + quickFixes.add(new PyInstallRequirementsFix(null, module, sdk, unsatisfied)); quickFixes.add(new IgnoreRequirementFix(unsatisfiedNames)); registerProblem(node, msg, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, null, @@ -160,15 +160,16 @@ public class PyPackageRequirementsInspection extends PyInspection { return; } } - if (PyPackageManagerImpl.PACKAGE_SETUPTOOLS.equals(packageName)) { + if (PyPackageManager.PACKAGE_SETUPTOOLS.equals(packageName)) { return; } final Module module = ModuleUtilCore.findModuleForPsiElement(packageReferenceExpression); if (module != null) { - Collection<PyRequirement> requirements = PyPackageManagerImpl.getRequirements(module); - if (requirements != null) { - final Sdk sdk = PythonSdkType.findPythonSdk(module); - if (sdk != null) { + final Sdk sdk = PythonSdkType.findPythonSdk(module); + if (sdk != null) { + final PyPackageManager manager = PyPackageManager.getInstance(sdk); + Collection<PyRequirement> requirements = manager.getRequirements(module); + if (requirements != null) { requirements = getTransitiveRequirements(sdk, requirements, new HashSet<PyPackage>()); } if (requirements == null) return; @@ -191,9 +192,7 @@ public class PyPackageRequirementsInspection extends PyInspection { } } final List<LocalQuickFix> quickFixes = new ArrayList<LocalQuickFix>(); - if (sdk != null && PyPackageManager.getInstance(sdk).hasPip()) { - quickFixes.add(new AddToRequirementsFix(module, packageName, LanguageLevel.forElement(importedExpression))); - } + quickFixes.add(new AddToRequirementsFix(module, packageName, LanguageLevel.forElement(importedExpression))); quickFixes.add(new IgnoreRequirementFix(Collections.singleton(packageName))); registerProblem(packageReferenceExpression, String.format("Package '%s' is not listed in project requirements", packageName), ProblemHighlightType.WEAK_WARNING, null, @@ -211,7 +210,7 @@ public class PyPackageRequirementsInspection extends PyInspection { final Set<PyRequirement> results = new HashSet<PyRequirement>(requirements); final List<PyPackage> packages; try { - packages = ((PyPackageManagerImpl) PyPackageManager.getInstance(sdk)).getPackagesFast(); + packages = PyPackageManager.getInstance(sdk).getPackages(PySdkUtil.isRemote(sdk)); } catch (PyExternalProcessException e) { return null; @@ -242,12 +241,12 @@ public class PyPackageRequirementsInspection extends PyInspection { @Nullable private static List<PyRequirement> findUnsatisfiedRequirements(@NotNull Module module, @NotNull Sdk sdk, @NotNull Set<String> ignoredPackages) { - final PyPackageManagerImpl manager = (PyPackageManagerImpl)PyPackageManager.getInstance(sdk); - List<PyRequirement> requirements = PyPackageManagerImpl.getRequirements(module); + final PyPackageManager manager = PyPackageManager.getInstance(sdk); + List<PyRequirement> requirements = manager.getRequirements(module); if (requirements != null) { final List<PyPackage> packages; try { - packages = manager.getPackagesFast(); + packages = manager.getPackages(PySdkUtil.isRemote(sdk)); } catch (PyExternalProcessException e) { return null; @@ -265,11 +264,11 @@ public class PyPackageRequirementsInspection extends PyInspection { } private static void setRunningPackagingTasks(@NotNull Module module, boolean value) { - module.putUserData(PyPackageManagerImpl.RUNNING_PACKAGING_TASKS, value); + module.putUserData(PyPackageManager.RUNNING_PACKAGING_TASKS, value); } private static boolean isRunningPackagingTasks(@NotNull Module module) { - final Boolean value = module.getUserData(PyPackageManagerImpl.RUNNING_PACKAGING_TASKS); + final Boolean value = module.getUserData(PyPackageManager.RUNNING_PACKAGING_TASKS); return value != null && value; } @@ -302,6 +301,21 @@ public class PyPackageRequirementsInspection extends PyInspection { @Override public void applyFix(@NotNull final Project project, @NotNull ProblemDescriptor descriptor) { + boolean installManagement = false; + final PyPackageManager manager = PyPackageManager.getInstance(mySdk); + if (!manager.hasManagement(false)) { + final int result = Messages.showYesNoDialog(project, + "Python packaging tools are required for installing packages. Do you want to " + + "install 'pip' and 'setuptools' for your interpreter?", + "Install Python Packaging Tools", + Messages.getQuestionIcon()); + if (result == Messages.YES) { + installManagement = true; + } + else { + return; + } + } final List<PyRequirement> chosen; if (myUnsatisfied.size() > 1) { final PyChooseRequirementsDialog dialog = new PyChooseRequirementsDialog(project, myUnsatisfied); @@ -313,21 +327,48 @@ public class PyPackageRequirementsInspection extends PyInspection { if (chosen.isEmpty()) { return; } - final PyPackageManagerImpl.UI ui = new PyPackageManagerImpl.UI(project, mySdk, new PyPackageManagerImpl.UI.Listener() { - @Override - public void started() { - setRunningPackagingTasks(myModule, true); - } + if (installManagement) { + final PyPackageManagerUI ui = new PyPackageManagerUI(project, mySdk, new UIListener(myModule) { + @Override + public void finished(List<PyExternalProcessException> exceptions) { + super.finished(exceptions); + if (exceptions.isEmpty()) { + installRequirements(project, chosen); + } + } + }); + ui.installManagement(); + } + else { + installRequirements(project, chosen); + } + } - @Override - public void finished(List<PyExternalProcessException> exceptions) { - setRunningPackagingTasks(myModule, false); - } - }); - ui.install(chosen, Collections.<String>emptyList()); + private void installRequirements(Project project, List<PyRequirement> requirements) { + final PyPackageManagerUI ui = new PyPackageManagerUI(project, mySdk, new UIListener(myModule)); + ui.install(requirements, Collections.<String>emptyList()); + } + } + + private static class UIListener implements PyPackageManagerUI.Listener { + private final Module myModule; + + public UIListener(Module module) { + myModule = module; + } + + @Override + public void started() { + setRunningPackagingTasks(myModule, true); + } + + @Override + public void finished(List<PyExternalProcessException> exceptions) { + setRunningPackagingTasks(myModule, false); } } + private static class IgnoreRequirementFix implements LocalQuickFix { @NotNull private final Set<String> myPackageNames; diff --git a/python/src/com/jetbrains/python/inspections/quickfix/GenerateBinaryStubsFix.java b/python/src/com/jetbrains/python/inspections/quickfix/GenerateBinaryStubsFix.java index 7d087bd3eb08..17600d32befb 100644 --- a/python/src/com/jetbrains/python/inspections/quickfix/GenerateBinaryStubsFix.java +++ b/python/src/com/jetbrains/python/inspections/quickfix/GenerateBinaryStubsFix.java @@ -170,7 +170,7 @@ public class GenerateBinaryStubsFix implements LocalQuickFix { new String[]{ homePath, PythonHelpersLocator.getHelperPath("extra_syspath.py"), myQualifiedName}, - PythonSdkType.getVirtualEnvAdditionalEnv(homePath), 5000 + PythonSdkType.getVirtualEnvExtraEnv(homePath), 5000 ); if (runResult.getExitCode() == 0 && !runResult.isTimeout()) { final String extraPath = runResult.getStdout(); diff --git a/python/src/com/jetbrains/python/inspections/quickfix/PyDefaultArgumentQuickFix.java b/python/src/com/jetbrains/python/inspections/quickfix/PyDefaultArgumentQuickFix.java index db47f17f8ea8..6e0d5c465d0e 100644 --- a/python/src/com/jetbrains/python/inspections/quickfix/PyDefaultArgumentQuickFix.java +++ b/python/src/com/jetbrains/python/inspections/quickfix/PyDefaultArgumentQuickFix.java @@ -61,7 +61,7 @@ public class PyDefaultArgumentQuickFix implements LocalQuickFix { PyStatementList list = function.getStatementList(); PyParameterList paramList = function.getParameterList(); - final StringBuilder functionText = new StringBuilder("def foo("); + final StringBuilder functionText = new StringBuilder("def " + function.getName() + "("); int size = paramList.getParameters().length; for (int i = 0; i != size; ++i) { PyParameter p = paramList.getParameters()[i]; diff --git a/python/src/com/jetbrains/python/inspections/quickfix/StatementEffectFunctionCallQuickFix.java b/python/src/com/jetbrains/python/inspections/quickfix/StatementEffectFunctionCallQuickFix.java index 43f4f0394015..017bf1a1af29 100644 --- a/python/src/com/jetbrains/python/inspections/quickfix/StatementEffectFunctionCallQuickFix.java +++ b/python/src/com/jetbrains/python/inspections/quickfix/StatementEffectFunctionCallQuickFix.java @@ -88,7 +88,13 @@ public class StatementEffectFunctionCallQuickFix implements LocalQuickFix { if (next instanceof PyExpressionStatement) { final PyExpression expr = ((PyExpressionStatement)next).getExpression(); if (expr instanceof PyBinaryExpression) { - addInArguments(stringBuilder, (PyBinaryExpression)expr); + final PsiElement operator = ((PyBinaryExpression)expr).getPsiOperator(); + if (operator instanceof LeafPsiElement && ((LeafPsiElement)operator).getElementType() == PyTokenTypes.IN_KEYWORD) { + addInArguments(stringBuilder, (PyBinaryExpression)expr); + } + else { + stringBuilder.append(next.getText()); + } } else if (expr instanceof PyTupleExpression) { final PyExpression[] elements = ((PyTupleExpression)expr).getElements(); @@ -114,14 +120,11 @@ public class StatementEffectFunctionCallQuickFix implements LocalQuickFix { } private static void addInArguments(@NotNull final StringBuilder stringBuilder, @NotNull final PyBinaryExpression binaryExpression) { - final PsiElement operator = binaryExpression.getPsiOperator(); - if (operator instanceof LeafPsiElement && ((LeafPsiElement)operator).getElementType() == PyTokenTypes.IN_KEYWORD) { - stringBuilder.append(binaryExpression.getLeftExpression().getText()); - stringBuilder.append(", "); - final PyExpression rightExpression = binaryExpression.getRightExpression(); - if (rightExpression != null) - stringBuilder.append(rightExpression.getText()); - } + stringBuilder.append(binaryExpression.getLeftExpression().getText()); + stringBuilder.append(", "); + final PyExpression rightExpression = binaryExpression.getRightExpression(); + if (rightExpression != null) + stringBuilder.append(rightExpression.getText()); } private static void replacePrint(@NotNull final PsiElement expression) { diff --git a/python/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesInspection.java b/python/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesInspection.java index b7523caf90e7..8049546a6e92 100644 --- a/python/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesInspection.java +++ b/python/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesInspection.java @@ -48,7 +48,6 @@ import com.jetbrains.python.documentation.DocStringTypeReference; import com.jetbrains.python.inspections.*; import com.jetbrains.python.inspections.quickfix.*; import com.jetbrains.python.packaging.PyPIPackageUtil; -import com.jetbrains.python.packaging.PyPackageManager; import com.jetbrains.python.packaging.PyRequirement; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.impl.PyBuiltinCache; @@ -598,9 +597,7 @@ public class PyUnresolvedReferencesInspection extends PyInspection { if (PyPIPackageUtil.INSTANCE.isInPyPI(packageName)) { final List<PyRequirement> requirements = Collections.singletonList(new PyRequirement(packageName)); final String name = "Install package " + packageName; - if (PyPackageManager.getInstance(sdk).hasPip()) { - actions.add(new PyPackageRequirementsInspection.PyInstallRequirementsFix(name, module, sdk, requirements)); - } + actions.add(new PyPackageRequirementsInspection.PyInstallRequirementsFix(name, module, sdk, requirements)); } } } diff --git a/python/src/com/jetbrains/python/packaging/PyPackageManagerImpl.java b/python/src/com/jetbrains/python/packaging/PyPackageManagerImpl.java index 65d95eb62713..9b5f669168f7 100644 --- a/python/src/com/jetbrains/python/packaging/PyPackageManagerImpl.java +++ b/python/src/com/jetbrains/python/packaging/PyPackageManagerImpl.java @@ -15,31 +15,17 @@ */ package com.jetbrains.python.packaging; -import com.google.common.collect.Collections2; import com.google.common.collect.Lists; import com.intellij.execution.ExecutionException; import com.intellij.execution.process.ProcessOutput; import com.intellij.execution.util.ExecUtil; -import com.intellij.icons.AllIcons; -import com.intellij.notification.Notification; -import com.intellij.notification.NotificationListener; -import com.intellij.notification.NotificationType; import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.module.Module; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.progress.Task; -import com.intellij.openapi.project.Project; import com.intellij.openapi.projectRoots.Sdk; -import com.intellij.openapi.projectRoots.SdkAdditionalData; import com.intellij.openapi.projectRoots.impl.ProjectJdkImpl; import com.intellij.openapi.roots.OrderRootType; -import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.util.Key; -import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; @@ -49,46 +35,36 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileManager; import com.intellij.openapi.vfs.newvfs.BulkFileListener; import com.intellij.openapi.vfs.newvfs.events.VFileEvent; -import com.intellij.remote.RemoteFile; -import com.intellij.remote.RemoteSdkAdditionalData; -import com.intellij.remote.RemoteSdkCredentials; -import com.intellij.remote.VagrantNotStartedException; import com.intellij.util.ArrayUtil; -import com.intellij.util.Function; -import com.intellij.util.PathMappingSettings; -import com.intellij.util.SystemProperties; import com.intellij.util.containers.HashSet; import com.intellij.util.messages.MessageBusConnection; import com.intellij.util.net.HttpConfigurable; -import com.intellij.webcore.packaging.PackagesNotificationPanel; import com.jetbrains.python.PythonHelpersLocator; import com.jetbrains.python.psi.LanguageLevel; import com.jetbrains.python.psi.PyExpression; import com.jetbrains.python.psi.PyListLiteralExpression; import com.jetbrains.python.psi.PyStringLiteralExpression; -import com.jetbrains.python.remote.PyRemoteSdkAdditionalDataBase; -import com.jetbrains.python.remote.PythonRemoteInterpreterManager; import com.jetbrains.python.sdk.PySdkUtil; import com.jetbrains.python.sdk.PythonSdkType; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import javax.swing.event.HyperlinkEvent; -import java.awt.*; import java.io.File; import java.io.IOException; import java.util.*; -import java.util.List; /** * @author vlan */ -@SuppressWarnings({"UnusedDeclaration", "FieldAccessedSynchronizedAndUnsynchronized"}) public class PyPackageManagerImpl extends PyPackageManager { - private static final Logger LOG = Logger.getInstance(PyPackageManagerImpl.class); + // Bundled versions of package management tools + public static final String SETUPTOOLS_VERSION = "1.1.5"; + public static final String PIP_VERSION = "1.4.1"; + + public static final String SETUPTOOLS = PACKAGE_SETUPTOOLS + "-" + SETUPTOOLS_VERSION; + public static final String PIP = PACKAGE_PIP + "-" + PIP_VERSION; public static final int OK = 0; - public static final int ERROR_WRONG_USAGE = 1; public static final int ERROR_NO_PIP = 2; public static final int ERROR_NO_SETUPTOOLS = 3; public static final int ERROR_INVALID_SDK = -1; @@ -97,260 +73,24 @@ public class PyPackageManagerImpl extends PyPackageManager { public static final int ERROR_INVALID_OUTPUT = -4; public static final int ERROR_ACCESS_DENIED = -5; public static final int ERROR_EXECUTION = -6; - public static final int ERROR_INTERRUPTED = -7; - public static final int ERROR_VAGRANT_NOT_LAUNCHED = 101; - public static final int ERROR_REMOTE_ACCESS = 102; - - public static final String PACKAGE_PIP = "pip"; - public static final String PACKAGE_DISTRIBUTE = "distribute"; - public static final String PACKAGE_SETUPTOOLS = "setuptools"; - - public static final Key<Boolean> RUNNING_PACKAGING_TASKS = Key.create("PyPackageRequirementsInspection.RunningPackagingTasks"); + private static final Logger LOG = Logger.getInstance(PyPackageManagerImpl.class); private static final String PACKAGING_TOOL = "packaging_tool.py"; private static final String VIRTUALENV = "virtualenv.py"; private static final int TIMEOUT = 10 * 60 * 1000; private static final String BUILD_DIR_OPTION = "--build-dir"; - public static final String USE_USER_SITE = "--user"; public static final String INSTALL = "install"; public static final String UNINSTALL = "uninstall"; public static final String UNTAR = "untar"; - // Bundled versions of package management tools - public static final String SETUPTOOLS_VERSION = "1.1.5"; - public static final String PIP_VERSION = "1.4.1"; - - public static final String SETUPTOOLS = PACKAGE_SETUPTOOLS + "-" + SETUPTOOLS_VERSION; - public static final String PIP = PACKAGE_PIP + "-" + PIP_VERSION; - private static final String LAUNCH_VAGRANT = "launchVagrant"; - private List<PyPackage> myPackagesCache = null; private Map<String, Set<PyPackage>> myDependenciesCache = null; private PyExternalProcessException myExceptionCache = null; - private Sdk mySdk; - - public static class UI { - @Nullable private Listener myListener; - @NotNull private Project myProject; - @NotNull private Sdk mySdk; - - public interface Listener { - void started(); - - void finished(List<PyExternalProcessException> exceptions); - } - - public UI(@NotNull Project project, @NotNull Sdk sdk, @Nullable Listener listener) { - myProject = project; - mySdk = sdk; - myListener = listener; - } - - public void installManagement(@NotNull final String name) { - final String progressTitle; - final String successTitle; - progressTitle = "Installing package " + name; - successTitle = "Packages installed successfully"; - run(new MultiExternalRunnable() { - @Override - public List<PyExternalProcessException> run(@NotNull ProgressIndicator indicator) { - final List<PyExternalProcessException> exceptions = new ArrayList<PyExternalProcessException>(); - indicator.setText(String.format("Installing package '%s'...", name)); - final PyPackageManagerImpl manager = (PyPackageManagerImpl)PyPackageManagers.getInstance().forSdk(mySdk); - try { - manager.installManagement(name); - } - catch (PyExternalProcessException e) { - exceptions.add(e); - } - return exceptions; - } - }, progressTitle, successTitle, "Installed package " + name, - "Install package failed" - ); - } - - public void install(@NotNull final List<PyRequirement> requirements, @NotNull final List<String> extraArgs) { - final String progressTitle; - final String successTitle; - progressTitle = "Installing packages"; - successTitle = "Packages installed successfully"; - run(new MultiExternalRunnable() { - @Override - public List<PyExternalProcessException> run(@NotNull ProgressIndicator indicator) { - final int size = requirements.size(); - final List<PyExternalProcessException> exceptions = new ArrayList<PyExternalProcessException>(); - final PyPackageManagerImpl manager = (PyPackageManagerImpl)PyPackageManagers.getInstance().forSdk(mySdk); - for (int i = 0; i < size; i++) { - final PyRequirement requirement = requirements.get(i); - if (myListener != null) { - indicator.setText(String.format("Installing package '%s'...", requirement)); - indicator.setFraction((double)i / size); - } - try { - manager.install(list(requirement), extraArgs); - } - catch (PyExternalProcessException e) { - exceptions.add(e); - } - } - manager.refresh(); - return exceptions; - } - }, progressTitle, successTitle, "Installed packages: " + PyPackageUtil.requirementsToString(requirements), - "Install packages failed" - ); - } - - public void uninstall(@NotNull final List<PyPackage> packages) { - final String packagesString = StringUtil.join(packages, new Function<PyPackage, String>() { - @Override - public String fun(PyPackage pkg) { - return "'" + pkg.getName() + "'"; - } - }, ", "); - if (checkDependents(packages)) return; - - run(new MultiExternalRunnable() { - @Override - public List<PyExternalProcessException> run(@NotNull ProgressIndicator indicator) { - final PyPackageManagerImpl manager = (PyPackageManagerImpl)PyPackageManagers.getInstance().forSdk(mySdk); - try { - manager.uninstall(packages); - return list(); - } - catch (PyExternalProcessException e) { - return list(e); - } - finally { - manager.refresh(); - } - } - }, "Uninstalling packages", "Packages uninstalled successfully", "Uninstalled packages: " + packagesString, - "Uninstall packages failed" - ); - } - - private boolean checkDependents(@NotNull final List<PyPackage> packages) { - try { - final Map<String, Set<PyPackage>> dependentPackages = collectDependents(packages, mySdk); - final int[] warning = {0}; - if (!dependentPackages.isEmpty()) { - ApplicationManager.getApplication().invokeAndWait(new Runnable() { - @Override - public void run() { - if (dependentPackages.size() == 1) { - String message = "You are attempting to uninstall "; - List<String> dep = new ArrayList<String>(); - int size = 1; - for (Map.Entry<String, Set<PyPackage>> entry : dependentPackages.entrySet()) { - final Set<PyPackage> value = entry.getValue(); - size = value.size(); - dep.add(entry.getKey() + " package which is required for " + StringUtil.join(value, ", ")); - } - message += StringUtil.join(dep, "\n"); - message += size == 1 ? " package" : " packages"; - message += "\n\nDo you want to proceed?"; - warning[0] = Messages.showYesNoDialog(message, "Warning", - AllIcons.General.BalloonWarning); - } - else { - String message = "You are attempting to uninstall packages which are required for another packages.\n\n"; - List<String> dep = new ArrayList<String>(); - for (Map.Entry<String, Set<PyPackage>> entry : dependentPackages.entrySet()) { - dep.add(entry.getKey() + " -> " + StringUtil.join(entry.getValue(), ", ")); - } - message += StringUtil.join(dep, "\n"); - message += "\n\nDo you want to proceed?"; - warning[0] = Messages.showYesNoDialog(message, "Warning", - AllIcons.General.BalloonWarning); - } - } - }, ModalityState.current()); - } - if (warning[0] != Messages.YES) return true; - } - catch (PyExternalProcessException e) { - LOG.info("Error loading packages dependents: " + e.getMessage(), e); - } - return false; - } - - private interface MultiExternalRunnable { - List<PyExternalProcessException> run(@NotNull ProgressIndicator indicator); - } - - private void run(@NotNull final MultiExternalRunnable runnable, @NotNull final String progressTitle, - @NotNull final String successTitle, @NotNull final String successDescription, @NotNull final String failureTitle) { - ProgressManager.getInstance().run(new Task.Backgroundable(myProject, progressTitle, false) { - @Override - public void run(@NotNull ProgressIndicator indicator) { - indicator.setText(progressTitle + "..."); - final Ref<Notification> notificationRef = new Ref<Notification>(null); - final String PACKAGING_GROUP_ID = "Packaging"; - final Application application = ApplicationManager.getApplication(); - if (myListener != null) { - application.invokeLater(new Runnable() { - @Override - public void run() { - myListener.started(); - } - }); - } - - final List<PyExternalProcessException> exceptions = runnable.run(indicator); - if (exceptions.isEmpty()) { - notificationRef.set(new Notification(PACKAGING_GROUP_ID, successTitle, successDescription, NotificationType.INFORMATION)); - } - else { - final String progressLower = progressTitle.toLowerCase(); - final String firstLine = String.format("Error%s occurred when %s.", exceptions.size() > 1 ? "s" : "", progressLower); - - final String description = createDescription(exceptions, firstLine); - notificationRef.set(new Notification(PACKAGING_GROUP_ID, failureTitle, - firstLine + " <a href=\"xxx\">Details...</a>", - NotificationType.ERROR, - new NotificationListener() { - @Override - public void hyperlinkUpdate(@NotNull Notification notification, - @NotNull HyperlinkEvent event) { - assert myProject != null; - PackagesNotificationPanel.showError(myProject, failureTitle, description); - } - } - )); - } - application.invokeLater(new Runnable() { - @Override - public void run() { - if (myListener != null) { - myListener.finished(exceptions); - } - final Notification notification = notificationRef.get(); - if (notification != null) { - notification.notify(myProject); - } - } - }); - } - }); - } - - public static String createDescription(List<PyExternalProcessException> exceptions, String firstLine) { - final StringBuilder b = new StringBuilder(); - b.append(firstLine); - b.append("\n\n"); - for (PyExternalProcessException exception : exceptions) { - b.append(exception.toString()); - b.append("\n"); - } - return b.toString(); - } - } + protected Sdk mySdk; @Override public void refresh() { @@ -373,7 +113,23 @@ public class PyPackageManagerImpl extends PyPackageManager { }); } - private void installManagement(String name) throws PyExternalProcessException { + @Override + public void installManagement() throws PyExternalProcessException { + if (!hasPackage(PACKAGE_SETUPTOOLS, false) && !hasPackage(PACKAGE_DISTRIBUTE, false)) { + installManagement(SETUPTOOLS); + } + if (!hasPackage(PACKAGE_PIP, false)) { + installManagement(PIP); + } + } + + @Override + public boolean hasManagement(boolean cachedOnly) { + return (hasPackage(PACKAGE_SETUPTOOLS, cachedOnly) || hasPackage(PACKAGE_DISTRIBUTE, cachedOnly)) && + hasPackage(PACKAGE_PIP, cachedOnly); + } + + protected void installManagement(@NotNull String name) throws PyExternalProcessException { final String helperPath = getHelperPath(name); ArrayList<String> args = Lists.newArrayList(UNTAR, helperPath); @@ -390,7 +146,7 @@ public class PyPackageManagerImpl extends PyPackageManager { } final String fileName = dirName + name + File.separatorChar + "setup.py"; try { - output = getProcessOutput(fileName, Collections.<String>singletonList(INSTALL), true, dirName + name); + output = getProcessOutput(fileName, Collections.singletonList(INSTALL), true, dirName + name); final int retcode = output.getExitCode(); if (output.isTimeout()) { throw new PyExternalProcessException(ERROR_TIMEOUT, fileName, Lists.newArrayList(INSTALL), "Timed out"); @@ -406,17 +162,28 @@ public class PyPackageManagerImpl extends PyPackageManager { } finally { clearCaches(); - FileUtil.delete(new File(dirName)); //TODO: remove temp directory for remote interpreter + FileUtil.delete(new File(dirName)); + } + } + + private boolean hasPackage(@NotNull String name, boolean cachedOnly) { + try { + return findPackage(name, cachedOnly) != null; + } + catch (PyExternalProcessException ignored) { + return false; } } PyPackageManagerImpl(@NotNull Sdk sdk) { mySdk = sdk; + subscribeToLocalChanges(sdk); + } + + protected void subscribeToLocalChanges(Sdk sdk) { final Application app = ApplicationManager.getApplication(); final MessageBusConnection connection = app.getMessageBus().connect(); - if (!PySdkUtil.isRemote(sdk)) { - connection.subscribe(VirtualFileManager.VFS_CHANGES, new MySdkRootWatcher()); - } + connection.subscribe(VirtualFileManager.VFS_CHANGES, new MySdkRootWatcher()); } public Sdk getSdk() { @@ -424,27 +191,13 @@ public class PyPackageManagerImpl extends PyPackageManager { } @Override - public void install(String requirementString) throws PyExternalProcessException { - boolean hasSetuptools = false; - boolean hasPip = false; - try { - hasSetuptools = findInstalledPackage(SETUPTOOLS) != null; - } - catch (PyExternalProcessException ignored) { - } - try { - hasPip = findInstalledPackage(PIP) != null; - } - catch (PyExternalProcessException ignored) { - } - - if (!hasSetuptools) installManagement(SETUPTOOLS); - if (!hasPip) installManagement(PIP); + public void install(@NotNull String requirementString) throws PyExternalProcessException { + installManagement(); install(Collections.singletonList(PyRequirement.fromString(requirementString)), Collections.<String>emptyList()); } - public void install(@NotNull List<PyRequirement> requirements, @NotNull List<String> extraArgs) - throws PyExternalProcessException { + @Override + public void install(@NotNull List<PyRequirement> requirements, @NotNull List<String> extraArgs) throws PyExternalProcessException { final List<String> args = new ArrayList<String>(); args.add(INSTALL); final File buildDir; @@ -455,7 +208,7 @@ public class PyPackageManagerImpl extends PyPackageManager { throw new PyExternalProcessException(ERROR_ACCESS_DENIED, PACKAGING_TOOL, args, "Cannot create temporary build directory"); } if (!extraArgs.contains(BUILD_DIR_OPTION)) { - args.addAll(list(BUILD_DIR_OPTION, buildDir.getAbsolutePath())); + args.addAll(Arrays.asList(BUILD_DIR_OPTION, buildDir.getAbsolutePath())); } boolean useUserSite = extraArgs.contains(USE_USER_SITE); @@ -499,69 +252,22 @@ public class PyPackageManagerImpl extends PyPackageManager { } } - private static Map<String, Set<PyPackage>> collectDependents(@NotNull final List<PyPackage> packages, Sdk sdk) - throws PyExternalProcessException { - Map<String, Set<PyPackage>> dependentPackages = new HashMap<String, Set<PyPackage>>(); - for (PyPackage pkg : packages) { - final Set<PyPackage> dependents = - ((PyPackageManagerImpl)PyPackageManager.getInstance(sdk)).getDependents(pkg.getName()); - if (dependents != null && !dependents.isEmpty()) { - for (PyPackage dependent : dependents) { - if (!packages.contains(dependent)) { - dependentPackages.put(pkg.getName(), dependents); - } - } - } - } - return dependentPackages; - } - - public static String getUserSite() { - if (SystemInfo.isWindows) { - final String appdata = System.getenv("APPDATA"); - return appdata + File.separator + "Python"; - } - else { - final String userHome = SystemProperties.getUserHome(); - return userHome + File.separator + ".local"; - } - } - - - public boolean cacheIsNotNull() { - return myPackagesCache != null; - } - - /** - * Returns the list of packages for the SDK without initiating a remote connection. Returns null - * for a remote interpreter if the list of packages was not loaded. - * - * @return the list of packages or null - */ @Nullable - public synchronized List<PyPackage> getPackagesFast() throws PyExternalProcessException { - if (myPackagesCache != null) { - return myPackagesCache; - } - if (PySdkUtil.isRemote(mySdk)) { - return null; - } - return getPackages(); - } - - @NotNull - public synchronized List<PyPackage> getPackages() throws PyExternalProcessException { + public synchronized List<PyPackage> getPackages(boolean cachedOnly) throws PyExternalProcessException { if (myPackagesCache == null) { if (myExceptionCache != null) { throw myExceptionCache; } - + if (cachedOnly) { + return null; + } loadPackages(); } return myPackagesCache; } - public synchronized Set<PyPackage> getDependents(String pkg) throws PyExternalProcessException { + @Nullable + public synchronized Set<PyPackage> getDependents(@NotNull PyPackage pkg) throws PyExternalProcessException { if (myDependenciesCache == null) { if (myExceptionCache != null) { throw myExceptionCache; @@ -569,12 +275,12 @@ public class PyPackageManagerImpl extends PyPackageManager { loadPackages(); } - return myDependenciesCache.get(pkg); + return myDependenciesCache.get(pkg.getName()); } public synchronized void loadPackages() throws PyExternalProcessException { try { - final String output = runPythonHelper(PACKAGING_TOOL, list("list")); + final String output = runPythonHelper(PACKAGING_TOOL, Arrays.asList("list")); myPackagesCache = parsePackagingToolOutput(output); Collections.sort(myPackagesCache, new Comparator<PyPackage>() { @Override @@ -592,7 +298,7 @@ public class PyPackageManagerImpl extends PyPackageManager { } } - private void calculateDependents() { + private synchronized void calculateDependents() { myDependenciesCache = new HashMap<String, Set<PyPackage>>(); for (PyPackage p : myPackagesCache) { final List<PyRequirement> requirements = p.getRequirements(); @@ -608,47 +314,18 @@ public class PyPackageManagerImpl extends PyPackageManager { @Override @Nullable - public PyPackage findInstalledPackage(String name) throws PyExternalProcessException { - return findPackageByName(name, getPackages()); - } - - @Override - public boolean findPackage(@NotNull final String name) { - try { - final String output = runPythonHelper(PACKAGING_TOOL, list("search", name)); - return StringUtil.containsIgnoreCase(output, name + " "); - } - catch (PyExternalProcessException e) { - LOG.error(e.getMessage()); - return false; - } - } - - @Nullable - public PyPackage findPackageFast(String name) throws PyExternalProcessException { - final List<PyPackage> packages = getPackagesFast(); - return packages != null ? findPackageByName(name, packages) : null; - } - - @Nullable - private static PyPackage findPackageByName(String name, List<PyPackage> packages) { - for (PyPackage pkg : packages) { - if (name.equalsIgnoreCase(pkg.getName())) { - return pkg; + public PyPackage findPackage(@NotNull String name, boolean cachedOnly) throws PyExternalProcessException { + final List<PyPackage> packages = getPackages(cachedOnly); + if (packages != null) { + for (PyPackage pkg : packages) { + if (name.equalsIgnoreCase(pkg.getName())) { + return pkg; + } } } return null; } - public boolean hasPip() { - try { - return findPackageFast(PACKAGE_PIP) != null; - } - catch (PyExternalProcessException e) { - return false; - } - } - @NotNull public String createVirtualEnv(@NotNull String destinationDir, boolean useGlobalSite) throws PyExternalProcessException { final List<String> args = new ArrayList<String>(); @@ -674,30 +351,21 @@ public class PyPackageManagerImpl extends PyPackageManager { final String path = (binary != null) ? binary : binaryFallback; if (usePyVenv) { - // TODO: Still no 'packaging' and 'pysetup3' for Python 3.3rc1, see PEP 405 + // Still no 'packaging' and 'pysetup3' for Python 3.3rc1, see PEP 405 final VirtualFile binaryFile = LocalFileSystem.getInstance().refreshAndFindFileByPath(path); if (binaryFile != null) { final ProjectJdkImpl tmpSdk = new ProjectJdkImpl("", PythonSdkType.getInstance()); tmpSdk.setHomePath(path); - final PyPackageManagerImpl manager = new PyPackageManagerImpl(tmpSdk); - manager.installManagement(SETUPTOOLS); - manager.installManagement(PIP); + final PyPackageManager manager = PyPackageManager.getInstance(tmpSdk); + manager.installManagement(); } } return path; } - public static void deleteVirtualEnv(@NotNull String sdkHome) throws PyExternalProcessException { - final File root = PythonSdkType.getVirtualEnvRoot(sdkHome); - if (root != null) { - FileUtil.delete(root); - } - } - @Nullable - public static List<PyRequirement> getRequirements(@NotNull Module module) { - // TODO: Cache requirements, clear cache on requirements.txt or setup.py updates - List<PyRequirement> requirements = getRequirementsFromTxt(module); + public List<PyRequirement> getRequirements(@NotNull Module module) { + List<PyRequirement> requirements = PySdkUtil.getRequirementsFromTxt(module); if (requirements != null) { return requirements; } @@ -721,25 +389,12 @@ public class PyPackageManagerImpl extends PyPackageManager { return null; } - @Nullable - public static List<PyRequirement> getRequirementsFromTxt(Module module) { - final VirtualFile requirementsTxt = PyPackageUtil.findRequirementsTxt(module); - if (requirementsTxt != null) { - return PyRequirement.parse(requirementsTxt); - } - return null; - } - - private void clearCaches() { + protected synchronized void clearCaches() { myPackagesCache = null; myDependenciesCache = null; myExceptionCache = null; } - private static <T> List<T> list(T... xs) { - return Arrays.asList(xs); - } - @Nullable private static String getProxyString() { final HttpConfigurable settings = HttpConfigurable.getInstance(); @@ -792,164 +447,61 @@ public class PyPackageManagerImpl extends PyPackageManager { } @Nullable - private String getHelperPath(String helper) { - String helperPath; - final SdkAdditionalData sdkData = mySdk.getSdkAdditionalData(); - if (sdkData instanceof PyRemoteSdkAdditionalDataBase) { - PyRemoteSdkAdditionalDataBase remoteSdkData = (PyRemoteSdkAdditionalDataBase)mySdk.getSdkAdditionalData(); - - try { - final RemoteSdkCredentials remoteSdkCredentials = remoteSdkData.getRemoteSdkCredentials(false); - if (!StringUtil.isEmpty(remoteSdkCredentials.getHelpersPath())) { - helperPath = new RemoteFile(remoteSdkCredentials.getHelpersPath(), - helper).getPath(); - } - else { - helperPath = null; - } - } - catch (Exception e) { - helperPath = null; - LOG.error(e); - } - } - else { - helperPath = PythonHelpersLocator.getHelperPath(helper); - } - return helperPath; + protected String getHelperPath(String helper) { + return PythonHelpersLocator.getHelperPath(helper); } - private ProcessOutput getProcessOutput(@NotNull String helperPath, + protected ProcessOutput getProcessOutput(@NotNull String helperPath, @NotNull List<String> args, boolean askForSudo, - @Nullable String workingDir) - throws PyExternalProcessException { - final SdkAdditionalData sdkData = mySdk.getSdkAdditionalData(); + @Nullable String workingDir) throws PyExternalProcessException { final String homePath = mySdk.getHomePath(); if (homePath == null) { throw new PyExternalProcessException(ERROR_INVALID_SDK, helperPath, args, "Cannot find interpreter for SDK"); } - if (sdkData instanceof PyRemoteSdkAdditionalDataBase) { //remote interpreter - RemoteSdkCredentials remoteSdkCredentials; + if (workingDir == null) { + workingDir = new File(homePath).getParent(); + } + final List<String> cmdline = new ArrayList<String>(); + cmdline.add(homePath); + cmdline.add(helperPath); + cmdline.addAll(args); + LOG.info("Running packaging tool: " + StringUtil.join(cmdline, " ")); + + final boolean canCreate = FileUtil.ensureCanCreateFile(new File(homePath)); + if (!canCreate && !SystemInfo.isWindows && askForSudo) { //is system site interpreter --> we need sudo privileges try { - remoteSdkCredentials = ((RemoteSdkAdditionalData)sdkData).getRemoteSdkCredentials(false); - } - catch (InterruptedException e) { - LOG.error(e); - remoteSdkCredentials = null; - } - catch (final ExecutionException e) { - if (e.getCause() instanceof VagrantNotStartedException) { - throw new PyExternalProcessException(ERROR_VAGRANT_NOT_LAUNCHED, helperPath, args, "Vagrant instance is down. <a href=\"" + - LAUNCH_VAGRANT + - "\">Launch vagrant</a>") - .withHandler(LAUNCH_VAGRANT, new Runnable() { - @Override - public void run() { - final PythonRemoteInterpreterManager manager = PythonRemoteInterpreterManager.getInstance(); - if (manager != null) { - - try { - manager.runVagrant(((VagrantNotStartedException)e.getCause()).getVagrantFolder()); - clearCaches(); - } - catch (ExecutionException e1) { - throw new RuntimeException(e1); - } - } - } - }); - } - else { - throw new PyExternalProcessException(ERROR_REMOTE_ACCESS, helperPath, args, e.getMessage()); - } - } - final PythonRemoteInterpreterManager manager = PythonRemoteInterpreterManager.getInstance(); - if (manager != null && remoteSdkCredentials != null) { - final List<String> cmdline = new ArrayList<String>(); - cmdline.add(homePath); - cmdline.add(RemoteFile.detectSystemByPath(homePath).createRemoteFile(helperPath).getPath()); - cmdline.addAll(Collections2.transform(args, new com.google.common.base.Function<String, String>() { - @Override - public String apply(@Nullable String input) { - return quoteIfNeeded(input); + final ProcessOutput result = ExecUtil.sudoAndGetOutput(cmdline, + "Please enter your password to make changes in system packages: ", + workingDir); + String message = result.getStderr(); + if (result.getExitCode() != 0) { + final String stdout = result.getStdout(); + if (StringUtil.isEmptyOrSpaces(message)) { + message = stdout; } - })); - try { - if (askForSudo) { - askForSudo = !manager.ensureCanWrite(null, remoteSdkCredentials, remoteSdkCredentials.getInterpreterPath()); + if (StringUtil.isEmptyOrSpaces(message)) { + message = "Failed to perform action. Permission denied."; } - ProcessOutput processOutput; - do { - PathMappingSettings mappings = manager.setupMappings(null, (PyRemoteSdkAdditionalDataBase)sdkData, null); - processOutput = - manager.runRemoteProcess(null, remoteSdkCredentials, mappings, ArrayUtil.toStringArray(cmdline), workingDir, askForSudo); - if (askForSudo && processOutput.getStderr().contains("sudo: 3 incorrect password attempts")) { - continue; - } - break; - } - while (true); - return processOutput; + throw new PyExternalProcessException(result.getExitCode(), helperPath, args, message); } - catch (ExecutionException e) { - throw new PyExternalProcessException(ERROR_INVALID_SDK, helperPath, args, "Error running SDK: " + e.getMessage(), e); + if (SystemInfo.isMac && !StringUtil.isEmptyOrSpaces(message)) { + throw new PyExternalProcessException(result.getExitCode(), helperPath, args, message); } + return result; } - else { - throw new PyExternalProcessException(ERROR_INVALID_SDK, helperPath, args, - PythonRemoteInterpreterManager.WEB_DEPLOYMENT_PLUGIN_IS_DISABLED); + catch (ExecutionException e) { + throw new PyExternalProcessException(ERROR_EXECUTION, helperPath, args, e.getMessage()); + } + catch (IOException e) { + throw new PyExternalProcessException(ERROR_ACCESS_DENIED, helperPath, args, e.getMessage()); } } else { - if (workingDir == null) { - workingDir = new File(homePath).getParent(); - } - final List<String> cmdline = new ArrayList<String>(); - cmdline.add(homePath); - cmdline.add(helperPath); - cmdline.addAll(args); - LOG.info("Running packaging tool: " + StringUtil.join(cmdline, " ")); - - final boolean canCreate = FileUtil.ensureCanCreateFile(new File(homePath)); - if (!canCreate && !SystemInfo.isWindows && askForSudo) { //is system site interpreter --> we need sudo privileges - try { - final ProcessOutput result = ExecUtil.sudoAndGetOutput(cmdline, - "Please enter your password to make changes in system packages: ", - workingDir); - String message = result.getStderr(); - if (result.getExitCode() != 0) { - final String stdout = result.getStdout(); - if (StringUtil.isEmptyOrSpaces(message)) { - message = stdout; - } - if (StringUtil.isEmptyOrSpaces(message)) { - message = "Failed to perform action. Permission denied."; - } - throw new PyExternalProcessException(result.getExitCode(), helperPath, args, message); - } - if (SystemInfo.isMac && !StringUtil.isEmptyOrSpaces(message)) { - throw new PyExternalProcessException(result.getExitCode(), helperPath, args, message); - } - return result; - } - catch (ExecutionException e) { - throw new PyExternalProcessException(ERROR_EXECUTION, helperPath, args, e.getMessage()); - } - catch (IOException e) { - throw new PyExternalProcessException(ERROR_ACCESS_DENIED, helperPath, args, e.getMessage()); - } - } - else { - return PySdkUtil.getProcessOutput(workingDir, ArrayUtil.toStringArray(cmdline), TIMEOUT); - } + return PySdkUtil.getProcessOutput(workingDir, ArrayUtil.toStringArray(cmdline), TIMEOUT); } } - private static String quoteIfNeeded(String arg) { - return arg.replace("<", "\\<").replace(">", "\\>"); //TODO: move this logic to ParametersListUtil.encode - } - @NotNull private static List<PyPackage> parsePackagingToolOutput(@NotNull String s) throws PyExternalProcessException { final String[] lines = StringUtil.splitByLines(s); @@ -976,17 +528,6 @@ public class PyPackageManagerImpl extends PyPackageManager { return packages; } - - @Override - public void showInstallationError(Project project, String title, String description) { - PackagesNotificationPanel.showError(project, title, description); - } - - @Override - public void showInstallationError(Component owner, String title, String description) { - PackagesNotificationPanel.showError(owner, title, description); - } - private class MySdkRootWatcher extends BulkFileListener.Adapter { @Override public void after(@NotNull List<? extends VFileEvent> events) { diff --git a/python/src/com/jetbrains/python/packaging/PyPackageManagerUI.java b/python/src/com/jetbrains/python/packaging/PyPackageManagerUI.java new file mode 100644 index 000000000000..28dfa6c834a4 --- /dev/null +++ b/python/src/com/jetbrains/python/packaging/PyPackageManagerUI.java @@ -0,0 +1,367 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jetbrains.python.packaging; + +import com.intellij.icons.AllIcons; +import com.intellij.notification.Notification; +import com.intellij.notification.NotificationListener; +import com.intellij.notification.NotificationType; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.Task; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.Function; +import com.intellij.webcore.packaging.PackagesNotificationPanel; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.event.HyperlinkEvent; +import java.util.*; + +/** +* @author vlan +*/ +public class PyPackageManagerUI { + private static final Logger LOG = Logger.getInstance(PyPackageManagerUI.class); + @Nullable private Listener myListener; + @NotNull private Project myProject; + @NotNull private Sdk mySdk; + + public interface Listener { + void started(); + + void finished(List<PyExternalProcessException> exceptions); + } + + public PyPackageManagerUI(@NotNull Project project, @NotNull Sdk sdk, @Nullable Listener listener) { + myProject = project; + mySdk = sdk; + myListener = listener; + } + + public void installManagement() { + ProgressManager.getInstance().run(new InstallManagementTask(myProject, mySdk, myListener)); + } + + public void install(@NotNull final List<PyRequirement> requirements, @NotNull final List<String> extraArgs) { + ProgressManager.getInstance().run(new InstallTask(myProject, mySdk, requirements, extraArgs, myListener)); + } + + public void uninstall(@NotNull final List<PyPackage> packages) { + if (checkDependents(packages)) { + return; + } + ProgressManager.getInstance().run(new UninstallTask(myProject, mySdk, myListener, packages)); + } + + private boolean checkDependents(@NotNull final List<PyPackage> packages) { + try { + final Map<String, Set<PyPackage>> dependentPackages = collectDependents(packages, mySdk); + final int[] warning = {0}; + if (!dependentPackages.isEmpty()) { + ApplicationManager.getApplication().invokeAndWait(new Runnable() { + @Override + public void run() { + if (dependentPackages.size() == 1) { + String message = "You are attempting to uninstall "; + List<String> dep = new ArrayList<String>(); + int size = 1; + for (Map.Entry<String, Set<PyPackage>> entry : dependentPackages.entrySet()) { + final Set<PyPackage> value = entry.getValue(); + size = value.size(); + dep.add(entry.getKey() + " package which is required for " + StringUtil.join(value, ", ")); + } + message += StringUtil.join(dep, "\n"); + message += size == 1 ? " package" : " packages"; + message += "\n\nDo you want to proceed?"; + warning[0] = Messages.showYesNoDialog(message, "Warning", + AllIcons.General.BalloonWarning); + } + else { + String message = "You are attempting to uninstall packages which are required for another packages.\n\n"; + List<String> dep = new ArrayList<String>(); + for (Map.Entry<String, Set<PyPackage>> entry : dependentPackages.entrySet()) { + dep.add(entry.getKey() + " -> " + StringUtil.join(entry.getValue(), ", ")); + } + message += StringUtil.join(dep, "\n"); + message += "\n\nDo you want to proceed?"; + warning[0] = Messages.showYesNoDialog(message, "Warning", + AllIcons.General.BalloonWarning); + } + } + }, ModalityState.current()); + } + if (warning[0] != Messages.YES) return true; + } + catch (PyExternalProcessException e) { + LOG.info("Error loading packages dependents: " + e.getMessage(), e); + } + return false; + } + + private static Map<String, Set<PyPackage>> collectDependents(@NotNull final List<PyPackage> packages, Sdk sdk) + throws PyExternalProcessException { + Map<String, Set<PyPackage>> dependentPackages = new HashMap<String, Set<PyPackage>>(); + for (PyPackage pkg : packages) { + final Set<PyPackage> dependents = PyPackageManager.getInstance(sdk).getDependents(pkg); + if (dependents != null && !dependents.isEmpty()) { + for (PyPackage dependent : dependents) { + if (!packages.contains(dependent)) { + dependentPackages.put(pkg.getName(), dependents); + } + } + } + } + return dependentPackages; + } + + private abstract static class PackagingTask extends Task.Backgroundable { + private static final String PACKAGING_GROUP_ID = "Packaging"; + + @Nullable protected final Listener myListener; + + public PackagingTask(@Nullable Project project, @NotNull String title, @Nullable Listener listener) { + super(project, title); + myListener = listener; + } + + @Override + public void run(@NotNull ProgressIndicator indicator) { + taskStarted(indicator); + taskFinished(runTask(indicator)); + } + + @NotNull + protected abstract List<PyExternalProcessException> runTask(@NotNull ProgressIndicator indicator); + + @NotNull + protected abstract String getSuccessTitle(); + + @NotNull + protected abstract String getSuccessDescription(); + + @NotNull + protected abstract String getFailureTitle(); + + protected void taskStarted(@NotNull ProgressIndicator indicator) { + indicator.setText(getTitle() + "..."); + if (myListener != null) { + ApplicationManager.getApplication().invokeLater(new Runnable() { + @Override + public void run() { + myListener.started(); + } + }); + } + } + + protected void taskFinished(@NotNull final List<PyExternalProcessException> exceptions) { + final Ref<Notification> notificationRef = new Ref<Notification>(null); + if (exceptions.isEmpty()) { + notificationRef.set(new Notification(PACKAGING_GROUP_ID, getSuccessTitle(), getSuccessDescription(), + NotificationType.INFORMATION)); + } + else { + final String firstLine = getTitle() + ": error occurred."; + final String description = createDescription(exceptions, firstLine); + notificationRef.set(new Notification(PACKAGING_GROUP_ID, getFailureTitle(), + firstLine + " <a href=\"xxx\">Details...</a>", + NotificationType.ERROR, + new NotificationListener() { + @Override + public void hyperlinkUpdate(@NotNull Notification notification, + @NotNull HyperlinkEvent event) { + assert myProject != null; + PackagesNotificationPanel.showError(myProject, getFailureTitle(), description); + } + } + )); + } + ApplicationManager.getApplication().invokeLater(new Runnable() { + @Override + public void run() { + if (myListener != null) { + myListener.finished(exceptions); + } + final Notification notification = notificationRef.get(); + if (notification != null) { + notification.notify(myProject); + } + } + }); + } + } + + private static class InstallTask extends PackagingTask { + @NotNull protected final Sdk mySdk; + @NotNull private final List<PyRequirement> myRequirements; + @NotNull private final List<String> myExtraArgs; + + public InstallTask(@Nullable Project project, + @NotNull Sdk sdk, + @NotNull List<PyRequirement> requirements, + @NotNull List<String> extraArgs, + @Nullable Listener listener) { + super(project, "Installing packages", listener); + mySdk = sdk; + myRequirements = requirements; + myExtraArgs = extraArgs; + } + + @NotNull + @Override + protected List<PyExternalProcessException> runTask(@NotNull ProgressIndicator indicator) { + final List<PyExternalProcessException> exceptions = new ArrayList<PyExternalProcessException>(); + final int size = myRequirements.size(); + final PyPackageManager manager = PyPackageManagers.getInstance().forSdk(mySdk); + for (int i = 0; i < size; i++) { + final PyRequirement requirement = myRequirements.get(i); + indicator.setText(String.format("Installing package '%s'...", requirement)); + indicator.setFraction((double)i / size); + try { + manager.install(Arrays.asList(requirement), myExtraArgs); + } + catch (PyExternalProcessException e) { + exceptions.add(e); + } + } + manager.refresh(); + return exceptions; + } + + @NotNull + @Override + protected String getSuccessTitle() { + return "Packages installed successfully"; + } + + @NotNull + @Override + protected String getSuccessDescription() { + return "Installed packages: " + PyPackageUtil.requirementsToString(myRequirements); + } + + @NotNull + @Override + protected String getFailureTitle() { + return "Install packages failed"; + } + } + + private static class InstallManagementTask extends InstallTask { + + public InstallManagementTask(@Nullable Project project, + @NotNull Sdk sdk, + @Nullable Listener listener) { + super(project, sdk, Collections.<PyRequirement>emptyList(), Collections.<String>emptyList(), listener); + } + + @NotNull + @Override + protected List<PyExternalProcessException> runTask(@NotNull ProgressIndicator indicator) { + final List<PyExternalProcessException> exceptions = new ArrayList<PyExternalProcessException>(); + final PyPackageManager manager = PyPackageManagers.getInstance().forSdk(mySdk); + indicator.setText("Installing packaging tools..."); + indicator.setIndeterminate(true); + try { + manager.installManagement(); + } + catch (PyExternalProcessException e) { + exceptions.add(e); + } + manager.refresh(); + return exceptions; + } + + @NotNull + @Override + protected String getSuccessDescription() { + return "Installed Python packaging tools"; + } + } + + private static class UninstallTask extends PackagingTask { + @NotNull private final Sdk mySdk; + @NotNull private final List<PyPackage> myPackages; + + public UninstallTask(@Nullable Project project, + @NotNull Sdk sdk, + @Nullable Listener listener, + @NotNull List<PyPackage> packages) { + super(project, "Uninstalling packages", listener); + mySdk = sdk; + myPackages = packages; + } + + @NotNull + @Override + protected List<PyExternalProcessException> runTask(@NotNull ProgressIndicator indicator) { + final PyPackageManager manager = PyPackageManagers.getInstance().forSdk(mySdk); + try { + manager.uninstall(myPackages); + return Arrays.asList(); + } + catch (PyExternalProcessException e) { + return Arrays.asList(e); + } + finally { + manager.refresh(); + } + } + + @NotNull + @Override + protected String getSuccessTitle() { + return "Packages uninstalled successfully"; + } + + @NotNull + @Override + protected String getSuccessDescription() { + final String packagesString = StringUtil.join(myPackages, new Function<PyPackage, String>() { + @Override + public String fun(PyPackage pkg) { + return "'" + pkg.getName() + "'"; + } + }, ", "); + return "Uninstalled packages: " + packagesString; + } + + @NotNull + @Override + protected String getFailureTitle() { + return "Uninstall packages failed"; + } + } + + public static String createDescription(List<PyExternalProcessException> exceptions, String firstLine) { + final StringBuilder b = new StringBuilder(); + b.append(firstLine); + b.append("\n\n"); + for (PyExternalProcessException exception : exceptions) { + b.append(exception.toString()); + b.append("\n"); + } + return b.toString(); + } +} diff --git a/python/src/com/jetbrains/python/packaging/PyPackageManagersImpl.java b/python/src/com/jetbrains/python/packaging/PyPackageManagersImpl.java index 054e9e01d23f..e68cede3f799 100644 --- a/python/src/com/jetbrains/python/packaging/PyPackageManagersImpl.java +++ b/python/src/com/jetbrains/python/packaging/PyPackageManagersImpl.java @@ -15,13 +15,11 @@ */ package com.jetbrains.python.packaging; -import com.intellij.openapi.module.Module; import com.intellij.openapi.projectRoots.Sdk; +import com.jetbrains.python.sdk.PythonSdkType; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import java.util.HashMap; -import java.util.List; import java.util.Map; /** @@ -36,22 +34,14 @@ public class PyPackageManagersImpl extends PyPackageManagers { final String name = sdk.getName(); PyPackageManagerImpl manager = myInstances.get(name); if (manager == null) { - manager = new PyPackageManagerImpl(sdk); + if (PythonSdkType.isRemote(sdk)) { + manager = new PyRemotePackageManagerImpl(sdk); + } + else { + manager = new PyPackageManagerImpl(sdk); + } myInstances.put(name, manager); } return manager; } - - @Nullable - @Override - public List<PyRequirement> getRequirements(Module module) { - return PyPackageManagerImpl.getRequirements(module); - } - - - @Nullable - @Override - public List<PyRequirement> getRequirementsFromTxt(Module module) { - return PyPackageManagerImpl.getRequirementsFromTxt(module); - } } diff --git a/python/src/com/jetbrains/python/packaging/PyRemotePackageManagerImpl.java b/python/src/com/jetbrains/python/packaging/PyRemotePackageManagerImpl.java new file mode 100644 index 000000000000..6c6de77d9454 --- /dev/null +++ b/python/src/com/jetbrains/python/packaging/PyRemotePackageManagerImpl.java @@ -0,0 +1,177 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jetbrains.python.packaging; + +import com.google.common.base.Function; +import com.google.common.collect.Collections2; +import com.intellij.execution.ExecutionException; +import com.intellij.execution.process.ProcessOutput; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.projectRoots.SdkAdditionalData; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.remote.RemoteFile; +import com.intellij.remote.RemoteSdkAdditionalData; +import com.intellij.remote.RemoteSdkCredentials; +import com.intellij.remote.VagrantNotStartedException; +import com.intellij.util.ArrayUtil; +import com.intellij.util.PathMappingSettings; +import com.jetbrains.python.remote.PyRemoteSdkAdditionalDataBase; +import com.jetbrains.python.remote.PythonRemoteInterpreterManager; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author vlan + */ +public class PyRemotePackageManagerImpl extends PyPackageManagerImpl { + private static final String LAUNCH_VAGRANT = "launchVagrant"; + public static final int ERROR_VAGRANT_NOT_LAUNCHED = 101; + public static final int ERROR_REMOTE_ACCESS = 102; + + private static final Logger LOG = Logger.getInstance(PyRemotePackageManagerImpl.class); + + PyRemotePackageManagerImpl(@NotNull Sdk sdk) { + super(sdk); + } + + @Nullable + @Override + protected String getHelperPath(String helper) { + final SdkAdditionalData sdkData = mySdk.getSdkAdditionalData(); + if (sdkData instanceof PyRemoteSdkAdditionalDataBase) { + final PyRemoteSdkAdditionalDataBase remoteSdkData = (PyRemoteSdkAdditionalDataBase)mySdk.getSdkAdditionalData(); + try { + final RemoteSdkCredentials remoteSdkCredentials = remoteSdkData.getRemoteSdkCredentials(false); + if (!StringUtil.isEmpty(remoteSdkCredentials.getHelpersPath())) { + return new RemoteFile(remoteSdkCredentials.getHelpersPath(), helper).getPath(); + } + else { + return null; + } + } + catch (Exception e) { + LOG.error(e); + } + } + return null; + } + + @Override + protected ProcessOutput getProcessOutput(@NotNull String helperPath, + @NotNull List<String> args, + boolean askForSudo, + @Nullable String workingDir) throws PyExternalProcessException { + final String homePath = mySdk.getHomePath(); + if (homePath == null) { + throw new PyExternalProcessException(ERROR_INVALID_SDK, helperPath, args, "Cannot find interpreter for SDK"); + } + final SdkAdditionalData sdkData = mySdk.getSdkAdditionalData(); + if (sdkData instanceof PyRemoteSdkAdditionalDataBase) { //remote interpreter + RemoteSdkCredentials remoteSdkCredentials; + try { + remoteSdkCredentials = ((RemoteSdkAdditionalData)sdkData).getRemoteSdkCredentials(false); + } + catch (InterruptedException e) { + LOG.error(e); + remoteSdkCredentials = null; + } + catch (final ExecutionException e) { + if (e.getCause() instanceof VagrantNotStartedException) { + throw new PyExternalProcessException(ERROR_VAGRANT_NOT_LAUNCHED, helperPath, args, "Vagrant instance is down. <a href=\"" + + LAUNCH_VAGRANT + + "\">Launch vagrant</a>") + .withHandler(LAUNCH_VAGRANT, new Runnable() { + @Override + public void run() { + final PythonRemoteInterpreterManager manager = PythonRemoteInterpreterManager.getInstance(); + if (manager != null) { + + try { + manager.runVagrant(((VagrantNotStartedException)e.getCause()).getVagrantFolder()); + clearCaches(); + } + catch (ExecutionException e1) { + throw new RuntimeException(e1); + } + } + } + }); + } + else { + throw new PyExternalProcessException(ERROR_REMOTE_ACCESS, helperPath, args, e.getMessage()); + } + } + final PythonRemoteInterpreterManager manager = PythonRemoteInterpreterManager.getInstance(); + if (manager != null && remoteSdkCredentials != null) { + final List<String> cmdline = new ArrayList<String>(); + cmdline.add(homePath); + cmdline.add(RemoteFile.detectSystemByPath(homePath).createRemoteFile(helperPath).getPath()); + cmdline.addAll(Collections2.transform(args, new Function<String, String>() { + @Override + public String apply(@Nullable String input) { + return quoteIfNeeded(input); + } + })); + try { + if (askForSudo) { + askForSudo = !manager.ensureCanWrite(null, remoteSdkCredentials, remoteSdkCredentials.getInterpreterPath()); + } + ProcessOutput processOutput; + do { + PathMappingSettings mappings = manager.setupMappings(null, (PyRemoteSdkAdditionalDataBase)sdkData, null); + processOutput = + manager.runRemoteProcess(null, remoteSdkCredentials, mappings, ArrayUtil.toStringArray(cmdline), workingDir, askForSudo); + if (askForSudo && processOutput.getStderr().contains("sudo: 3 incorrect password attempts")) { + continue; + } + break; + } + while (true); + return processOutput; + } + catch (ExecutionException e) { + throw new PyExternalProcessException(ERROR_INVALID_SDK, helperPath, args, "Error running SDK: " + e.getMessage(), e); + } + } + else { + throw new PyExternalProcessException(ERROR_INVALID_SDK, helperPath, args, + PythonRemoteInterpreterManager.WEB_DEPLOYMENT_PLUGIN_IS_DISABLED); + } + } + else { + throw new PyExternalProcessException(ERROR_INVALID_SDK, helperPath, args, "Invalid remote SDK"); + } + } + + @Override + protected void subscribeToLocalChanges(Sdk sdk) { + // Local VFS changes aren't needed + } + + @Override + protected void installManagement(@NotNull String name) throws PyExternalProcessException { + super.installManagement(name); + // TODO: remove temp directory for remote interpreter + } + + private static String quoteIfNeeded(String arg) { + return arg.replace("<", "\\<").replace(">", "\\>"); //TODO: move this logic to ParametersListUtil.encode + } +} diff --git a/python/src/com/jetbrains/python/packaging/ui/PyInstalledPackagesPanel.java b/python/src/com/jetbrains/python/packaging/ui/PyInstalledPackagesPanel.java index 348d2817ec75..094149336d5d 100644 --- a/python/src/com/jetbrains/python/packaging/ui/PyInstalledPackagesPanel.java +++ b/python/src/com/jetbrains/python/packaging/ui/PyInstalledPackagesPanel.java @@ -25,10 +25,8 @@ import com.intellij.util.Consumer; import com.intellij.webcore.packaging.InstalledPackage; import com.intellij.webcore.packaging.InstalledPackagesPanel; import com.intellij.webcore.packaging.PackagesNotificationPanel; -import com.jetbrains.python.packaging.PyExternalProcessException; -import com.jetbrains.python.packaging.PyPackage; -import com.jetbrains.python.packaging.PyPackageManager; -import com.jetbrains.python.packaging.PyPackageManagerImpl; +import com.jetbrains.python.packaging.*; +import com.jetbrains.python.sdk.PySdkUtil; import com.jetbrains.python.sdk.PythonSdkType; import com.jetbrains.python.sdk.flavors.IronPythonSdkFlavor; import com.jetbrains.python.sdk.flavors.PythonSdkFlavor; @@ -42,33 +40,13 @@ import java.util.Set; * @author yole */ public class PyInstalledPackagesPanel extends InstalledPackagesPanel { - public static final String INSTALL_SETUPTOOLS = "installSetuptools"; - public static final String INSTALL_PIP = "installPip"; + public static final String INSTALL_MANAGEMENT = "installManagement"; public static final String CREATE_VENV = "createVEnv"; - private boolean myHasSetuptools; - private boolean myHasPip = true; + private boolean myHasManagement = false; public PyInstalledPackagesPanel(Project project, PackagesNotificationPanel area) { super(project, area); - myNotificationArea.addLinkHandler(INSTALL_SETUPTOOLS, new Runnable() { - @Override - public void run() { - final Sdk sdk = getSelectedSdk(); - if (sdk != null) { - installManagementTool(sdk, PyPackageManagerImpl.SETUPTOOLS); - } - } - }); - myNotificationArea.addLinkHandler(INSTALL_PIP, new Runnable() { - @Override - public void run() { - final Sdk sdk = getSelectedSdk(); - if (sdk != null) { - installManagementTool(sdk, PyPackageManagerImpl.PIP); - } - } - }); } private Sdk getSelectedSdk() { @@ -85,19 +63,8 @@ public class PyInstalledPackagesPanel extends InstalledPackagesPanel { application.executeOnPooledThread(new Runnable() { @Override public void run() { - PyExternalProcessException exc = null; - try { - PyPackageManagerImpl packageManager = (PyPackageManagerImpl)PyPackageManager.getInstance(selectedSdk); - myHasSetuptools = packageManager.findInstalledPackage(PyPackageManagerImpl.PACKAGE_SETUPTOOLS) != null; - if (!myHasSetuptools) { - myHasSetuptools = packageManager.findInstalledPackage(PyPackageManagerImpl.PACKAGE_DISTRIBUTE) != null; - } - myHasPip = packageManager.findInstalledPackage(PyPackageManagerImpl.PACKAGE_PIP) != null; - } - catch (PyExternalProcessException e) { - exc = e; - } - final PyExternalProcessException externalProcessException = exc; + PyPackageManager packageManager = PyPackageManager.getInstance(selectedSdk); + myHasManagement = packageManager.hasManagement(false); application.invokeLater(new Runnable() { @Override public void run() { @@ -112,42 +79,24 @@ public class PyInstalledPackagesPanel extends InstalledPackagesPanel { myNotificationArea.hide(); if (!invalid) { String text = null; - if (externalProcessException != null) { - final int retCode = externalProcessException.getRetcode(); - if (retCode == PyPackageManagerImpl.ERROR_NO_PIP) { - myHasPip = false; - } - else if (retCode == PyPackageManagerImpl.ERROR_NO_SETUPTOOLS) { - myHasSetuptools = false; - } - else { - text = externalProcessException.getMessage(); - } - final boolean hasPackagingTools = myHasPip && myHasSetuptools; - allowCreateVirtualEnv &= !hasPackagingTools; - - if (externalProcessException.hasHandler()) { - final String key = externalProcessException.getHandler().first; - myNotificationArea.addLinkHandler(key, - new Runnable() { - @Override - public void run() { - externalProcessException.getHandler().second.run(); - myNotificationArea.removeLinkHandler(key); - updateNotifications(selectedSdk); + if (!myHasManagement) { + myNotificationArea.addLinkHandler(INSTALL_MANAGEMENT, + new Runnable() { + @Override + public void run() { + final Sdk sdk = getSelectedSdk(); + if (sdk != null) { + installManagementTools(sdk); } + myNotificationArea.removeLinkHandler(INSTALL_MANAGEMENT); + updateNotifications(selectedSdk); } - ); - } + } + ); } - if (text == null) { - if (!myHasSetuptools) { - text = "Python package management tools not found. <a href=\"" + INSTALL_SETUPTOOLS + "\">Install 'setuptools'</a>"; - } - else if (!myHasPip) { - text = "Python packaging tool 'pip' not found. <a href=\"" + INSTALL_PIP + "\">Install 'pip'</a>"; - } + if (!myHasManagement) { + text = "Python packaging tools not found. <a href=\"" + INSTALL_MANAGEMENT + "\">Install packaging tools</a>"; } if (text != null) { if (allowCreateVirtualEnv) { @@ -157,7 +106,7 @@ public class PyInstalledPackagesPanel extends InstalledPackagesPanel { } } - myInstallButton.setEnabled(!invalid && externalProcessException == null && myHasPip); + myInstallButton.setEnabled(!invalid && myHasManagement); } } }, ModalityState.any()); @@ -170,8 +119,8 @@ public class PyInstalledPackagesPanel extends InstalledPackagesPanel { return Sets.newHashSet("pip", "distutils", "setuptools"); } - private void installManagementTool(@NotNull final Sdk sdk, final String name) { - final PyPackageManagerImpl.UI ui = new PyPackageManagerImpl.UI(myProject, sdk, new PyPackageManagerImpl.UI.Listener() { + private void installManagementTools(@NotNull final Sdk sdk) { + final PyPackageManagerUI ui = new PyPackageManagerUI(myProject, sdk, new PyPackageManagerUI.Listener() { @Override public void started() { myPackagesTable.setPaintBusy(true); @@ -180,11 +129,11 @@ public class PyInstalledPackagesPanel extends InstalledPackagesPanel { @Override public void finished(List<PyExternalProcessException> exceptions) { myPackagesTable.setPaintBusy(false); - PyPackageManagerImpl packageManager = (PyPackageManagerImpl)PyPackageManager.getInstance(sdk); + PyPackageManager packageManager = PyPackageManager.getInstance(sdk); if (!exceptions.isEmpty()) { - final String firstLine = "Install package failed. "; - final String description = PyPackageManagerImpl.UI.createDescription(exceptions, firstLine); - packageManager.showInstallationError(myProject, "Failed to install " + name, description); + final String firstLine = "Install Python packaging tools failed. "; + final String description = PyPackageManagerUI.createDescription(exceptions, firstLine); + PackagesNotificationPanel.showError(myProject, "Failed to install Python packaging tools", description); } packageManager.refresh(); updatePackages(new PyPackageManagementService(myProject, sdk)); @@ -194,22 +143,22 @@ public class PyInstalledPackagesPanel extends InstalledPackagesPanel { updateNotifications(sdk); } }); - ui.installManagement(name); + ui.installManagement(); } @Override protected boolean canUninstallPackage(InstalledPackage pkg) { - if (!myHasPip) return false; + if (!myHasManagement) return false; if (PythonSdkType.isVirtualEnv(getSelectedSdk()) && pkg instanceof PyPackage) { final String location = ((PyPackage)pkg).getLocation(); - if (location != null && location.startsWith(PyPackageManagerImpl.getUserSite())) { + if (location != null && location.startsWith(PySdkUtil.getUserSite())) { return false; } } final String name = pkg.getName(); - if (PyPackageManagerImpl.PACKAGE_PIP.equals(name) || - PyPackageManagerImpl.PACKAGE_SETUPTOOLS.equals(name) || - PyPackageManagerImpl.PACKAGE_DISTRIBUTE.equals(name)) { + if (PyPackageManager.PACKAGE_PIP.equals(name) || + PyPackageManager.PACKAGE_SETUPTOOLS.equals(name) || + PyPackageManager.PACKAGE_DISTRIBUTE.equals(name)) { return false; } return true; @@ -217,6 +166,6 @@ public class PyInstalledPackagesPanel extends InstalledPackagesPanel { @Override protected boolean canUpgradePackage(InstalledPackage pyPackage) { - return myHasPip; + return myHasManagement; } } diff --git a/python/src/com/jetbrains/python/packaging/ui/PyPackageManagementService.java b/python/src/com/jetbrains/python/packaging/ui/PyPackageManagementService.java index ffd291a995b1..95330b361dfb 100644 --- a/python/src/com/jetbrains/python/packaging/ui/PyPackageManagementService.java +++ b/python/src/com/jetbrains/python/packaging/ui/PyPackageManagementService.java @@ -23,6 +23,7 @@ import com.intellij.webcore.packaging.InstalledPackage; import com.intellij.webcore.packaging.PackageManagementService; import com.intellij.webcore.packaging.RepoPackage; import com.jetbrains.python.packaging.*; +import com.jetbrains.python.sdk.PySdkUtil; import com.jetbrains.python.sdk.PythonSdkType; import org.apache.xmlrpc.AsyncCallback; import org.jetbrains.annotations.NonNls; @@ -112,7 +113,7 @@ public class PyPackageManagementService extends PackageManagementService { public String getInstallToUserText() { String userSiteText = "Install to user's site packages directory"; if (!PythonSdkType.isRemote(mySdk)) - userSiteText += " (" + PyPackageManagerImpl.getUserSite() + ")"; + userSiteText += " (" + PySdkUtil.getUserSite() + ")"; return userSiteText; } @@ -130,12 +131,12 @@ public class PyPackageManagementService extends PackageManagementService { public Collection<InstalledPackage> getInstalledPackages() throws IOException { List<PyPackage> packages; try { - packages = ((PyPackageManagerImpl)PyPackageManager.getInstance(mySdk)).getPackages(); + packages = PyPackageManager.getInstance(mySdk).getPackages(false); } catch (PyExternalProcessException e) { throw new IOException(e); } - return new ArrayList<InstalledPackage>(packages); + return packages != null ? new ArrayList<InstalledPackage>(packages) : new ArrayList<InstalledPackage>(); } @Override @@ -145,7 +146,7 @@ public class PyPackageManagementService extends PackageManagementService { final String repository = PyPIPackageUtil.PYPI_URL.equals(repoPackage.getRepoUrl()) ? null : repoPackage.getRepoUrl(); final List<String> extraArgs = new ArrayList<String>(); if (installToUser) { - extraArgs.add(PyPackageManagerImpl.USE_USER_SITE); + extraArgs.add(PyPackageManager.USE_USER_SITE); } if (extraOptions != null) { // TODO: Respect arguments quotation @@ -166,7 +167,7 @@ public class PyPackageManagementService extends PackageManagementService { req = new PyRequirement(packageName); } - final PyPackageManagerImpl.UI ui = new PyPackageManagerImpl.UI(myProject, mySdk, new PyPackageManagerImpl.UI.Listener() { + final PyPackageManagerUI ui = new PyPackageManagerUI(myProject, mySdk, new PyPackageManagerUI.Listener() { @Override public void started() { listener.operationStarted(packageName); @@ -183,7 +184,7 @@ public class PyPackageManagementService extends PackageManagementService { private String toErrorDescription(List<PyExternalProcessException> exceptions) { String errorDescription = null; if (exceptions != null && exceptions.size() > 0) { - errorDescription = PyPackageManagerImpl.UI.createDescription(exceptions, ""); + errorDescription = PyPackageManagerUI.createDescription(exceptions, ""); } return errorDescription; } @@ -191,7 +192,7 @@ public class PyPackageManagementService extends PackageManagementService { @Override public void uninstallPackages(List<InstalledPackage> installedPackages, final Listener listener) { final String packageName = installedPackages.size() == 1 ? installedPackages.get(0).getName() : null; - PyPackageManagerImpl.UI ui = new PyPackageManagerImpl.UI(myProject, mySdk, new PyPackageManagerImpl.UI.Listener() { + PyPackageManagerUI ui = new PyPackageManagerUI(myProject, mySdk, new PyPackageManagerUI.Listener() { @Override public void started() { listener.operationStarted(packageName); diff --git a/python/src/com/jetbrains/python/projectView/PyElementNode.java b/python/src/com/jetbrains/python/projectView/PyElementNode.java index 20c7b811b8a6..6ec347236773 100644 --- a/python/src/com/jetbrains/python/projectView/PyElementNode.java +++ b/python/src/com/jetbrains/python/projectView/PyElementNode.java @@ -19,11 +19,14 @@ import com.intellij.ide.projectView.PresentationData; import com.intellij.ide.projectView.ViewSettings; import com.intellij.ide.projectView.impl.nodes.BasePsiNode; import com.intellij.ide.util.treeView.AbstractTreeNode; +import com.intellij.navigation.ItemPresentation; import com.intellij.openapi.project.Project; +import com.jetbrains.python.PyNames; import com.jetbrains.python.psi.PyClass; import com.jetbrains.python.psi.PyElement; import com.jetbrains.python.psi.PyFunction; +import javax.swing.*; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -57,14 +60,23 @@ public class PyElementNode extends BasePsiNode<PyElement> { @Override protected void updateImpl(PresentationData data) { - PyElement value = getValue(); + final PyElement value = getValue(); final String name = value.getName(); - StringBuilder presentableText = new StringBuilder(name != null ? name : "<unnamed>"); - if (value instanceof PyFunction) { - presentableText.append(((PyFunction) value).getParameterList().getPresentableText(false)); + final ItemPresentation presentation = value.getPresentation(); + String presentableText = name != null ? name : PyNames.UNNAMED_ELEMENT; + Icon presentableIcon = value.getIcon(0); + if (presentation != null) { + final String text = presentation.getPresentableText(); + if (text != null) { + presentableText = text; + } + final Icon icon = presentation.getIcon(false); + if (icon != null) { + presentableIcon = icon; + } } - data.setPresentableText(presentableText.toString()); - data.setIcon(value.getIcon(0)); + data.setPresentableText(presentableText); + data.setIcon(presentableIcon); } @Override diff --git a/python/src/com/jetbrains/python/psi/PyUtil.java b/python/src/com/jetbrains/python/psi/PyUtil.java index cd4e582bb6f2..dc5cbf4917a5 100644 --- a/python/src/com/jetbrains/python/psi/PyUtil.java +++ b/python/src/com/jetbrains/python/psi/PyUtil.java @@ -1394,8 +1394,8 @@ public class PyUtil { @Nullable public static PsiElement findPrevAtOffset(PsiFile psiFile, int caretOffset, Class... toSkip) { - PsiElement element = psiFile.findElementAt(caretOffset); - if (element == null || caretOffset < 0) { + PsiElement element; + if (caretOffset < 0) { return null; } int lineStartOffset = 0; diff --git a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java index bb2104fc0c77..611cbc84ad2e 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java @@ -17,6 +17,7 @@ package com.jetbrains.python.psi.impl; import com.intellij.codeInsight.completion.CompletionUtil; import com.intellij.lang.ASTNode; +import com.intellij.navigation.ItemPresentation; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.NotNullLazyValue; @@ -53,10 +54,13 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.util.*; +import static com.intellij.openapi.util.text.StringUtil.join; +import static com.intellij.openapi.util.text.StringUtil.notNullize; + /** * @author yole */ -public class PyClassImpl extends PyPresentableElementImpl<PyClassStub> implements PyClass { +public class PyClassImpl extends PyBaseElementImpl<PyClassStub> implements PyClass { public static final PyClass[] EMPTY_ARRAY = new PyClassImpl[0]; private List<PyTargetExpression> myInstanceAttributes; @@ -288,6 +292,32 @@ public class PyClassImpl extends PyPresentableElementImpl<PyClassStub> implement return result.toArray(new PyClass[result.size()]); } + @Override + public ItemPresentation getPresentation() { + return new PyElementPresentation(this) { + @Nullable + @Override + public String getPresentableText() { + if (!isValid()) { + return null; + } + final StringBuilder result = new StringBuilder(notNullize(getName(), PyNames.UNNAMED_ELEMENT)); + final PyExpression[] superClassExpressions = getSuperClassExpressions(); + if (superClassExpressions.length > 0) { + result.append("("); + result.append(join(Arrays.asList(superClassExpressions), new Function<PyExpression, String>() { + public String fun(PyExpression expr) { + String name = expr.getText(); + return notNullize(name, PyNames.UNNAMED_ELEMENT); + } + }, ", ")); + result.append(")"); + } + return result.toString(); + } + }; + } + @NotNull private static List<PyClassLikeType> mroMerge(@NotNull List<List<PyClassLikeType>> sequences) { List<PyClassLikeType> result = new LinkedList<PyClassLikeType>(); // need to insert to 0th position on linearize diff --git a/python/src/com/jetbrains/python/psi/impl/PyElementPresentation.java b/python/src/com/jetbrains/python/psi/impl/PyElementPresentation.java new file mode 100644 index 000000000000..7888071c7321 --- /dev/null +++ b/python/src/com/jetbrains/python/psi/impl/PyElementPresentation.java @@ -0,0 +1,76 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jetbrains.python.psi.impl; + +import com.intellij.navigation.ColoredItemPresentation; +import com.intellij.openapi.editor.colors.TextAttributesKey; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiFile; +import com.jetbrains.python.PyNames; +import com.jetbrains.python.psi.PyElement; +import com.jetbrains.python.psi.resolve.QualifiedNameFinder; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; + +/** +* @author vlan +*/ +public class PyElementPresentation implements ColoredItemPresentation { + @NotNull private final PyElement myElement; + + public PyElementPresentation(@NotNull PyElement element) { + myElement = element; + } + + @Nullable + @Override + public TextAttributesKey getTextAttributesKey() { + return null; + } + + @Nullable + @Override + public String getPresentableText() { + final String name = myElement.getName(); + return name != null ? name : PyNames.UNNAMED_ELEMENT; + } + + @Nullable + @Override + public String getLocationString() { + return "(" + getPackageForFile(myElement.getContainingFile()) + ")"; + } + + @Nullable + @Override + public Icon getIcon(boolean unused) { + return myElement.getIcon(0); + } + + public static String getPackageForFile(@NotNull PsiFile containingFile) { + final VirtualFile vFile = containingFile.getVirtualFile(); + + if (vFile != null) { + final String importableName = QualifiedNameFinder.findShortestImportableName(containingFile, vFile); + if (importableName != null) { + return importableName; + } + } + return ""; + } +} diff --git a/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java index 86f84abb7fb6..2c1842f3337f 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java @@ -16,6 +16,7 @@ package com.jetbrains.python.psi.impl; import com.intellij.lang.ASTNode; +import com.intellij.navigation.ItemPresentation; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.Ref; @@ -54,6 +55,7 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.util.*; +import static com.intellij.openapi.util.text.StringUtil.notNullize; import static com.jetbrains.python.psi.PyFunction.Modifier.CLASSMETHOD; import static com.jetbrains.python.psi.PyFunction.Modifier.STATICMETHOD; import static com.jetbrains.python.psi.impl.PyCallExpressionHelper.interpretAsModifierWrappingCall; @@ -61,7 +63,7 @@ import static com.jetbrains.python.psi.impl.PyCallExpressionHelper.interpretAsMo /** * Implements PyFunction. */ -public class PyFunctionImpl extends PyPresentableElementImpl<PyFunctionStub> implements PyFunction { +public class PyFunctionImpl extends PyBaseElementImpl<PyFunctionStub> implements PyFunction { public PyFunctionImpl(ASTNode astNode) { super(astNode); @@ -259,6 +261,17 @@ public class PyFunctionImpl extends PyPresentableElementImpl<PyFunctionStub> imp return type; } + @Override + public ItemPresentation getPresentation() { + return new PyElementPresentation(this) { + @Nullable + @Override + public String getPresentableText() { + return notNullize(getName(), PyNames.UNNAMED_ELEMENT) + getParameterList().getPresentableText(true); + } + }; + } + @Nullable private PyType replaceSelf(@Nullable PyType returnType, @Nullable PyExpression receiver, @NotNull TypeEvalContext context) { if (receiver != null) { @@ -292,32 +305,30 @@ public class PyFunctionImpl extends PyPresentableElementImpl<PyFunctionStub> imp final PyBuiltinCache cache = PyBuiltinCache.getInstance(this); final PyStatementList statements = getStatementList(); final Set<PyType> types = new LinkedHashSet<PyType>(); - if (statements != null) { - statements.accept(new PyRecursiveElementVisitor() { - @Override - public void visitPyYieldExpression(PyYieldExpression node) { - final PyType type = context.getType(node); - if (node.isDelegating() && type instanceof PyCollectionType) { - final PyCollectionType collectionType = (PyCollectionType)type; - types.add(collectionType.getElementType(context)); - } - else { - types.add(type); - } + statements.accept(new PyRecursiveElementVisitor() { + @Override + public void visitPyYieldExpression(PyYieldExpression node) { + final PyType type = context.getType(node); + if (node.isDelegating() && type instanceof PyCollectionType) { + final PyCollectionType collectionType = (PyCollectionType)type; + types.add(collectionType.getElementType(context)); } - - @Override - public void visitPyFunction(PyFunction node) { - // Ignore nested functions + else { + types.add(type); } - }); - final int n = types.size(); - if (n == 1) { - elementType = Ref.create(types.iterator().next()); } - else if (n > 0) { - elementType = Ref.create(PyUnionType.union(types)); + + @Override + public void visitPyFunction(PyFunction node) { + // Ignore nested functions } + }); + final int n = types.size(); + if (n == 1) { + elementType = Ref.create(types.iterator().next()); + } + else if (n > 0) { + elementType = Ref.create(PyUnionType.union(types)); } if (elementType != null) { final PyClass generator = cache.getClass(PyNames.FAKE_GENERATOR); @@ -335,14 +346,12 @@ public class PyFunctionImpl extends PyPresentableElementImpl<PyFunctionStub> imp public PyType getReturnStatementType(TypeEvalContext typeEvalContext) { ReturnVisitor visitor = new ReturnVisitor(this, typeEvalContext); final PyStatementList statements = getStatementList(); - if (statements != null) { - statements.accept(visitor); - if (isGeneratedStub() && !visitor.myHasReturns) { - if (PyNames.INIT.equals(getName())) { - return PyNoneType.INSTANCE; - } - return null; + statements.accept(visitor); + if (isGeneratedStub() && !visitor.myHasReturns) { + if (PyNames.INIT.equals(getName())) { + return PyNoneType.INSTANCE; } + return null; } return visitor.result(); } @@ -376,9 +385,6 @@ public class PyFunctionImpl extends PyPresentableElementImpl<PyFunctionStub> imp @Nullable public String extractDeprecationMessage() { PyStatementList statementList = getStatementList(); - if (statementList == null) { - return null; - } return extractDeprecationMessage(Arrays.asList(statementList.getStatements())); } @@ -430,7 +436,7 @@ public class PyFunctionImpl extends PyPresentableElementImpl<PyFunctionStub> imp @Nullable @Override public StructuredDocString getStructuredDocString() { - return CachedValuesManager.getManager(getProject()).getCachedValue(this, myCachedStructuredDocStringProvider); + return CachedValuesManager.getCachedValue(this, myCachedStructuredDocStringProvider); } private boolean isGeneratedStub() { @@ -527,15 +533,7 @@ public class PyFunctionImpl extends PyPresentableElementImpl<PyFunctionStub> imp public PyStringLiteralExpression getDocStringExpression() { final PyStatementList stmtList = getStatementList(); - return stmtList != null ? DocStringUtil.findDocStringExpression(stmtList) : null; - } - - protected String getElementLocation() { - final PyClass containingClass = getContainingClass(); - if (containingClass != null) { - return "(" + containingClass.getName() + " in " + getPackageForFile(getContainingFile()) + ")"; - } - return super.getElementLocation(); + return DocStringUtil.findDocStringExpression(stmtList); } @NotNull diff --git a/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java b/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java index cc9178c07f9e..64108441c347 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java @@ -16,6 +16,7 @@ package com.jetbrains.python.psi.impl; import com.intellij.lang.ASTNode; +import com.intellij.navigation.ItemPresentation; import com.intellij.openapi.extensions.Extensions; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; @@ -48,7 +49,7 @@ import java.util.Map; /** * @author yole */ -public class PyNamedParameterImpl extends PyPresentableElementImpl<PyNamedParameterStub> implements PyNamedParameter { +public class PyNamedParameterImpl extends PyBaseElementImpl<PyNamedParameterStub> implements PyNamedParameter { public PyNamedParameterImpl(ASTNode astNode) { super(astNode); } @@ -280,6 +281,11 @@ public class PyNamedParameterImpl extends PyPresentableElementImpl<PyNamedParame return null; } + @Override + public ItemPresentation getPresentation() { + return new PyElementPresentation(this); + } + private static void processLocalCalls(@NotNull PyFunction function, @NotNull Processor<PyCallExpression> processor) { final PsiFile file = function.getContainingFile(); final String name = function.getName(); diff --git a/python/src/com/jetbrains/python/psi/impl/PyPresentableElementImpl.java b/python/src/com/jetbrains/python/psi/impl/PyPresentableElementImpl.java deleted file mode 100644 index d04d04062651..000000000000 --- a/python/src/com/jetbrains/python/psi/impl/PyPresentableElementImpl.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2000-2013 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.jetbrains.python.psi.impl; - -import com.intellij.lang.ASTNode; -import com.intellij.navigation.ItemPresentation; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiFile; -import com.intellij.psi.PsiNamedElement; -import com.intellij.psi.stubs.IStubElementType; -import com.intellij.psi.stubs.StubElement; -import com.jetbrains.python.psi.resolve.QualifiedNameFinder; - -import javax.swing.*; - -/** - * @author yole - */ -public abstract class PyPresentableElementImpl<T extends StubElement> extends PyBaseElementImpl<T> implements PsiNamedElement { - public PyPresentableElementImpl(ASTNode astNode) { - super(astNode); - } - - protected PyPresentableElementImpl(final T stub, final IStubElementType nodeType) { - super(stub, nodeType); - } - - public ItemPresentation getPresentation() { - return new ItemPresentation() { - public String getPresentableText() { - final String name = getName(); - return name != null ? name : "<none>"; - } - - public String getLocationString() { - return getElementLocation(); - } - - public Icon getIcon(final boolean open) { - return PyPresentableElementImpl.this.getIcon(0); - } - }; - } - - protected String getElementLocation() { - return "(" + getPackageForFile(getContainingFile()) + ")"; - } - - public static String getPackageForFile(final PsiFile containingFile) { - final VirtualFile vFile = containingFile.getVirtualFile(); - - if (vFile != null) { - final String importableName = QualifiedNameFinder.findShortestImportableName(containingFile, vFile); - if (importableName != null) { - return importableName; - } - } - return ""; - } -} diff --git a/python/src/com/jetbrains/python/psi/impl/PySingleStarParameterImpl.java b/python/src/com/jetbrains/python/psi/impl/PySingleStarParameterImpl.java index 70a46fbe39f9..44a01759f403 100644 --- a/python/src/com/jetbrains/python/psi/impl/PySingleStarParameterImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PySingleStarParameterImpl.java @@ -16,21 +16,18 @@ package com.jetbrains.python.psi.impl; import com.intellij.lang.ASTNode; -import com.intellij.psi.PsiElement; -import com.intellij.util.IncorrectOperationException; +import com.intellij.navigation.ItemPresentation; import com.jetbrains.python.PyElementTypes; import com.jetbrains.python.psi.PyExpression; import com.jetbrains.python.psi.PyNamedParameter; import com.jetbrains.python.psi.PySingleStarParameter; import com.jetbrains.python.psi.PyTupleParameter; import com.jetbrains.python.psi.stubs.PySingleStarParameterStub; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; /** * @author yole */ -public class PySingleStarParameterImpl extends PyPresentableElementImpl<PySingleStarParameterStub> implements PySingleStarParameter { +public class PySingleStarParameterImpl extends PyBaseElementImpl<PySingleStarParameterStub> implements PySingleStarParameter { public PySingleStarParameterImpl(ASTNode astNode) { super(astNode); } @@ -39,22 +36,22 @@ public class PySingleStarParameterImpl extends PyPresentableElementImpl<PySingle super(stub, PyElementTypes.SINGLE_STAR_PARAMETER); } - public PsiElement setName(@NonNls @NotNull String name) throws IncorrectOperationException { - throw new UnsupportedOperationException(); - } - + @Override public PyNamedParameter getAsNamed() { return null; } + @Override public PyTupleParameter getAsTuple() { return null; } + @Override public PyExpression getDefaultValue() { return null; } + @Override public boolean hasDefaultValue() { return false; } @@ -63,4 +60,9 @@ public class PySingleStarParameterImpl extends PyPresentableElementImpl<PySingle public boolean isSelf() { return false; } + + @Override + public ItemPresentation getPresentation() { + return new PyElementPresentation(this); + } } diff --git a/python/src/com/jetbrains/python/psi/impl/PyStringLiteralExpressionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyStringLiteralExpressionImpl.java index bf519737f0f4..084aeecc3df0 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyStringLiteralExpressionImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyStringLiteralExpressionImpl.java @@ -327,7 +327,7 @@ public class PyStringLiteralExpressionImpl extends PyElementImpl implements PySt @Nullable @Override public String getLocationString() { - return "(" + PyPresentableElementImpl.getPackageForFile(getContainingFile()) + ")"; + return "(" + PyElementPresentation.getPackageForFile(getContainingFile()) + ")"; } @Nullable diff --git a/python/src/com/jetbrains/python/psi/impl/PyTargetExpressionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyTargetExpressionImpl.java index 3805c8df525e..d125190b77e9 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyTargetExpressionImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyTargetExpressionImpl.java @@ -16,6 +16,7 @@ package com.jetbrains.python.psi.impl; import com.intellij.lang.ASTNode; +import com.intellij.navigation.ItemPresentation; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.Ref; @@ -61,7 +62,7 @@ import java.util.List; /** * @author yole */ -public class PyTargetExpressionImpl extends PyPresentableElementImpl<PyTargetExpressionStub> implements PyTargetExpression { +public class PyTargetExpressionImpl extends PyBaseElementImpl<PyTargetExpressionStub> implements PyTargetExpression { QualifiedName myQualifiedName; public PyTargetExpressionImpl(ASTNode astNode) { @@ -659,12 +660,19 @@ public class PyTargetExpressionImpl extends PyPresentableElementImpl<PyTargetExp return null; } - protected String getElementLocation() { - final PyClass containingClass = getContainingClass(); - if (containingClass != null) { - return "(" + containingClass.getName() + " in " + getPackageForFile(getContainingFile()) + ")"; - } - return super.getElementLocation(); + @Override + public ItemPresentation getPresentation() { + return new PyElementPresentation(this) { + @Nullable + @Override + public String getLocationString() { + final PyClass containingClass = getContainingClass(); + if (containingClass != null) { + return "(" + containingClass.getName() + " in " + getPackageForFile(getContainingFile()) + ")"; + } + return super.getLocationString(); + } + }; } @Nullable diff --git a/python/src/com/jetbrains/python/psi/impl/PyTupleParameterImpl.java b/python/src/com/jetbrains/python/psi/impl/PyTupleParameterImpl.java index 09e097cb0e99..9935841c33bb 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyTupleParameterImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyTupleParameterImpl.java @@ -16,19 +16,17 @@ package com.jetbrains.python.psi.impl; import com.intellij.lang.ASTNode; -import com.intellij.psi.PsiElement; -import com.intellij.util.IncorrectOperationException; +import com.intellij.navigation.ItemPresentation; import com.jetbrains.python.PyElementTypes; import com.jetbrains.python.PythonDialectsTokenSetProvider; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.stubs.PyTupleParameterStub; -import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; /** * Represents a tuple parameter as stubbed element. */ -public class PyTupleParameterImpl extends PyPresentableElementImpl<PyTupleParameterStub> implements PyTupleParameter { +public class PyTupleParameterImpl extends PyBaseElementImpl<PyTupleParameterStub> implements PyTupleParameter { public PyTupleParameterImpl(ASTNode astNode) { super(astNode); @@ -62,10 +60,6 @@ public class PyTupleParameterImpl extends PyPresentableElementImpl<PyTupleParame return getDefaultValue() != null; } - public PsiElement setName(@NonNls @NotNull String name) throws IncorrectOperationException { - throw new IncorrectOperationException("Can't rename a tuple parameter to '" + name +"'"); - } - @Override protected void acceptPyVisitor(PyElementVisitor pyVisitor) { pyVisitor.visitPyTupleParameter(this); @@ -80,4 +74,9 @@ public class PyTupleParameterImpl extends PyPresentableElementImpl<PyTupleParame public boolean isSelf() { return false; } + + @Override + public ItemPresentation getPresentation() { + return new PyElementPresentation(this); + } } diff --git a/python/src/com/jetbrains/python/psi/impl/PythonLanguageLevelPusher.java b/python/src/com/jetbrains/python/psi/impl/PythonLanguageLevelPusher.java index 7e15cab6ffda..af767684643e 100644 --- a/python/src/com/jetbrains/python/psi/impl/PythonLanguageLevelPusher.java +++ b/python/src/com/jetbrains/python/psi/impl/PythonLanguageLevelPusher.java @@ -19,7 +19,9 @@ import com.intellij.facet.Facet; import com.intellij.facet.FacetManager; import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.fileTypes.FileTypeManager; +import com.intellij.openapi.fileTypes.FileTypeRegistry; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.module.ModuleType; @@ -163,7 +165,8 @@ public class PythonLanguageLevelPusher implements FilePropertyPusher<LanguageLev oStream.close(); for (VirtualFile child : fileOrDir.getChildren()) { - if (!child.isDirectory() && PythonFileType.INSTANCE.equals(child.getFileType())) { + final FileType fileType = FileTypeRegistry.getInstance().getFileTypeByFileName(child.getName()); + if (!child.isDirectory() && PythonFileType.INSTANCE.equals(fileType)) { PushedFilePropertiesUpdater.getInstance(project).filePropertiesChanged(child); } } diff --git a/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureHandler.java b/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureHandler.java index 6895249a641f..e360c9e46685 100644 --- a/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureHandler.java +++ b/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureHandler.java @@ -126,7 +126,7 @@ public class PyChangeSignatureHandler implements ChangeSignatureHandler { private static boolean isNotUnderSourceRoot(@NotNull final Project project, @Nullable final PsiFile psiFile, - @NotNull final Editor editor) { + @Nullable final Editor editor) { if (psiFile == null) return true; final VirtualFile virtualFile = psiFile.getVirtualFile(); if (virtualFile != null) { diff --git a/python/src/com/jetbrains/python/refactoring/move/PyMoveClassOrFunctionDelegate.java b/python/src/com/jetbrains/python/refactoring/move/PyMoveClassOrFunctionDelegate.java index cf1b4d19dd4e..8306a4faec23 100644 --- a/python/src/com/jetbrains/python/refactoring/move/PyMoveClassOrFunctionDelegate.java +++ b/python/src/com/jetbrains/python/refactoring/move/PyMoveClassOrFunctionDelegate.java @@ -39,11 +39,10 @@ import org.jetbrains.annotations.Nullable; * @author vlan */ public class PyMoveClassOrFunctionDelegate extends MoveHandlerDelegate { - @Override public boolean canMove(PsiElement[] elements, @Nullable PsiElement targetContainer) { for (PsiElement element : elements) { - if (element instanceof PyClass || element instanceof PyFunction) continue; + if ((element instanceof PyClass || element instanceof PyFunction) && PyUtil.isTopLevel(element)) continue; return false; } return super.canMove(elements, targetContainer); diff --git a/python/src/com/jetbrains/python/sdk/CreateVirtualEnvDialog.java b/python/src/com/jetbrains/python/sdk/CreateVirtualEnvDialog.java index 39fc2c909cf6..1d0a4bc3ad54 100644 --- a/python/src/com/jetbrains/python/sdk/CreateVirtualEnvDialog.java +++ b/python/src/com/jetbrains/python/sdk/CreateVirtualEnvDialog.java @@ -48,9 +48,9 @@ import com.intellij.ui.components.JBLabel; import com.intellij.util.NullableConsumer; import com.intellij.util.PathUtil; import com.intellij.util.PlatformUtils; +import com.intellij.webcore.packaging.PackagesNotificationPanel; import com.jetbrains.python.packaging.PyExternalProcessException; import com.jetbrains.python.packaging.PyPackageManager; -import com.jetbrains.python.packaging.PyPackageManagerImpl; import com.jetbrains.python.packaging.PyPackageService; import com.jetbrains.python.sdk.flavors.VirtualEnvSdkFlavor; import com.jetbrains.python.ui.IdeaDialog; @@ -416,7 +416,7 @@ public class CreateVirtualEnvDialog extends IdeaDialog { String myPath; public void run(@NotNull final ProgressIndicator indicator) { - final PyPackageManagerImpl packageManager = (PyPackageManagerImpl)PyPackageManager.getInstance(basicSdk); + final PyPackageManager packageManager = PyPackageManager.getInstance(basicSdk); try { indicator.setText("Creating virtual environment for " + basicSdk.getName()); myPath = packageManager.createVirtualEnv(getDestination(), useGlobalSitePackages()); @@ -425,7 +425,7 @@ public class CreateVirtualEnvDialog extends IdeaDialog { ApplicationManager.getApplication().invokeLater(new Runnable() { @Override public void run() { - packageManager.showInstallationError(getOwner(), "Failed to Create Virtual Environment", e.toString()); + PackagesNotificationPanel.showError(getOwner(), "Failed to Create Virtual Environment", e.toString()); } }, ModalityState.any()); } diff --git a/python/src/com/jetbrains/python/sdk/PySdkUtil.java b/python/src/com/jetbrains/python/sdk/PySdkUtil.java index 8f2b62b1b414..97d5ee545cbb 100644 --- a/python/src/com/jetbrains/python/sdk/PySdkUtil.java +++ b/python/src/com/jetbrains/python/sdk/PySdkUtil.java @@ -15,10 +15,13 @@ */ package com.jetbrains.python.sdk; +import com.intellij.execution.ExecutionException; import com.intellij.execution.process.CapturingProcessHandler; import com.intellij.execution.process.ProcessOutput; +import com.intellij.execution.util.ExecUtil; import com.intellij.openapi.application.PathManager; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.module.Module; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.roots.OrderRootType; import com.intellij.openapi.util.SystemInfo; @@ -29,8 +32,10 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.remote.RemoteSdkAdditionalData; -import com.intellij.util.ArrayUtil; +import com.intellij.util.SystemProperties; import com.intellij.util.containers.HashMap; +import com.jetbrains.python.packaging.PyPackageUtil; +import com.jetbrains.python.packaging.PyRequirement; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -38,8 +43,7 @@ import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.IOException; import java.io.OutputStream; -import java.util.ArrayList; -import java.util.Collections; +import java.util.Arrays; import java.util.List; import java.util.Map; @@ -56,6 +60,7 @@ public class PySdkUtil { // Windows EOF marker, Ctrl+Z public static final int SUBSTITUTE = 26; + public static final String PATH_ENV_VARIABLE = "PATH"; private PySdkUtil() { // explicitly none @@ -87,57 +92,29 @@ public class PySdkUtil { return getProcessOutput(homePath, command, null, timeout); } - /** - * Executes a process and returns its stdout and stderr outputs as lists of lines. - * Waits for process for possibly limited duration. - * - * @param homePath process run directory - * @param command command to execute and its arguments - * @param addEnv items are prepended to same-named values of inherited process environment. - * @param timeout how many milliseconds to wait until the process terminates; non-positive means inifinity. - * @return a tuple of (stdout lines, stderr lines, exit_code), lines in them have line terminators stripped, or may be null. - */ @NotNull public static ProcessOutput getProcessOutput(String homePath, @NonNls String[] command, - @Nullable @NonNls String[] addEnv, + @Nullable @NonNls Map<String, String> extraEnv, final int timeout) { - return getProcessOutput(homePath, command, addEnv, timeout, null, true); + return getProcessOutput(homePath, command, extraEnv, timeout, null, true); } - /** - * Executes a process and returns its stdout and stderr outputs as lists of lines. - * Waits for process for possibly limited duration. - * - * @param homePath process run directory - * @param command command to execute and its arguments - * @param addEnv items are prepended to same-named values of inherited process environment. - * @param timeout how many milliseconds to wait until the process terminates; non-positive means infinity. - * @param stdin the data to write to the process standard input stream - * @param needEOFMarker - * @return a tuple of (stdout lines, stderr lines, exit_code), lines in them have line terminators stripped, or may be null. - */ @NotNull public static ProcessOutput getProcessOutput(String homePath, @NonNls String[] command, - @Nullable @NonNls String[] addEnv, + @Nullable @NonNls Map<String, String> extraEnv, final int timeout, @Nullable byte[] stdin, boolean needEOFMarker) { - final ProcessOutput failureOutput = new ProcessOutput(); if (homePath == null || !new File(homePath).exists()) { - return failureOutput; + return new ProcessOutput(); } + final Map<String, String> systemEnv = System.getenv(); + final Map<String, String> env = extraEnv != null ? mergeEnvVariables(systemEnv, extraEnv) : systemEnv; try { - List<String> commands = new ArrayList<String>(); - if (SystemInfo.isWindows && StringUtil.endsWithIgnoreCase(command[0], ".bat")) { - commands.add("cmd"); - commands.add("/c"); - } - Collections.addAll(commands, command); - String[] newEnv = buildAdditionalEnv(addEnv); - Process process = Runtime.getRuntime().exec(ArrayUtil.toStringArray(commands), newEnv, new File(homePath)); - CapturingProcessHandler processHandler = new CapturingProcessHandler(process); + final Process process = ExecUtil.exec(Arrays.asList(command), homePath, env); + final CapturingProcessHandler processHandler = new CapturingProcessHandler(process); if (stdin != null) { final OutputStream processInput = processHandler.getProcessInput(); assert processInput != null; @@ -152,72 +129,61 @@ public class PySdkUtil { } return processHandler.runProcess(timeout); } - catch (final IOException ex) { - LOG.warn(ex); - return new ProcessOutput() { - @Override - public String getStderr() { - String err = super.getStderr(); - if (!StringUtil.isEmpty(err)) { - err += "\n" + ex.getMessage(); - } - else { - err = ex.getMessage(); - } - return err; - } - }; + catch (ExecutionException e) { + return getOutputForException(e); + } + catch (IOException e) { + return getOutputForException(e); } } - private static String[] buildAdditionalEnv(String[] addEnv) { - String[] newEnv = null; - if (addEnv != null) { - Map<String, String> envMap = buildEnvMap(addEnv); - newEnv = new String[envMap.size()]; - int i = 0; - for (Map.Entry<String, String> entry : envMap.entrySet()) { - newEnv[i] = entry.getKey() + "=" + entry.getValue(); - i += 1; + private static ProcessOutput getOutputForException(final Exception e) { + LOG.warn(e); + return new ProcessOutput() { + @Override + public String getStderr() { + String err = super.getStderr(); + if (!StringUtil.isEmpty(err)) { + err += "\n" + e.getMessage(); + } + else { + err = e.getMessage(); + } + return err; } - } - return newEnv; + }; } - public static Map<String, String> buildEnvMap(String[] addEnv) { - Map<String, String> envMap = new HashMap<String, String>(System.getenv()); - // turn additional ent into map - Map<String, String> addMap = new HashMap<String, String>(); - for (String envItem : addEnv) { - int pos = envItem.indexOf('='); - if (pos > 0) { - String key = envItem.substring(0, pos); - String value = envItem.substring(pos + 1, envItem.length()); - addMap.put(key, value); - } - else { - LOG.warn(String.format("Invalid env value: '%s'", envItem)); - } - } - // fuse old and new - for (Map.Entry<String, String> entry : addMap.entrySet()) { - final String key = entry.getKey(); - final String value = entry.getValue(); - final String oldValue = envMap.get(key); - if (oldValue != null) { - envMap.put(key, value + oldValue); + @NotNull + public static Map<String, String> mergeEnvVariables(@NotNull Map<String, String> environment, + @NotNull Map<String, String> extraEnvironment) { + final Map<String, String> result = new HashMap<String, String>(environment); + for (Map.Entry<String, String> entry : extraEnvironment.entrySet()) { + if (PATH_ENV_VARIABLE.equals(entry.getKey()) && result.containsKey(PATH_ENV_VARIABLE)) { + result.put(PATH_ENV_VARIABLE, result.get(PATH_ENV_VARIABLE) + File.pathSeparator + entry.getValue()); } else { - envMap.put(key, value); + result.put(entry.getKey(), entry.getValue()); } } - return envMap; + return result; } public static boolean isRemote(@Nullable Sdk sdk) { return sdk != null && sdk.getSdkAdditionalData() instanceof RemoteSdkAdditionalData; } + public static String getUserSite() { + if (SystemInfo.isWindows) { + final String appdata = System.getenv("APPDATA"); + return appdata + File.separator + "Python"; + } + else { + final String userHome = SystemProperties.getUserHome(); + return userHome + File.separator + ".local"; + } + } + public static boolean isElementInSkeletons(@NotNull final PsiElement element) { final PsiFile file = element.getContainingFile(); if (file != null) { @@ -266,4 +232,13 @@ public class PySdkUtil { } return null; } + + @Nullable + public static List<PyRequirement> getRequirementsFromTxt(Module module) { + final VirtualFile requirementsTxt = PyPackageUtil.findRequirementsTxt(module); + if (requirementsTxt != null) { + return PyRequirement.parse(requirementsTxt); + } + return null; + } } diff --git a/python/src/com/jetbrains/python/sdk/PythonSdkDetailsStep.java b/python/src/com/jetbrains/python/sdk/PythonSdkDetailsStep.java index 923629ce4473..cc6a2c6b6121 100644 --- a/python/src/com/jetbrains/python/sdk/PythonSdkDetailsStep.java +++ b/python/src/com/jetbrains/python/sdk/PythonSdkDetailsStep.java @@ -46,7 +46,7 @@ import java.util.List; import java.util.Set; public class PythonSdkDetailsStep extends BaseListPopupStep<String> { - private DialogWrapper myMore; + @Nullable private DialogWrapper myMore; private final Project myProject; private final Component myOwnerComponent; private final Sdk[] myExistingSdks; @@ -120,6 +120,8 @@ public class PythonSdkDetailsStep extends BaseListPopupStep<String> { } private void optionSelected(final String selectedValue) { + if (!MORE.equals(selectedValue) && myMore != null) + Disposer.dispose(myMore.getDisposable()); if (LOCAL.equals(selectedValue)) { createLocalSdk(); } @@ -129,7 +131,7 @@ public class PythonSdkDetailsStep extends BaseListPopupStep<String> { else if (VIRTUALENV.equals(selectedValue)) { createVirtualEnvSdk(); } - else { + else if (myMore != null) { myMore.show(); } } diff --git a/python/src/com/jetbrains/python/sdk/PythonSdkType.java b/python/src/com/jetbrains/python/sdk/PythonSdkType.java index 58de09370d37..460c875491ce 100644 --- a/python/src/com/jetbrains/python/sdk/PythonSdkType.java +++ b/python/src/com/jetbrains/python/sdk/PythonSdkType.java @@ -15,6 +15,7 @@ */ package com.jetbrains.python.sdk; +import com.google.common.collect.ImmutableMap; import com.intellij.execution.ExecutionException; import com.intellij.execution.configurations.GeneralCommandLine; import com.intellij.execution.process.ProcessOutput; @@ -771,16 +772,12 @@ public class PythonSdkType extends SdkType { } @NotNull - public static List<String> getSysPathsFromScript(String bin_path) throws InvalidSdkException { + public static List<String> getSysPathsFromScript(@NotNull String binaryPath) throws InvalidSdkException { String scriptFile = PythonHelpersLocator.getHelperPath("syspath.py"); // to handle the situation when PYTHONPATH contains ., we need to run the syspath script in the // directory of the script itself - otherwise the dir in which we run the script (e.g. /usr/bin) will be added to SDK path - String[] add_environment = getVirtualEnvAdditionalEnv(bin_path); - final ProcessOutput run_result = PySdkUtil.getProcessOutput( - new File(scriptFile).getParent(), - new String[]{bin_path, scriptFile}, - add_environment, MINUTE - ); + final ProcessOutput run_result = PySdkUtil.getProcessOutput(new File(scriptFile).getParent(), new String[]{binaryPath, scriptFile}, + getVirtualEnvExtraEnv(binaryPath), MINUTE); if (!run_result.checkSuccess(LOG)) { throw new InvalidSdkException(String.format("Failed to determine Python's sys.path value:\nSTDOUT: %s\nSTDERR: %s", run_result.getStdout(), @@ -789,15 +786,16 @@ public class PythonSdkType extends SdkType { return run_result.getStdoutLines(); } - // Returns a piece of env good as additional env for getProcessOutput. + /** + * Returns a piece of env good as additional env for getProcessOutput. + */ @Nullable - public static String[] getVirtualEnvAdditionalEnv(String bin_path) { - File virtualenv_root = getVirtualEnvRoot(bin_path); - String[] add_environment = null; - if (virtualenv_root != null) { - add_environment = new String[]{"PATH=" + virtualenv_root + File.pathSeparator}; + public static Map<String, String> getVirtualEnvExtraEnv(@NotNull String binaryPath) { + final File root = getVirtualEnvRoot(binaryPath); + if (root != null) { + return ImmutableMap.of("PATH", root.toString()); } - return add_environment; + return null; } @Nullable diff --git a/python/src/com/jetbrains/python/sdk/PythonSdkUpdater.java b/python/src/com/jetbrains/python/sdk/PythonSdkUpdater.java index b4b968fdb108..fb77cdb26f8f 100644 --- a/python/src/com/jetbrains/python/sdk/PythonSdkUpdater.java +++ b/python/src/com/jetbrains/python/sdk/PythonSdkUpdater.java @@ -32,7 +32,6 @@ import com.intellij.openapi.projectRoots.SdkModificator; import com.intellij.openapi.projectRoots.SdkTypeId; import com.intellij.openapi.roots.OrderRootType; import com.intellij.openapi.startup.StartupActivity; -import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.io.FileUtilRt; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; @@ -149,7 +148,7 @@ public class PythonSdkUpdater implements StartupActivity { } public static void updateSdk(@Nullable Project project, @Nullable Component ownerComponent, @NotNull final Sdk sdk, String skeletonsPath) throws InvalidSdkException { - PySkeletonRefresher.refreshSkeletonsOfSdk(project, ownerComponent, skeletonsPath, new Ref<Boolean>(false), sdk); // NOTE: whole thing would need a rename + PySkeletonRefresher.refreshSkeletonsOfSdk(project, ownerComponent, skeletonsPath, sdk); // NOTE: whole thing would need a rename if (!PySdkUtil.isRemote(sdk)) { updateSysPath(sdk); } diff --git a/python/src/com/jetbrains/python/sdk/flavors/WinPythonSdkFlavor.java b/python/src/com/jetbrains/python/sdk/flavors/WinPythonSdkFlavor.java index 9d22af8c03bf..3a9ac951509d 100644 --- a/python/src/com/jetbrains/python/sdk/flavors/WinPythonSdkFlavor.java +++ b/python/src/com/jetbrains/python/sdk/flavors/WinPythonSdkFlavor.java @@ -37,6 +37,8 @@ public class WinPythonSdkFlavor extends CPythonSdkFlavor { "HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\Python\\PythonCore", "python.exe", "HKEY_LOCAL_MACHINE\\SOFTWARE\\IronPython", "ipy.exe"); + private static Set<String> ourRegistryCache; + private WinPythonSdkFlavor() { } @@ -78,18 +80,25 @@ public class WinPythonSdkFlavor extends CPythonSdkFlavor { } public static void findInRegistry(Collection<String> candidates) { - for (Map.Entry<String, String> entry : ourRegistryMap.entrySet()) { - final String prefix = entry.getKey(); - final String exePath = entry.getValue(); - List<String> strings = WindowsRegistryUtil.readRegistryBranch(prefix); - for (String string : strings) { - final String path = - WindowsRegistryUtil.readRegistryDefault(prefix + "\\" + string + - "\\InstallPath"); - if (path != null) { - File f = new File(path, exePath); - if (f.exists()) { - candidates.add(FileUtil.toSystemDependentName(f.getPath())); + fillRegistryCache(); + candidates.addAll(ourRegistryCache); + } + + private static void fillRegistryCache() { + if (ourRegistryCache == null) { + ourRegistryCache = new HashSet<String>(); + for (Map.Entry<String, String> entry : ourRegistryMap.entrySet()) { + final String prefix = entry.getKey(); + final String exePath = entry.getValue(); + List<String> strings = WindowsRegistryUtil.readRegistryBranch(prefix); + for (String string : strings) { + final String path = WindowsRegistryUtil.readRegistryDefault(prefix + "\\" + string + + "\\InstallPath"); + if (path != null) { + File f = new File(path, exePath); + if (f.exists()) { + ourRegistryCache.add(FileUtil.toSystemDependentName(f.getPath())); + } } } } diff --git a/python/src/com/jetbrains/python/sdk/skeletons/PySkeletonGenerator.java b/python/src/com/jetbrains/python/sdk/skeletons/PySkeletonGenerator.java index 901472e6d2c3..7e099041e16a 100644 --- a/python/src/com/jetbrains/python/sdk/skeletons/PySkeletonGenerator.java +++ b/python/src/com/jetbrains/python/sdk/skeletons/PySkeletonGenerator.java @@ -15,6 +15,7 @@ */ package com.jetbrains.python.sdk.skeletons; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; import com.intellij.execution.process.ProcessOutput; import com.intellij.openapi.application.ex.ApplicationManagerEx; @@ -52,21 +53,12 @@ public class PySkeletonGenerator { ENV_PATH_PARAM.put(IronPythonSdkFlavor.class, "IRONPYTHONPATH"); // TODO: Make strategy and move to PythonSdkFlavor? } - protected static final Logger LOG = Logger.getInstance("#" + PySkeletonGenerator.class.getName()); - - protected static final int MINUTE = 60 * 1000; - protected static final String GENERATOR3 = "generator3.py"; - private static final String[] EMPTY_ENVS = new String[0]; private final String mySkeletonsPath; - /** - * Env variables to be added to skeleton generator - */ - @NotNull - private final String[] myEnvs; + @NotNull private final Map<String, String> myEnv; public void finishSkeletonsGeneration() { } @@ -85,7 +77,6 @@ public class PySkeletonGenerator { } } - /** * @param skeletonPath path where skeletons should be generated * @param pySdk SDK @@ -94,11 +85,11 @@ public class PySkeletonGenerator { public PySkeletonGenerator(String skeletonPath, @NotNull final Sdk pySdk, @Nullable final String currentFolder) { mySkeletonsPath = skeletonPath; final PythonSdkFlavor flavor = PythonSdkFlavor.getFlavor(pySdk); - if ((currentFolder != null) && (flavor != null) && ENV_PATH_PARAM.containsKey(flavor.getClass())) { - myEnvs = new String[]{String.format("%s=%s", ENV_PATH_PARAM.get(flavor.getClass()), currentFolder)}; + if (currentFolder != null && flavor != null && ENV_PATH_PARAM.containsKey(flavor.getClass())) { + myEnv = ImmutableMap.of(ENV_PATH_PARAM.get(flavor.getClass()), currentFolder); } else { - myEnvs = EMPTY_ENVS; + myEnv = Collections.emptyMap(); } } @@ -171,23 +162,19 @@ public class PySkeletonGenerator { if (modfilename != null) { commandLine.add(modfilename); } - final List<String> envs = new ArrayList<String>(Arrays.asList(myEnvs)); - final String[] virtualEnvAdditionalEnv = PythonSdkType.getVirtualEnvAdditionalEnv(binaryPath); - if (virtualEnvAdditionalEnv != null) { - envs.addAll(Arrays.asList(virtualEnvAdditionalEnv)); - } + final Map<String, String> extraEnv = PythonSdkType.getVirtualEnvExtraEnv(binaryPath); + final Map<String, String> env = extraEnv != null ? PySdkUtil.mergeEnvVariables(myEnv, extraEnv) : myEnv; - return getProcessOutput(parent_dir, ArrayUtil.toStringArray(commandLine), envs.toArray(new String[envs.size()]), - MINUTE * 10 - ); + return getProcessOutput(parent_dir, ArrayUtil.toStringArray(commandLine), env, MINUTE * 10); } - protected ProcessOutput getProcessOutput(String homePath, String[] commandLine, String[] env, int timeout) throws InvalidSdkException { + protected ProcessOutput getProcessOutput(String homePath, String[] commandLine, Map<String, String> extraEnv, + int timeout) throws InvalidSdkException { return PySdkUtil.getProcessOutput( homePath, commandLine, - env, + extraEnv, timeout ); } @@ -207,7 +194,7 @@ public class PySkeletonGenerator { "-d", mySkeletonsPath, // output dir "-b", // for builtins }, - PythonSdkType.getVirtualEnvAdditionalEnv(binaryPath), MINUTE * 5 + PythonSdkType.getVirtualEnvExtraEnv(binaryPath), MINUTE * 5 ); runResult.checkSuccess(LOG); LOG.info("Rebuilding builtin skeletons took " + (System.currentTimeMillis() - startTime) + " ms"); @@ -228,7 +215,7 @@ public class PySkeletonGenerator { final ProcessOutput process = getProcessOutput(parentDir, ArrayUtil.toStringArray(cmd), - PythonSdkType.getVirtualEnvAdditionalEnv(homePath), + PythonSdkType.getVirtualEnvExtraEnv(homePath), MINUTE * 4); // see PY-3898 LOG.info("Retrieving binary module list took " + (System.currentTimeMillis() - startTime) + " ms"); diff --git a/python/src/com/jetbrains/python/sdk/skeletons/PySkeletonRefresher.java b/python/src/com/jetbrains/python/sdk/skeletons/PySkeletonRefresher.java index 4e4a43102202..b1e8ca19acf8 100644 --- a/python/src/com/jetbrains/python/sdk/skeletons/PySkeletonRefresher.java +++ b/python/src/com/jetbrains/python/sdk/skeletons/PySkeletonRefresher.java @@ -19,9 +19,6 @@ import com.google.common.base.Joiner; import com.google.common.collect.Lists; import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; import com.intellij.execution.ExecutionException; -import com.intellij.notification.Notification; -import com.intellij.notification.NotificationType; -import com.intellij.notification.Notifications; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.PathManager; import com.intellij.openapi.diagnostic.Logger; @@ -31,7 +28,6 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.roots.OrderRootType; import com.intellij.openapi.util.Pair; -import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; @@ -48,7 +44,6 @@ import com.jetbrains.python.PyNames; import com.jetbrains.python.codeInsight.userSkeletons.PyUserSkeletonsUtil; import com.jetbrains.python.packaging.PyExternalProcessException; import com.jetbrains.python.packaging.PyPackageManager; -import com.jetbrains.python.packaging.PyPackageManagerImpl; import com.jetbrains.python.psi.resolve.PythonSdkPathCache; import com.jetbrains.python.remote.PythonRemoteInterpreterManager; import com.jetbrains.python.sdk.InvalidSdkException; @@ -105,10 +100,6 @@ public class PySkeletonRefresher { private PySkeletonGenerator mySkeletonsGenerator; - public static void refreshSkeletonsOfSdk(@NotNull Project project, @NotNull Sdk sdk) throws InvalidSdkException { - refreshSkeletonsOfSdk(project, null, PythonSdkType.findSkeletonsPath(sdk), new Ref<Boolean>(false), sdk); - } - public static synchronized boolean isGeneratingSkeletons() { return ourGeneratingCount > 0; } @@ -120,7 +111,6 @@ public class PySkeletonRefresher { public static void refreshSkeletonsOfSdk(@Nullable Project project, Component ownerComponent, String skeletonsPath, - @Nullable Ref<Boolean> migrationFlag, @NotNull Sdk sdk) throws InvalidSdkException { final Map<String, List<String>> errors = new TreeMap<String, List<String>>(); @@ -137,7 +127,7 @@ public class PySkeletonRefresher { changeGeneratingSkeletons(1); try { - List<String> sdkErrors = refresher.regenerateSkeletons(checker, migrationFlag); + List<String> sdkErrors = refresher.regenerateSkeletons(checker); if (sdkErrors.size() > 0) { String sdkName = sdk.getName(); List<String> knownErrors = errors.get(sdkName); @@ -286,8 +276,7 @@ public class PySkeletonRefresher { return mySkeletonsPath; } - public List<String> regenerateSkeletons(@Nullable SkeletonVersionChecker cachedChecker, - @Nullable Ref<Boolean> migrationFlag) throws InvalidSdkException { + public List<String> regenerateSkeletons(@Nullable SkeletonVersionChecker cachedChecker) throws InvalidSdkException { final List<String> errorList = new SmartList<String>(); final String homePath = mySdk.getHomePath(); final String skeletonsPath = getSkeletonsPath(); @@ -299,14 +288,13 @@ public class PySkeletonRefresher { final String readablePath = FileUtil.getLocationRelativeToUserHome(homePath); mySkeletonsGenerator.prepare(); - myBlacklist = loadBlacklist(); indicate(PyBundle.message("sdk.gen.querying.$0", readablePath)); // get generator version and binary libs list in one go - final PySkeletonGenerator.ListBinariesResult binaries = - mySkeletonsGenerator.listBinaries(mySdk, calculateExtraSysPath(mySdk, getSkeletonsPath())); + final String extraSysPath = calculateExtraSysPath(mySdk, getSkeletonsPath()); + final PySkeletonGenerator.ListBinariesResult binaries = mySkeletonsGenerator.listBinaries(mySdk, extraSysPath); myGeneratorVersion = binaries.generatorVersion; myPregeneratedSkeletons = findPregeneratedSkeletons(); @@ -325,77 +313,19 @@ public class PySkeletonRefresher { final SkeletonHeader oldHeader = readSkeletonHeader(builtinsFile); final boolean oldOrNonExisting = oldHeader == null || oldHeader.getVersion() == 0; - if (migrationFlag != null && !migrationFlag.get() && oldOrNonExisting) { - migrationFlag.set(true); - Notifications.Bus.notify( - new Notification( - PythonSdkType.SKELETONS_TOPIC, PyBundle.message("sdk.gen.notify.converting.old.skels"), - PyBundle.message("sdk.gen.notify.converting.text"), - NotificationType.INFORMATION - ) - ); - } - if (myPregeneratedSkeletons != null && oldOrNonExisting) { - indicate("Unpacking pregenerated skeletons..."); - try { - final VirtualFile jar = JarFileSystem.getInstance().getVirtualFileForJar(myPregeneratedSkeletons); - if (jar != null) { - ZipUtil.extract(new File(jar.getPath()), - new File(getSkeletonsPath()), null); - } - } - catch (IOException e) { - LOG.info("Error unpacking pregenerated skeletons", e); - } + unpackPreGeneratedSkeletons(); } if (oldOrNonExisting) { - final Sdk base = PythonSdkType.getInstance().getVirtualEnvBaseSdk(mySdk); - if (base != null) { - indicate("Copying base SDK skeletons for virtualenv..."); - final String baseSkeletonsPath = PythonSdkType.getSkeletonsPath(PathManager.getSystemPath(), base.getHomePath()); - final PySkeletonGenerator.ListBinariesResult baseBinaries = - mySkeletonsGenerator.listBinaries(base, calculateExtraSysPath(base, baseSkeletonsPath)); - for (Map.Entry<String, PyBinaryItem> entry : binaries.modules.entrySet()) { - final String module = entry.getKey(); - final PyBinaryItem binary = entry.getValue(); - final PyBinaryItem baseBinary = baseBinaries.modules.get(module); - final File fromFile = getSkeleton(module, baseSkeletonsPath); - if (baseBinaries.modules.containsKey(module) && - fromFile.exists() && - binary.length() == baseBinary.length()) { // Weak binary modules equality check - final File toFile = fromFile.isDirectory() ? - getPackageSkeleton(module, skeletonsPath) : - getModuleSkeleton(module, skeletonsPath); - try { - FileUtil.copy(fromFile, toFile); - } - catch (IOException e) { - LOG.info("Error copying base virtualenv SDK skeleton for " + module, e); - } - } - } - } + copyBaseSdkSkeletonsToVirtualEnv(skeletonsPath, binaries); } - final SkeletonHeader newHeader = readSkeletonHeader(builtinsFile); - final boolean mustUpdateBuiltins = myPregeneratedSkeletons == null && - (newHeader == null || newHeader.getVersion() < myVersionChecker.getBuiltinVersion()); - if (mustUpdateBuiltins) { - indicate(PyBundle.message("sdk.gen.updating.builtins.$0", readablePath)); - mySkeletonsGenerator.generateBuiltinSkeletons(mySdk); - if (myProject != null) { - PythonSdkPathCache.getInstance(myProject, mySdk).clearBuiltins(); - } - } + final boolean builtinsUpdated = updateSkeletonsForBuiltins(readablePath, builtinsFile); if (!binaries.modules.isEmpty()) { - indicate(PyBundle.message("sdk.gen.updating.$0", readablePath)); - - List<UpdateResult> updateErrors = updateOrCreateSkeletons(binaries.modules); //Skeletons regeneration - + final List<UpdateResult> updateErrors = updateOrCreateSkeletons(binaries.modules); if (updateErrors.size() > 0) { indicateMinor(BLACKLIST_FILE_NAME); for (UpdateResult error : updateErrors) { @@ -410,7 +340,6 @@ public class PySkeletonRefresher { } indicate(PyBundle.message("sdk.gen.reloading")); - mySkeletonsGenerator.refreshGeneratedSkeletons(); if (!oldOrNonExisting) { @@ -419,14 +348,15 @@ public class PySkeletonRefresher { } if (PySdkUtil.isRemote(mySdk)) { try { - ((PyPackageManagerImpl)PyPackageManager.getInstance(mySdk)).loadPackages(); + // Force loading packages + PyPackageManager.getInstance(mySdk).getPackages(false); } catch (PyExternalProcessException e) { // ignore - already logged } } - if ((mustUpdateBuiltins || PySdkUtil.isRemote(mySdk)) && myProject != null) { + if ((builtinsUpdated || PySdkUtil.isRemote(mySdk)) && myProject != null) { ApplicationManager.getApplication().invokeLater(new Runnable() { @Override public void run() { @@ -438,6 +368,64 @@ public class PySkeletonRefresher { return errorList; } + private boolean updateSkeletonsForBuiltins(String readablePath, File builtinsFile) throws InvalidSdkException { + final SkeletonHeader newHeader = readSkeletonHeader(builtinsFile); + final boolean mustUpdateBuiltins = myPregeneratedSkeletons == null && + (newHeader == null || newHeader.getVersion() < myVersionChecker.getBuiltinVersion()); + if (mustUpdateBuiltins) { + indicate(PyBundle.message("sdk.gen.updating.builtins.$0", readablePath)); + mySkeletonsGenerator.generateBuiltinSkeletons(mySdk); + if (myProject != null) { + PythonSdkPathCache.getInstance(myProject, mySdk).clearBuiltins(); + } + } + return mustUpdateBuiltins; + } + + private void copyBaseSdkSkeletonsToVirtualEnv(String skeletonsPath, PySkeletonGenerator.ListBinariesResult binaries) + throws InvalidSdkException { + final Sdk base = PythonSdkType.getInstance().getVirtualEnvBaseSdk(mySdk); + if (base != null) { + indicate("Copying base SDK skeletons for virtualenv..."); + final String baseSkeletonsPath = PythonSdkType.getSkeletonsPath(PathManager.getSystemPath(), base.getHomePath()); + final PySkeletonGenerator.ListBinariesResult baseBinaries = + mySkeletonsGenerator.listBinaries(base, calculateExtraSysPath(base, baseSkeletonsPath)); + for (Map.Entry<String, PyBinaryItem> entry : binaries.modules.entrySet()) { + final String module = entry.getKey(); + final PyBinaryItem binary = entry.getValue(); + final PyBinaryItem baseBinary = baseBinaries.modules.get(module); + final File fromFile = getSkeleton(module, baseSkeletonsPath); + if (baseBinaries.modules.containsKey(module) && + fromFile.exists() && + binary.length() == baseBinary.length()) { // Weak binary modules equality check + final File toFile = fromFile.isDirectory() ? + getPackageSkeleton(module, skeletonsPath) : + getModuleSkeleton(module, skeletonsPath); + try { + FileUtil.copy(fromFile, toFile); + } + catch (IOException e) { + LOG.info("Error copying base virtualenv SDK skeleton for " + module, e); + } + } + } + } + } + + private void unpackPreGeneratedSkeletons() throws InvalidSdkException { + indicate("Unpacking pregenerated skeletons..."); + try { + final VirtualFile jar = JarFileSystem.getInstance().getVirtualFileForJar(myPregeneratedSkeletons); + if (jar != null) { + ZipUtil.extract(new File(jar.getPath()), + new File(getSkeletonsPath()), null); + } + } + catch (IOException e) { + LOG.info("Error unpacking pregenerated skeletons", e); + } + } + @Nullable public static SkeletonHeader readSkeletonHeader(@NotNull File file) { try { @@ -835,7 +823,7 @@ public class PySkeletonRefresher { return null; } LOG.info("Pregenerated skeletons root is " + root); - final String versionString = mySdk.getVersionString(); + @NonNls final String versionString = mySdk.getVersionString(); if (versionString == null) { return null; } diff --git a/python/src/com/jetbrains/python/statistics/PyPackageUsagesCollector.java b/python/src/com/jetbrains/python/statistics/PyPackageUsagesCollector.java index f8b4adde5b33..8e029e042aa0 100644 --- a/python/src/com/jetbrains/python/statistics/PyPackageUsagesCollector.java +++ b/python/src/com/jetbrains/python/statistics/PyPackageUsagesCollector.java @@ -25,7 +25,7 @@ import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.projectRoots.Sdk; import com.jetbrains.python.packaging.PyPIPackageUtil; -import com.jetbrains.python.packaging.PyPackageManagerImpl; +import com.jetbrains.python.packaging.PyPackageManager; import com.jetbrains.python.packaging.PyRequirement; import com.jetbrains.python.sdk.PythonSdkType; import org.jetbrains.annotations.NotNull; @@ -46,12 +46,12 @@ public class PyPackageUsagesCollector extends AbstractApplicationUsagesCollector public Set<UsageDescriptor> getProjectUsages(@NotNull Project project) throws CollectUsagesException { final Set<UsageDescriptor> result = new HashSet<UsageDescriptor>(); for(final Module m: ModuleManager.getInstance(project).getModules()) { - Sdk pythonSdk = PythonSdkType.findPythonSdk(m); + final Sdk pythonSdk = PythonSdkType.findPythonSdk(m); if (pythonSdk != null) { ApplicationManager.getApplication().runReadAction(new Runnable() { @Override public void run() { - List<PyRequirement> requirements = PyPackageManagerImpl.getRequirements(m); + List<PyRequirement> requirements = PyPackageManager.getInstance(pythonSdk).getRequirements(m); if (requirements != null) { Collection<String> packages = new HashSet<String>(PyPIPackageUtil.INSTANCE.getPackageNames()); for (PyRequirement requirement : requirements) { diff --git a/python/src/com/jetbrains/python/structureView/PyStructureViewElement.java b/python/src/com/jetbrains/python/structureView/PyStructureViewElement.java index 93c44f7f2a17..de9f7bf5a5ca 100644 --- a/python/src/com/jetbrains/python/structureView/PyStructureViewElement.java +++ b/python/src/com/jetbrains/python/structureView/PyStructureViewElement.java @@ -23,7 +23,6 @@ import com.intellij.openapi.editor.colors.TextAttributesKey; import com.intellij.psi.PsiElement; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.ui.LayeredIcon; -import com.intellij.util.Function; import com.jetbrains.python.PyNames; import com.jetbrains.python.psi.*; import icons.PythonIcons; @@ -33,9 +32,6 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.util.*; -import static com.intellij.openapi.util.text.StringUtil.join; -import static com.intellij.openapi.util.text.StringUtil.notNullize; - /** * Handles nodes in Structure View. * @author yole @@ -237,32 +233,15 @@ public class PyStructureViewElement implements StructureViewTreeElement { @NotNull @Override public ItemPresentation getPresentation() { + final ItemPresentation presentation = myElement.getPresentation(); return new ColoredItemPresentation() { + @Nullable + @Override public String getPresentableText() { - final String unnamed = "<unnamed>"; - if (myElement instanceof PyFunction) { - PyParameterList argList = ((PyFunction) myElement).getParameterList(); - StringBuilder result = new StringBuilder(notNullize(myElement.getName(), unnamed)); - result.append(argList.getPresentableText(true)); - return result.toString(); - } - else if (myElement instanceof PyClass && myElement.isValid()) { - PyClass c = (PyClass) myElement; - StringBuilder result = new StringBuilder(notNullize(c.getName(), unnamed)); - PyExpression[] superClassExpressions = c.getSuperClassExpressions(); - if (superClassExpressions.length > 0) { - result.append("("); - result.append(join(Arrays.asList(superClassExpressions), new Function<PyExpression, String>() { - public String fun(PyExpression expr) { - String name = expr.getText(); - return notNullize(name, unnamed); - } - }, ", ")); - result.append(")"); - } - return result.toString(); + if (myElement instanceof PyFile) { + return myElement.getName(); } - return notNullize(myElement.getName(), unnamed); + return presentation != null ? presentation.getPresentableText() : PyNames.UNNAMED_ELEMENT; } @Nullable diff --git a/python/src/com/jetbrains/python/testing/PyRerunFailedTestsAction.java b/python/src/com/jetbrains/python/testing/PyRerunFailedTestsAction.java index 0b922514e52e..01c8ac68540c 100644 --- a/python/src/com/jetbrains/python/testing/PyRerunFailedTestsAction.java +++ b/python/src/com/jetbrains/python/testing/PyRerunFailedTestsAction.java @@ -28,33 +28,29 @@ import com.intellij.execution.testframework.actions.AbstractRerunFailedTestsActi import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.ComponentContainer; +import com.intellij.psi.PsiElement; +import com.intellij.psi.search.GlobalSearchScope; import com.jetbrains.python.run.AbstractPythonRunConfiguration; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; +import java.util.*; -/* - * User: ktisha - */ public class PyRerunFailedTestsAction extends AbstractRerunFailedTestsAction { - protected PyRerunFailedTestsAction(@NotNull ComponentContainer componentContainer) { super(componentContainer); } @Override @Nullable - public MyRunProfile getRunProfile() { + protected MyRunProfile getRunProfile(@NotNull ExecutionEnvironment environment) { final TestFrameworkRunningModel model = getModel(); - if (model == null) return null; - final AbstractPythonRunConfiguration configuration = (AbstractPythonRunConfiguration)model.getProperties().getConfiguration(); - return new MyTestRunProfile(configuration); + if (model == null) { + return null; + } + return new MyTestRunProfile((AbstractPythonRunConfiguration)model.getProperties().getConfiguration()); } - private class MyTestRunProfile extends MyRunProfile { public MyTestRunProfile(RunConfigurationBase configuration) { @@ -71,6 +67,19 @@ public class PyRerunFailedTestsAction extends AbstractRerunFailedTestsAction { @Override public RunProfileState getState(@NotNull Executor executor, @NotNull ExecutionEnvironment env) throws ExecutionException { final AbstractPythonRunConfiguration configuration = ((AbstractPythonRunConfiguration)getPeer()); + + // If configuration wants to take care about rerun itself + if (configuration instanceof TestRunConfigurationReRunResponsible) { + // TODO: Extract method + final Set<PsiElement> failedTestElements = new HashSet<PsiElement>(); + for (final AbstractTestProxy proxy : getFailedTests(getProject())) { + final Location<?> location = proxy.getLocation(getProject(), GlobalSearchScope.allScope(getProject())); + if (location != null) { + failedTestElements.add(location.getPsiElement()); + } + } + return ((TestRunConfigurationReRunResponsible)configuration).rerunTests(executor, env, failedTestElements); + } return new FailedPythonTestCommandLineStateBase(configuration, env, (PythonTestCommandLineStateBase)configuration.getState(executor, env)); } diff --git a/python/src/com/jetbrains/python/testing/PythonTestCommandLineStateBase.java b/python/src/com/jetbrains/python/testing/PythonTestCommandLineStateBase.java index 1a5aafe59051..c73a488dc7f5 100644 --- a/python/src/com/jetbrains/python/testing/PythonTestCommandLineStateBase.java +++ b/python/src/com/jetbrains/python/testing/PythonTestCommandLineStateBase.java @@ -63,6 +63,7 @@ public abstract class PythonTestCommandLineStateBase extends PythonCommandLineSt myConfiguration = configuration; } + @Override @NotNull protected ConsoleView createAndAttachConsole(Project project, ProcessHandler processHandler, Executor executor) throws ExecutionException { @@ -89,6 +90,7 @@ public abstract class PythonTestCommandLineStateBase extends PythonCommandLineSt return new PythonTRunnerConsoleProperties(myConfiguration, executor, false); } + @Override public GeneralCommandLine generateCommandLine() throws ExecutionException { GeneralCommandLine cmd = super.generateCommandLine(); @@ -135,7 +137,7 @@ public abstract class PythonTestCommandLineStateBase extends PythonCommandLineSt PyRerunFailedTestsAction rerunFailedTestsAction = new PyRerunFailedTestsAction(console); if (console instanceof SMTRunnerConsoleView) { - rerunFailedTestsAction.init(((BaseTestsOutputConsoleView)console).getProperties(), getEnvironment()); + rerunFailedTestsAction.init(((BaseTestsOutputConsoleView)console).getProperties()); rerunFailedTestsAction.setModelProvider(new Getter<TestFrameworkRunningModel>() { @Override public TestFrameworkRunningModel get() { diff --git a/python/src/com/jetbrains/python/testing/TestRunConfigurationReRunResponsible.java b/python/src/com/jetbrains/python/testing/TestRunConfigurationReRunResponsible.java new file mode 100644 index 000000000000..a8e9e1f8d5e4 --- /dev/null +++ b/python/src/com/jetbrains/python/testing/TestRunConfigurationReRunResponsible.java @@ -0,0 +1,30 @@ +package com.jetbrains.python.testing; + +import com.intellij.execution.ExecutionException; +import com.intellij.execution.Executor; +import com.intellij.execution.configurations.RunProfileState; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.psi.PsiElement; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; + +/** + * Configuration that handles rerun failed tests itself. + * + * @author Ilya.Kazakevich + */ +public interface TestRunConfigurationReRunResponsible { + /** + * Rerun failed tests + * @param executor test executor + * @param environment test environment + * @param failedTests a pack of psi elements, indicating failed tests (to retrn) + * @return state to run or null if no rerun actions found (i.e. no errors in failedTest, empty etc) + * @throws ExecutionException failed to run + */ + @Nullable + RunProfileState rerunTests(@NotNull final Executor executor, @NotNull final ExecutionEnvironment environment, + @NotNull Collection<PsiElement> failedTests) throws ExecutionException; +} diff --git a/python/src/com/jetbrains/python/testing/VFSTestFrameworkListener.java b/python/src/com/jetbrains/python/testing/VFSTestFrameworkListener.java index 9d763e2ce286..64adf2093028 100644 --- a/python/src/com/jetbrains/python/testing/VFSTestFrameworkListener.java +++ b/python/src/com/jetbrains/python/testing/VFSTestFrameworkListener.java @@ -35,7 +35,6 @@ import com.intellij.util.ui.update.Update; import com.jetbrains.python.PyNames; import com.jetbrains.python.packaging.PyExternalProcessException; import com.jetbrains.python.packaging.PyPackageManager; -import com.jetbrains.python.packaging.PyPackageManagerImpl; import com.jetbrains.python.sdk.PySdkUtil; import com.jetbrains.python.sdk.PythonSdkType; import org.jetbrains.annotations.NotNull; @@ -131,9 +130,9 @@ public class VFSTestFrameworkListener implements ApplicationComponent { LOG.info("Searching test runner in empty sdk"); return null; } - final PyPackageManagerImpl packageManager = (PyPackageManagerImpl)PyPackageManager.getInstance(sdk); + final PyPackageManager packageManager = PyPackageManager.getInstance(sdk); try { - return packageManager.findInstalledPackage(testPackageName) != null; + return packageManager.findPackage(testPackageName, false) != null; } catch (PyExternalProcessException e) { LOG.info("Can't load package list " + e.getMessage()); diff --git a/python/src/com/jetbrains/python/testing/pytest/PyTestConfigurationProducer.java b/python/src/com/jetbrains/python/testing/pytest/PyTestConfigurationProducer.java index f988dbd315e6..8dc27d4fd995 100644 --- a/python/src/com/jetbrains/python/testing/pytest/PyTestConfigurationProducer.java +++ b/python/src/com/jetbrains/python/testing/pytest/PyTestConfigurationProducer.java @@ -31,7 +31,6 @@ import com.intellij.webcore.packaging.PackageVersionComparator; import com.jetbrains.python.packaging.PyExternalProcessException; import com.jetbrains.python.packaging.PyPackage; import com.jetbrains.python.packaging.PyPackageManager; -import com.jetbrains.python.packaging.PyPackageManagerImpl; import com.jetbrains.python.psi.PyClass; import com.jetbrains.python.psi.PyFile; import com.jetbrains.python.psi.PyFunction; @@ -97,9 +96,9 @@ public class PyTestConfigurationProducer extends PythonTestConfigurationProducer if (pyFunction != null) { keywords = pyFunction.getName(); if (pyClass != null) { - final PyPackageManagerImpl packageManager = (PyPackageManagerImpl)PyPackageManager.getInstance(sdk); + final PyPackageManager packageManager = PyPackageManager.getInstance(sdk); try { - final PyPackage pytestPackage = packageManager.findInstalledPackage("pytest"); + final PyPackage pytestPackage = packageManager.findPackage("pytest", false); if (pytestPackage != null && PackageVersionComparator.VERSION_COMPARATOR.compare(pytestPackage.getVersion(), "2.3.3") >= 0) { keywords = pyClass.getName() + " and " + keywords; } diff --git a/python/src/com/jetbrains/python/validation/Pep8ExternalAnnotator.java b/python/src/com/jetbrains/python/validation/Pep8ExternalAnnotator.java index a7faa6ebb98f..22bb1d308d2c 100644 --- a/python/src/com/jetbrains/python/validation/Pep8ExternalAnnotator.java +++ b/python/src/com/jetbrains/python/validation/Pep8ExternalAnnotator.java @@ -15,6 +15,7 @@ */ package com.jetbrains.python.validation; +import com.google.common.collect.ImmutableMap; import com.intellij.codeHighlighting.HighlightDisplayLevel; import com.intellij.codeInsight.daemon.HighlightDisplayKey; import com.intellij.codeInsight.intention.IntentionAction; @@ -173,7 +174,7 @@ public class Pep8ExternalAnnotator extends ExternalAnnotator<Pep8ExternalAnnotat options.add("-"); ProcessOutput output = PySdkUtil.getProcessOutput(new File(collectedInfo.interpreterPath).getParent(), ArrayUtil.toStringArray(options), - new String[] { "PYTHONUNBUFFERED=1" }, + ImmutableMap.of("PYTHONBUFFERED", "1"), 10000, collectedInfo.fileText.getBytes(), false); diff --git a/python/src/com/jetbrains/python/validation/StringLiteralQuotesAnnotator.java b/python/src/com/jetbrains/python/validation/StringLiteralQuotesAnnotator.java index 0666e8319caa..997123351d36 100644 --- a/python/src/com/jetbrains/python/validation/StringLiteralQuotesAnnotator.java +++ b/python/src/com/jetbrains/python/validation/StringLiteralQuotesAnnotator.java @@ -18,8 +18,10 @@ package com.jetbrains.python.validation; import com.intellij.lang.ASTNode; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.text.StringUtil; +import com.jetbrains.python.PyBundle; import com.jetbrains.python.psi.PyStringLiteralExpression; import com.jetbrains.python.psi.impl.PyStringLiteralExpressionImpl; +import org.jetbrains.annotations.NotNull; import java.util.List; @@ -29,17 +31,16 @@ import java.util.List; * @author dcheryasov */ public class StringLiteralQuotesAnnotator extends PyAnnotator { - public static final String MISSING_Q = "Missing closing quote"; private static final String TRIPLE_QUOTES = "\"\"\""; private static final String TRIPLE_APOS = "'''"; public void visitPyStringLiteralExpression(final PyStringLiteralExpression node) { - List<ASTNode> stringNodes = node.getStringNodes(); + final List<ASTNode> stringNodes = node.getStringNodes(); for (ASTNode stringNode : stringNodes) { - boolean foundError; - String nodeText = stringNode.getText(); - int index = PyStringLiteralExpressionImpl.getPrefixLength(nodeText); - String unprefixed = nodeText.substring(index); + final String nodeText = stringNode.getText(); + final int index = PyStringLiteralExpressionImpl.getPrefixLength(nodeText); + final String unprefixed = nodeText.substring(index); + final boolean foundError; if (StringUtil.startsWith(unprefixed, TRIPLE_QUOTES)) { foundError = checkTripleQuotedString(stringNode, unprefixed, TRIPLE_QUOTES); } @@ -49,23 +50,33 @@ public class StringLiteralQuotesAnnotator extends PyAnnotator { else { foundError = checkQuotedString(stringNode, unprefixed); } - if (foundError) break; + if (foundError) { + break; + } } } - private boolean checkQuotedString(ASTNode stringNode, String nodeText) { - char firstQuote = nodeText.charAt(0); - int lastChar = nodeText.length()-1; - if (lastChar == 0 || nodeText.charAt(lastChar) != firstQuote || - (nodeText.charAt(lastChar-1) == '\\' && (lastChar == 1 || nodeText.charAt(lastChar-2) != '\\'))) { - getHolder().createErrorAnnotation(stringNode, MISSING_Q + " [" + firstQuote + "]"); + private boolean checkQuotedString(@NotNull ASTNode stringNode, @NotNull String nodeText) { + final char firstQuote = nodeText.charAt(0); + final char lastChar = nodeText.charAt(nodeText.length() - 1); + int precedingBackslashCount = 0; + for (int i = nodeText.length() - 2; i >= 0; i--) { + if (nodeText.charAt(i) == '\\') { + precedingBackslashCount++; + } + else { + break; + } + } + if (nodeText.length() == 1 || lastChar != firstQuote || precedingBackslashCount % 2 != 0) { + getHolder().createErrorAnnotation(stringNode, PyBundle.message("ANN.missing.closing.quote", firstQuote)); return true; } return false; } - private boolean checkTripleQuotedString(ASTNode stringNode, String text, final String quotes) { - if (text.length() < 6 || !text.endsWith(quotes)) { + private boolean checkTripleQuotedString(@NotNull ASTNode stringNode, @NotNull String text, @NotNull String quotes) { + if (text.length() < 6 || !text.endsWith(quotes)) { int startOffset = StringUtil.trimTrailing(stringNode.getText()).lastIndexOf('\n'); if (startOffset < 0) { startOffset = stringNode.getTextRange().getStartOffset(); @@ -73,8 +84,8 @@ public class StringLiteralQuotesAnnotator extends PyAnnotator { else { startOffset = stringNode.getTextRange().getStartOffset() + startOffset + 1; } - TextRange highlightRange = new TextRange(startOffset, stringNode.getTextRange().getEndOffset()); - getHolder().createErrorAnnotation(highlightRange, "Missing closing triple quotes"); + final TextRange highlightRange = new TextRange(startOffset, stringNode.getTextRange().getEndOffset()); + getHolder().createErrorAnnotation(highlightRange, PyBundle.message("ANN.missing.closing.triple.quotes")); return true; } return false; |