diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/editor/PyEnterAtIndentHandler.java b/python-impl/src/main/java/com/jetbrains/python/impl/editor/PyEnterAtIndentHandler.java index 04326b9f..1a094873 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/editor/PyEnterAtIndentHandler.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/editor/PyEnterAtIndentHandler.java @@ -26,8 +26,7 @@ import consulo.language.editor.inject.EditorWindow; import consulo.language.inject.InjectedLanguageManager; import consulo.language.psi.PsiFile; -import consulo.util.lang.ref.Ref; - +import consulo.util.lang.ref.SimpleReference; import jakarta.annotation.Nonnull; /** @@ -35,27 +34,29 @@ */ @ExtensionImpl public class PyEnterAtIndentHandler extends EnterHandlerDelegateAdapter { - @Override - public Result preprocessEnter(@Nonnull PsiFile file, - @Nonnull Editor editor, - @Nonnull Ref caretOffset, - @Nonnull Ref caretAdvance, - @Nonnull DataContext dataContext, - EditorActionHandler originalHandler) { - int offset = caretOffset.get(); - if (editor instanceof EditorWindow) { - file = InjectedLanguageManager.getInstance(file.getProject()).getTopLevelFile(file); - editor = EditorWindow.getTopLevelEditor(editor); - offset = editor.getCaretModel().getOffset(); - } - if (!(file instanceof PyFile)) { - return Result.Continue; - } + @Override + public Result preprocessEnter( + @Nonnull PsiFile file, + @Nonnull Editor editor, + @Nonnull SimpleReference caretOffset, + @Nonnull SimpleReference caretAdvance, + @Nonnull DataContext dataContext, + EditorActionHandler originalHandler + ) { + int offset = caretOffset.get(); + if (editor instanceof EditorWindow) { + file = InjectedLanguageManager.getInstance(file.getProject()).getTopLevelFile(file); + editor = EditorWindow.getTopLevelEditor(editor); + offset = editor.getCaretModel().getOffset(); + } + if (!(file instanceof PyFile)) { + return Result.Continue; + } - // honor dedent (PY-3009) - if (EditorBackspaceUtil.isWhitespaceBeforeCaret(editor)) { - return Result.DefaultSkipIndent; + // honor dedent (PY-3009) + if (EditorBackspaceUtil.isWhitespaceBeforeCaret(editor)) { + return Result.DefaultSkipIndent; + } + return Result.Continue; } - return Result.Continue; - } } diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/editor/PyEnterBetweenBracketsHandler.java b/python-impl/src/main/java/com/jetbrains/python/impl/editor/PyEnterBetweenBracketsHandler.java index a45406b2..2a9fb3ca 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/editor/PyEnterBetweenBracketsHandler.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/editor/PyEnterBetweenBracketsHandler.java @@ -13,17 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.jetbrains.python.impl.editor; +import com.jetbrains.python.PythonLanguage; +import consulo.annotation.access.RequiredReadAction; import consulo.annotation.component.ExtensionImpl; -import consulo.ide.impl.idea.codeInsight.editorActions.enter.EnterBetweenBracesHandler; -import consulo.dataContext.DataContext; import consulo.codeEditor.Editor; import consulo.codeEditor.action.EditorActionHandler; -import consulo.util.lang.ref.Ref; +import consulo.dataContext.DataContext; +import consulo.ide.impl.idea.codeInsight.editorActions.enter.EnterBetweenBracesHandler; import consulo.language.psi.PsiFile; -import com.jetbrains.python.PythonLanguage; +import consulo.util.lang.ref.SimpleReference; import jakarta.annotation.Nonnull; /** @@ -31,21 +31,24 @@ */ @ExtensionImpl public class PyEnterBetweenBracketsHandler extends EnterBetweenBracesHandler { - @Override - public Result preprocessEnter(@Nonnull PsiFile file, - @Nonnull Editor editor, - @Nonnull Ref caretOffsetRef, - @Nonnull Ref caretAdvance, - @Nonnull DataContext dataContext, - EditorActionHandler originalHandler) { - if (!file.getLanguage().is(PythonLanguage.getInstance())) { - return Result.Continue; + @Override + @RequiredReadAction + public Result preprocessEnter( + @Nonnull PsiFile file, + @Nonnull Editor editor, + @Nonnull SimpleReference caretOffsetRef, + @Nonnull SimpleReference caretAdvance, + @Nonnull DataContext dataContext, + EditorActionHandler originalHandler + ) { + if (!file.getLanguage().is(PythonLanguage.getInstance())) { + return Result.Continue; + } + return super.preprocessEnter(file, editor, caretOffsetRef, caretAdvance, dataContext, originalHandler); } - return super.preprocessEnter(file, editor, caretOffsetRef, caretAdvance, dataContext, originalHandler); - } - @Override - protected boolean isBracePair(char c1, char c2) { - return c1 == '[' && c2 == ']'; - } + @Override + protected boolean isBracePair(char c1, char c2) { + return c1 == '[' && c2 == ']'; + } } diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/editor/PythonEnterHandler.java b/python-impl/src/main/java/com/jetbrains/python/impl/editor/PythonEnterHandler.java index 93df5d24..22867358 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/editor/PythonEnterHandler.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/editor/PythonEnterHandler.java @@ -19,8 +19,9 @@ import com.jetbrains.python.impl.codeInsight.PyCodeInsightSettings; import com.jetbrains.python.impl.documentation.docstrings.*; import com.jetbrains.python.impl.psi.PyIndentUtil; -import com.jetbrains.python.psi.*; import com.jetbrains.python.impl.psi.impl.PyStringLiteralExpressionImpl; +import com.jetbrains.python.psi.*; +import consulo.annotation.access.RequiredReadAction; import consulo.annotation.component.ExtensionImpl; import consulo.application.util.LineTokenizer; import consulo.codeEditor.Editor; @@ -38,10 +39,11 @@ import consulo.language.inject.InjectedLanguageManager; import consulo.language.psi.*; import consulo.language.psi.util.PsiTreeUtil; -import consulo.util.lang.ref.Ref; - +import consulo.ui.annotation.RequiredUIAccess; +import consulo.util.lang.ref.SimpleReference; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; + import java.util.regex.Matcher; /** @@ -49,411 +51,439 @@ */ @ExtensionImpl public class PythonEnterHandler extends EnterHandlerDelegateAdapter { - private int myPostprocessShift = 0; - - public static final Class[] IMPLICIT_WRAP_CLASSES = new Class[]{ - PyListLiteralExpression.class, - PySetLiteralExpression.class, - PyDictLiteralExpression.class, - PyDictLiteralExpression.class, - PyParenthesizedExpression.class, - PyArgumentList.class, - PyParameterList.class - }; - - private static final Class[] WRAPPABLE_CLASSES = new Class[]{ - PsiComment.class, - PyParenthesizedExpression.class, - PyListCompExpression.class, - PyDictCompExpression.class, - PySetCompExpression.class, - PyDictLiteralExpression.class, - PySetLiteralExpression.class, - PyListLiteralExpression.class, - PyArgumentList.class, - PyParameterList.class, - PyDecoratorList.class, - PySliceExpression.class, - PySubscriptionExpression.class, - PyGeneratorExpression.class - }; - - @Override - public Result preprocessEnter(@Nonnull PsiFile file, - @Nonnull Editor editor, - @Nonnull Ref caretOffset, - @Nonnull Ref caretAdvance, - @Nonnull DataContext dataContext, - EditorActionHandler originalHandler) { - int offset = caretOffset.get(); - if (editor instanceof EditorWindow) { - file = InjectedLanguageManager.getInstance(file.getProject()).getTopLevelFile(file); - editor = EditorWindow.getTopLevelEditor(editor); - offset = editor.getCaretModel().getOffset(); - } - if (!(file instanceof PyFile)) { - return Result.Continue; - } - final Boolean isSplitLine = DataManager.getInstance() - .loadFromDataContext(dataContext, - consulo.ide.impl.idea.openapi.editor.actions.SplitLineAction.SPLIT_LINE_KEY); - if (isSplitLine != null) { - return Result.Continue; - } - final Document doc = editor.getDocument(); - PsiDocumentManager.getInstance(file.getProject()).commitDocument(doc); - final PsiElement element = file.findElementAt(offset); - CodeInsightSettings codeInsightSettings = CodeInsightSettings.getInstance(); - if (codeInsightSettings.JAVADOC_STUB_ON_ENTER) { - PsiElement comment = element; - if (comment == null && offset != 0) { - comment = file.findElementAt(offset - 1); - } - int expectedStringStart = editor.getCaretModel().getOffset() - 3; // """ or ''' - if (comment != null) { - final DocstringState state = canGenerateDocstring(comment, expectedStringStart, doc); - if (state != DocstringState.NONE) { - insertDocStringStub(editor, comment, state); - return Result.Continue; - } - } - } + private int myPostprocessShift = 0; + + public static final Class[] IMPLICIT_WRAP_CLASSES = new Class[]{ + PyListLiteralExpression.class, + PySetLiteralExpression.class, + PyDictLiteralExpression.class, + PyDictLiteralExpression.class, + PyParenthesizedExpression.class, + PyArgumentList.class, + PyParameterList.class + }; + + private static final Class[] WRAPPABLE_CLASSES = new Class[]{ + PsiComment.class, + PyParenthesizedExpression.class, + PyListCompExpression.class, + PyDictCompExpression.class, + PySetCompExpression.class, + PyDictLiteralExpression.class, + PySetLiteralExpression.class, + PyListLiteralExpression.class, + PyArgumentList.class, + PyParameterList.class, + PyDecoratorList.class, + PySliceExpression.class, + PySubscriptionExpression.class, + PyGeneratorExpression.class + }; + + @Override + @RequiredReadAction + public Result preprocessEnter( + @Nonnull PsiFile file, + @Nonnull Editor editor, + @Nonnull SimpleReference caretOffset, + @Nonnull SimpleReference caretAdvance, + @Nonnull DataContext dataContext, + EditorActionHandler originalHandler + ) { + int offset = caretOffset.get(); + if (editor instanceof EditorWindow) { + file = InjectedLanguageManager.getInstance(file.getProject()).getTopLevelFile(file); + editor = EditorWindow.getTopLevelEditor(editor); + offset = editor.getCaretModel().getOffset(); + } + if (!(file instanceof PyFile)) { + return Result.Continue; + } + Boolean isSplitLine = DataManager.getInstance().loadFromDataContext( + dataContext, + consulo.ide.impl.idea.openapi.editor.actions.SplitLineAction.SPLIT_LINE_KEY + ); + if (isSplitLine != null) { + return Result.Continue; + } + Document doc = editor.getDocument(); + PsiDocumentManager.getInstance(file.getProject()).commitDocument(doc); + PsiElement element = file.findElementAt(offset); + CodeInsightSettings codeInsightSettings = CodeInsightSettings.getInstance(); + if (codeInsightSettings.JAVADOC_STUB_ON_ENTER) { + PsiElement comment = element; + if (comment == null && offset != 0) { + comment = file.findElementAt(offset - 1); + } + int expectedStringStart = editor.getCaretModel().getOffset() - 3; // """ or ''' + if (comment != null) { + DocstringState state = canGenerateDocstring(comment, expectedStringStart, doc); + if (state != DocstringState.NONE) { + insertDocStringStub(editor, comment, state); + return Result.Continue; + } + } + } - if (element == null) { - return Result.Continue; - } + if (element == null) { + return Result.Continue; + } - PsiElement elementParent = element.getParent(); - if (element.getNode().getElementType() == PyTokenTypes.LPAR) { - elementParent = elementParent.getParent(); - } - if (elementParent instanceof PyParenthesizedExpression || elementParent instanceof PyGeneratorExpression) { - return Result.Continue; - } + PsiElement elementParent = element.getParent(); + if (element.getNode().getElementType() == PyTokenTypes.LPAR) { + elementParent = elementParent.getParent(); + } + if (elementParent instanceof PyParenthesizedExpression || elementParent instanceof PyGeneratorExpression) { + return Result.Continue; + } - if (offset > 0 && !(PyTokenTypes.STRING_NODES.contains(element.getNode().getElementType()))) { - final PsiElement prevElement = file.findElementAt(offset - 1); - if (prevElement == element) { - return Result.Continue; - } - } + if (offset > 0 && !(PyTokenTypes.STRING_NODES.contains(element.getNode().getElementType()))) { + PsiElement prevElement = file.findElementAt(offset - 1); + if (prevElement == element) { + return Result.Continue; + } + } - if (PyTokenTypes.TRIPLE_NODES.contains(element.getNode().getElementType()) || element.getNode() - .getElementType() == PyTokenTypes.DOCSTRING) { - return Result.Continue; - } + if (PyTokenTypes.TRIPLE_NODES.contains(element.getNode().getElementType()) + || element.getNode().getElementType() == PyTokenTypes.DOCSTRING) { + return Result.Continue; + } - final PsiElement prevElement = file.findElementAt(offset - 1); - PyStringLiteralExpression string = PsiTreeUtil.findElementOfClassAtOffset(file, offset, PyStringLiteralExpression.class, false); + PsiElement prevElement = file.findElementAt(offset - 1); + PyStringLiteralExpression string = + PsiTreeUtil.findElementOfClassAtOffset(file, offset, PyStringLiteralExpression.class, false); + + if (string != null && prevElement != null + && PyTokenTypes.STRING_NODES.contains(prevElement.getNode().getElementType()) + && string.getTextOffset() < offset && !(element.getNode() instanceof PsiWhiteSpace)) { + String stringText = element.getText(); + int prefixLength = PyStringLiteralExpressionImpl.getPrefixLength(stringText); + if (string.getTextOffset() + prefixLength >= offset) { + return Result.Continue; + } + String pref = element.getText().substring(0, prefixLength); + String quote = element.getText().substring(prefixLength, prefixLength + 1); + boolean nextIsBackslash = "\\".equals(doc.getText(TextRange.create(offset - 1, offset))); + boolean isEscapedQuote = quote.equals(doc.getText(TextRange.create(offset, offset + 1))) && nextIsBackslash; + boolean isEscapedBackslash = "\\".equals(doc.getText(TextRange.create(offset - 2, offset - 1))) && nextIsBackslash; + if (nextIsBackslash && !isEscapedQuote && !isEscapedBackslash) { + return Result.Continue; + } - if (string != null && prevElement != null && PyTokenTypes.STRING_NODES.contains(prevElement.getNode() - .getElementType()) && string.getTextOffset() < offset && !(element - .getNode() instanceof - PsiWhiteSpace)) { - final String stringText = element.getText(); - final int prefixLength = PyStringLiteralExpressionImpl.getPrefixLength(stringText); - if (string.getTextOffset() + prefixLength >= offset) { - return Result.Continue; - } - final String pref = element.getText().substring(0, prefixLength); - final String quote = element.getText().substring(prefixLength, prefixLength + 1); - final boolean nextIsBackslash = "\\".equals(doc.getText(TextRange.create(offset - 1, offset))); - final boolean isEscapedQuote = quote.equals(doc.getText(TextRange.create(offset, offset + 1))) && nextIsBackslash; - final boolean isEscapedBackslash = "\\".equals(doc.getText(TextRange.create(offset - 2, offset - 1))) && nextIsBackslash; - if (nextIsBackslash && !isEscapedQuote && !isEscapedBackslash) { - return Result.Continue; - } + StringBuilder replacementString = new StringBuilder(); + myPostprocessShift = prefixLength + quote.length(); - final StringBuilder replacementString = new StringBuilder(); - myPostprocessShift = prefixLength + quote.length(); + //noinspection unchecked + if (PsiTreeUtil.getParentOfType(string, IMPLICIT_WRAP_CLASSES) != null) { + replacementString.append(quote).append(pref).append(quote); + doc.insertString(offset, replacementString); + caretOffset.set(caretOffset.get() + 1); + return Result.Continue; + } + else { + if (isEscapedQuote) { + replacementString.append(quote); + caretOffset.set(caretOffset.get() + 1); + } + replacementString.append(quote).append(" \\").append(pref); + if (!isEscapedQuote) { + replacementString.append(quote); + } + doc.insertString(offset, replacementString.toString()); + caretOffset.set(caretOffset.get() + 3); + return Result.Continue; + } + } - if (PsiTreeUtil.getParentOfType(string, IMPLICIT_WRAP_CLASSES) != null) { - replacementString.append(quote).append(pref).append(quote); - doc.insertString(offset, replacementString); - caretOffset.set(caretOffset.get() + 1); - return Result.Continue; - } - else { - if (isEscapedQuote) { - replacementString.append(quote); - caretOffset.set(caretOffset.get() + 1); - } - replacementString.append(quote).append(" \\").append(pref); - if (!isEscapedQuote) { - replacementString.append(quote); - } - doc.insertString(offset, replacementString.toString()); - caretOffset.set(caretOffset.get() + 3); + if (!PyCodeInsightSettings.getInstance().INSERT_BACKSLASH_ON_WRAP) { + return Result.Continue; + } + return checkInsertBackslash(file, caretOffset, dataContext, offset, doc); + } + + @RequiredReadAction + private static Result checkInsertBackslash( + PsiFile file, + SimpleReference caretOffset, + DataContext dataContext, + int offset, + Document doc + ) { + boolean autoWrapInProgress = DataManager.getInstance().loadFromDataContext( + dataContext, + consulo.ide.impl.idea.codeInsight.editorActions.AutoHardWrapHandler.AUTO_WRAP_LINE_IN_PROGRESS_KEY + ) != null; + if (needInsertBackslash(file, offset, autoWrapInProgress)) { + doc.insertString(offset, "\\"); + caretOffset.set(caretOffset.get() + 1); + } return Result.Continue; - } } - - if (!PyCodeInsightSettings.getInstance().INSERT_BACKSLASH_ON_WRAP) { - return Result.Continue; - } - return checkInsertBackslash(file, caretOffset, dataContext, offset, doc); - } - - private static Result checkInsertBackslash(PsiFile file, Ref caretOffset, DataContext dataContext, int offset, Document doc) { - boolean autoWrapInProgress = DataManager.getInstance() - .loadFromDataContext(dataContext, - consulo.ide.impl.idea.codeInsight.editorActions.AutoHardWrapHandler.AUTO_WRAP_LINE_IN_PROGRESS_KEY) != null; - if (needInsertBackslash(file, offset, autoWrapInProgress)) { - doc.insertString(offset, "\\"); - caretOffset.set(caretOffset.get() + 1); - } - return Result.Continue; - } - - public static boolean needInsertBackslash(PsiFile file, int offset, boolean autoWrapInProgress) { - if (offset > 0) { - final PsiElement beforeCaret = file.findElementAt(offset - 1); - if (beforeCaret instanceof PsiWhiteSpace && beforeCaret.getText().indexOf('\\') >= 0) { - // we've got a backslash at EOL already, don't need another one - return false; - } - } - PsiElement atCaret = file.findElementAt(offset); - if (atCaret == null) { - return false; - } - ASTNode nodeAtCaret = atCaret.getNode(); - return needInsertBackslash(nodeAtCaret, autoWrapInProgress); - } - - public static boolean needInsertBackslash(ASTNode nodeAtCaret, boolean autoWrapInProgress) { - PsiElement statementBefore = findStatementBeforeCaret(nodeAtCaret); - PsiElement statementAfter = findStatementAfterCaret(nodeAtCaret); - if (statementBefore != statementAfter) { // Enter pressed at statement break - return false; - } - if (statementBefore == null) { // empty file - return false; + @RequiredReadAction + public static boolean needInsertBackslash(PsiFile file, int offset, boolean autoWrapInProgress) { + if (offset > 0) { + PsiElement beforeCaret = file.findElementAt(offset - 1); + if (beforeCaret instanceof PsiWhiteSpace && beforeCaret.getText().indexOf('\\') >= 0) { + // we've got a backslash at EOL already, don't need another one + return false; + } + } + PsiElement atCaret = file.findElementAt(offset); + if (atCaret == null) { + return false; + } + ASTNode nodeAtCaret = atCaret.getNode(); + return needInsertBackslash(nodeAtCaret, autoWrapInProgress); } - if (PsiTreeUtil.hasErrorElements(statementBefore)) { - if (!autoWrapInProgress) { - // code is already bad, don't mess it up even further - return false; - } - // if we're in middle of typing, it's expected that we will have error elements - } + @RequiredReadAction + public static boolean needInsertBackslash(ASTNode nodeAtCaret, boolean autoWrapInProgress) { + PsiElement statementBefore = findStatementBeforeCaret(nodeAtCaret); + PsiElement statementAfter = findStatementAfterCaret(nodeAtCaret); + if (statementBefore != statementAfter) { // Enter pressed at statement break + return false; + } + if (statementBefore == null) { // empty file + return false; + } - if (inFromImportParentheses(statementBefore, nodeAtCaret.getTextRange().getStartOffset())) { - return false; - } + if (PsiTreeUtil.hasErrorElements(statementBefore)) { + if (!autoWrapInProgress) { + // code is already bad, don't mess it up even further + return false; + } + // if we're in middle of typing, it's expected that we will have error elements + } - PsiElement wrappableBefore = findWrappable(nodeAtCaret, true); - PsiElement wrappableAfter = findWrappable(nodeAtCaret, false); - if (!(wrappableBefore instanceof PsiComment)) { - while (wrappableBefore != null) { - PsiElement next = PsiTreeUtil.getParentOfType(wrappableBefore, WRAPPABLE_CLASSES); - if (next == null) { - break; + if (inFromImportParentheses(statementBefore, nodeAtCaret.getTextRange().getStartOffset())) { + return false; + } + + PsiElement wrappableBefore = findWrappable(nodeAtCaret, true); + PsiElement wrappableAfter = findWrappable(nodeAtCaret, false); + if (!(wrappableBefore instanceof PsiComment)) { + while (wrappableBefore != null) { + @SuppressWarnings("unchecked") + PsiElement next = PsiTreeUtil.getParentOfType(wrappableBefore, WRAPPABLE_CLASSES); + if (next == null) { + break; + } + wrappableBefore = next; + } + } + if (!(wrappableAfter instanceof PsiComment)) { + while (wrappableAfter != null) { + @SuppressWarnings("unchecked") + PsiElement next = PsiTreeUtil.getParentOfType(wrappableAfter, WRAPPABLE_CLASSES); + if (next == null) { + break; + } + wrappableAfter = next; + } + } + if (wrappableBefore instanceof PsiComment || wrappableAfter instanceof PsiComment) { + return false; + } + if (wrappableAfter == null) { + return !(wrappableBefore instanceof PyDecoratorList); + } + return wrappableBefore != wrappableAfter; + } + + private static void insertDocStringStub(Editor editor, PsiElement element, DocstringState state) { + PyDocStringOwner docOwner = PsiTreeUtil.getParentOfType(element, PyDocStringOwner.class); + if (docOwner != null) { + int caretOffset = editor.getCaretModel().getOffset(); + Document document = editor.getDocument(); + String quotes = document.getText(TextRange.from(caretOffset - 3, 3)); + String docString = PyDocstringGenerator.forDocStringOwner(docOwner) + .withInferredParameters(true) + .withQuotes(quotes) + .forceNewMode() + .buildDocString(); + if (state == DocstringState.INCOMPLETE) { + document.insertString(caretOffset, docString.substring(3)); + } + else if (state == DocstringState.EMPTY) { + document.replaceString(caretOffset, caretOffset + 3, docString.substring(3)); + } } - wrappableBefore = next; - } - } - if (!(wrappableAfter instanceof PsiComment)) { - while (wrappableAfter != null) { - PsiElement next = PsiTreeUtil.getParentOfType(wrappableAfter, WRAPPABLE_CLASSES); - if (next == null) { - break; - } - wrappableAfter = next; - } - } - if (wrappableBefore instanceof PsiComment || wrappableAfter instanceof PsiComment) { - return false; - } - if (wrappableAfter == null) { - return !(wrappableBefore instanceof PyDecoratorList); - } - return wrappableBefore != wrappableAfter; - } - - private static void insertDocStringStub(Editor editor, PsiElement element, DocstringState state) { - PyDocStringOwner docOwner = PsiTreeUtil.getParentOfType(element, PyDocStringOwner.class); - if (docOwner != null) { - final int caretOffset = editor.getCaretModel().getOffset(); - final Document document = editor.getDocument(); - final String quotes = document.getText(TextRange.from(caretOffset - 3, 3)); - final String docString = - PyDocstringGenerator.forDocStringOwner(docOwner).withInferredParameters(true).withQuotes(quotes).forceNewMode().buildDocString(); - if (state == DocstringState.INCOMPLETE) { - document.insertString(caretOffset, docString.substring(3)); - } - else if (state == DocstringState.EMPTY) { - document.replaceString(caretOffset, caretOffset + 3, docString.substring(3)); - } } - } - - @Nullable - private static PsiElement findWrappable(ASTNode nodeAtCaret, boolean before) { - PsiElement wrappable = before ? findBeforeCaret(nodeAtCaret, WRAPPABLE_CLASSES) : findAfterCaret(nodeAtCaret, WRAPPABLE_CLASSES); - if (wrappable == null) { - PsiElement emptyTuple = - before ? findBeforeCaret(nodeAtCaret, PyTupleExpression.class) : findAfterCaret(nodeAtCaret, PyTupleExpression.class); - if (emptyTuple != null && emptyTuple.getNode().getFirstChildNode().getElementType() == PyTokenTypes.LPAR) { - wrappable = emptyTuple; - } + + @Nullable + @SuppressWarnings("unchecked") + private static PsiElement findWrappable(ASTNode nodeAtCaret, boolean before) { + PsiElement wrappable = before ? findBeforeCaret(nodeAtCaret, WRAPPABLE_CLASSES) : findAfterCaret(nodeAtCaret, WRAPPABLE_CLASSES); + if (wrappable == null) { + PsiElement emptyTuple = before + ? findBeforeCaret(nodeAtCaret, PyTupleExpression.class) + : findAfterCaret(nodeAtCaret, PyTupleExpression.class); + if (emptyTuple != null && emptyTuple.getNode().getFirstChildNode().getElementType() == PyTokenTypes.LPAR) { + wrappable = emptyTuple; + } + } + return wrappable; } - return wrappable; - } - - @Nullable - private static PsiElement findStatementBeforeCaret(ASTNode node) { - return findBeforeCaret(node, PyStatement.class); - } - - @Nullable - private static PsiElement findStatementAfterCaret(ASTNode node) { - return findAfterCaret(node, PyStatement.class); - } - - private static PsiElement findBeforeCaret(ASTNode atCaret, Class... classes) { - while (atCaret != null) { - atCaret = TreeUtil.prevLeaf(atCaret); - if (atCaret != null && atCaret.getElementType() != TokenType.WHITE_SPACE) { - return getNonStrictParentOfType(atCaret.getPsi(), classes); - } + + @Nullable + @SuppressWarnings("unchecked") + private static PsiElement findStatementBeforeCaret(ASTNode node) { + return findBeforeCaret(node, PyStatement.class); } - return null; - } - - private static PsiElement findAfterCaret(ASTNode atCaret, Class... classes) { - while (atCaret != null) { - if (atCaret.getElementType() != TokenType.WHITE_SPACE) { - return getNonStrictParentOfType(atCaret.getPsi(), classes); - } - atCaret = TreeUtil.nextLeaf(atCaret); + + @Nullable + @SuppressWarnings("unchecked") + private static PsiElement findStatementAfterCaret(ASTNode node) { + return findAfterCaret(node, PyStatement.class); } - return null; - } - - @Nullable - private static T getNonStrictParentOfType(@Nonnull PsiElement element, @Nonnull Class... classes) { - PsiElement run = element; - while (run != null) { - for (Class aClass : classes) { - if (aClass.isInstance(run)) { - return (T)run; - } - } - if (run instanceof PsiFile || run instanceof PyStatementList) { - break; - } - run = run.getParent(); + + @SafeVarargs + private static PsiElement findBeforeCaret(ASTNode atCaret, Class... classes) { + while (atCaret != null) { + atCaret = TreeUtil.prevLeaf(atCaret); + if (atCaret != null && atCaret.getElementType() != TokenType.WHITE_SPACE) { + return getNonStrictParentOfType(atCaret.getPsi(), classes); + } + } + return null; } - return null; - } + @SafeVarargs + private static PsiElement findAfterCaret(ASTNode atCaret, Class... classes) { + while (atCaret != null) { + if (atCaret.getElementType() != TokenType.WHITE_SPACE) { + return getNonStrictParentOfType(atCaret.getPsi(), classes); + } + atCaret = TreeUtil.nextLeaf(atCaret); + } + return null; + } + + @Nullable + @SafeVarargs + @SuppressWarnings("unchecked") + private static T getNonStrictParentOfType(@Nonnull PsiElement element, @Nonnull Class... classes) { + PsiElement run = element; + while (run != null) { + for (Class aClass : classes) { + if (aClass.isInstance(run)) { + return (T)run; + } + } + if (run instanceof PsiFile || run instanceof PyStatementList) { + break; + } + run = run.getParent(); + } - private static boolean inFromImportParentheses(PsiElement statement, int offset) { - if (!(statement instanceof PyFromImportStatement)) { - return false; + return null; } - PyFromImportStatement fromImportStatement = (PyFromImportStatement)statement; - PsiElement leftParen = fromImportStatement.getLeftParen(); - if (leftParen != null && offset >= leftParen.getTextRange().getEndOffset()) { - return true; - } - return false; - } - @Override - public Result postProcessEnter(@Nonnull PsiFile file, @Nonnull Editor editor, @Nonnull DataContext dataContext) { - if (!(file instanceof PyFile)) { - return Result.Continue; - } - if (myPostprocessShift > 0) { - editor.getCaretModel().moveCaretRelatively(myPostprocessShift, 0, false, false, false); - myPostprocessShift = 0; - return Result.Continue; - } - addGoogleDocStringSectionIndent(file, editor, editor.getCaretModel().getOffset()); - return super.postProcessEnter(file, editor, dataContext); - } - - private static void addGoogleDocStringSectionIndent(@Nonnull PsiFile file, @Nonnull Editor editor, int offset) { - final Document document = editor.getDocument(); - PsiDocumentManager.getInstance(file.getProject()).commitDocument(document); - final PsiElement element = file.findElementAt(offset); - if (element != null) { - // Insert additional indentation after section header in Google code style docstrings - final PyStringLiteralExpression pyString = DocStringUtil.getParentDefinitionDocString(element); - if (pyString != null) { - final String docStringText = pyString.getText(); - final DocStringFormat format = DocStringUtil.guessDocStringFormat(docStringText, pyString); - if (format == DocStringFormat.GOOGLE && offset + 1 < document.getTextLength()) { - final int lineNum = document.getLineNumber(offset); - final TextRange lineRange = TextRange.create(document.getLineStartOffset(lineNum - 1), document.getLineEndOffset(lineNum - 1)); - final Matcher matcher = GoogleCodeStyleDocString.SECTION_HEADER.matcher(document.getText(lineRange)); - if (matcher.matches() && SectionBasedDocString.isValidSectionTitle(matcher.group(1))) { - document.insertString(offset, GoogleCodeStyleDocStringBuilder.getDefaultSectionIndent(file.getProject())); - editor.getCaretModel().moveCaretRelatively(2, 0, false, false, false); - } - } - } + @RequiredReadAction + private static boolean inFromImportParentheses(PsiElement statement, int offset) { + if (!(statement instanceof PyFromImportStatement fromImportStatement)) { + return false; + } + PsiElement leftParen = fromImportStatement.getLeftParen(); + return leftParen != null && offset >= leftParen.getTextRange().getEndOffset(); } - } - - enum DocstringState { - NONE, - INCOMPLETE, - EMPTY - } - - @Nonnull - public static DocstringState canGenerateDocstring(@Nonnull PsiElement element, int firstQuoteOffset, @Nonnull Document document) { - if (firstQuoteOffset < 0 || firstQuoteOffset > document.getTextLength() - 3) { - return DocstringState.NONE; + + @Override + @RequiredUIAccess + public Result postProcessEnter(@Nonnull PsiFile file, @Nonnull Editor editor, @Nonnull DataContext dataContext) { + if (!(file instanceof PyFile)) { + return Result.Continue; + } + if (myPostprocessShift > 0) { + editor.getCaretModel().moveCaretRelatively(myPostprocessShift, 0, false, false, false); + myPostprocessShift = 0; + return Result.Continue; + } + addGoogleDocStringSectionIndent(file, editor, editor.getCaretModel().getOffset()); + return super.postProcessEnter(file, editor, dataContext); + } + + @RequiredUIAccess + private static void addGoogleDocStringSectionIndent(@Nonnull PsiFile file, @Nonnull Editor editor, int offset) { + Document document = editor.getDocument(); + PsiDocumentManager.getInstance(file.getProject()).commitDocument(document); + PsiElement element = file.findElementAt(offset); + if (element != null) { + // Insert additional indentation after section header in Google code style docstrings + PyStringLiteralExpression pyString = DocStringUtil.getParentDefinitionDocString(element); + if (pyString != null) { + String docStringText = pyString.getText(); + DocStringFormat format = DocStringUtil.guessDocStringFormat(docStringText, pyString); + if (format == DocStringFormat.GOOGLE && offset + 1 < document.getTextLength()) { + int lineNum = document.getLineNumber(offset); + TextRange lineRange = + TextRange.create(document.getLineStartOffset(lineNum - 1), document.getLineEndOffset(lineNum - 1)); + Matcher matcher = GoogleCodeStyleDocString.SECTION_HEADER.matcher(document.getText(lineRange)); + if (matcher.matches() && SectionBasedDocString.isValidSectionTitle(matcher.group(1))) { + document.insertString(offset, GoogleCodeStyleDocStringBuilder.getDefaultSectionIndent(file.getProject())); + editor.getCaretModel().moveCaretRelatively(2, 0, false, false, false); + } + } + } + } } - final String quotes = document.getText(TextRange.from(firstQuoteOffset, 3)); - if (!quotes.equals("\"\"\"") && !quotes.equals("'''")) { - return DocstringState.NONE; + + enum DocstringState { + NONE, + INCOMPLETE, + EMPTY } - final PyStringLiteralExpression pyString = DocStringUtil.getParentDefinitionDocString(element); - if (pyString != null) { - - String nodeText = element.getText(); - final int prefixLength = PyStringLiteralExpressionImpl.getPrefixLength(nodeText); - nodeText = nodeText.substring(prefixLength); - - final String literalText = pyString.getText(); - if (literalText.endsWith(nodeText) && nodeText.startsWith(quotes)) { - if (firstQuoteOffset == pyString.getTextOffset() + prefixLength) { - PsiErrorElement error = PsiTreeUtil.getNextSiblingOfType(pyString, PsiErrorElement.class); - if (error == null) { - error = PsiTreeUtil.getNextSiblingOfType(pyString.getParent(), PsiErrorElement.class); - } - if (error != null) { - return DocstringState.INCOMPLETE; - } - - if (nodeText.equals(quotes + quotes)) { - return DocstringState.EMPTY; - } - - if (nodeText.length() < 6 || !nodeText.endsWith(quotes)) { - return DocstringState.INCOMPLETE; - } - // Sometimes if incomplete docstring is followed by another declaration with a docstring, it might be treated - // as complete docstring, because we can't understand that closing quotes actually belong to another docstring. - final String docstringIndent = PyIndentUtil.getLineIndent(document, document.getLineNumber(firstQuoteOffset)); - for (String line : LineTokenizer.tokenizeIntoList(nodeText, false)) { - final String lineIndent = PyIndentUtil.getLineIndent(line); - final String lineContent = line.substring(lineIndent.length()); - if ((lineContent.startsWith("def ") || lineContent.startsWith("class ")) && - docstringIndent.length() > lineIndent.length() && docstringIndent.startsWith(lineIndent)) { - return DocstringState.INCOMPLETE; + + @Nonnull + @RequiredReadAction + public static DocstringState canGenerateDocstring(@Nonnull PsiElement element, int firstQuoteOffset, @Nonnull Document document) { + if (firstQuoteOffset < 0 || firstQuoteOffset > document.getTextLength() - 3) { + return DocstringState.NONE; + } + String quotes = document.getText(TextRange.from(firstQuoteOffset, 3)); + if (!quotes.equals("\"\"\"") && !quotes.equals("'''")) { + return DocstringState.NONE; + } + PyStringLiteralExpression pyString = DocStringUtil.getParentDefinitionDocString(element); + if (pyString != null) { + + String nodeText = element.getText(); + int prefixLength = PyStringLiteralExpressionImpl.getPrefixLength(nodeText); + nodeText = nodeText.substring(prefixLength); + + String literalText = pyString.getText(); + if (literalText.endsWith(nodeText) && nodeText.startsWith(quotes)) { + if (firstQuoteOffset == pyString.getTextOffset() + prefixLength) { + PsiErrorElement error = PsiTreeUtil.getNextSiblingOfType(pyString, PsiErrorElement.class); + if (error == null) { + error = PsiTreeUtil.getNextSiblingOfType(pyString.getParent(), PsiErrorElement.class); + } + if (error != null) { + return DocstringState.INCOMPLETE; + } + + if (nodeText.equals(quotes + quotes)) { + return DocstringState.EMPTY; + } + + if (nodeText.length() < 6 || !nodeText.endsWith(quotes)) { + return DocstringState.INCOMPLETE; + } + // Sometimes if incomplete docstring is followed by another declaration with a docstring, it might be treated + // as complete docstring, because we can't understand that closing quotes actually belong to another docstring. + String docstringIndent = PyIndentUtil.getLineIndent(document, document.getLineNumber(firstQuoteOffset)); + for (String line : LineTokenizer.tokenizeIntoList(nodeText, false)) { + String lineIndent = PyIndentUtil.getLineIndent(line); + String lineContent = line.substring(lineIndent.length()); + if ((lineContent.startsWith("def ") || lineContent.startsWith("class ")) && + docstringIndent.length() > lineIndent.length() && docstringIndent.startsWith(lineIndent)) { + return DocstringState.INCOMPLETE; + } + } + } } - } } - } + return DocstringState.NONE; } - return DocstringState.NONE; - } } diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/run/PythonRunConfigurationProducer.java b/python-impl/src/main/java/com/jetbrains/python/impl/run/PythonRunConfigurationProducer.java index ee453044..a585aa44 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/run/PythonRunConfigurationProducer.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/run/PythonRunConfigurationProducer.java @@ -17,8 +17,8 @@ import com.jetbrains.python.PythonFileType; import com.jetbrains.python.psi.types.TypeEvalContext; +import consulo.annotation.access.RequiredReadAction; import consulo.annotation.component.ExtensionImpl; -import consulo.component.extension.Extensions; import consulo.execution.action.ConfigurationContext; import consulo.execution.action.ConfigurationFromContext; import consulo.execution.action.Location; @@ -26,115 +26,100 @@ import consulo.language.file.light.LightVirtualFile; import consulo.language.psi.PsiElement; import consulo.language.psi.PsiFile; -import consulo.language.util.ModuleUtilCore; import consulo.module.Module; -import consulo.util.lang.ref.Ref; +import consulo.util.lang.ref.SimpleReference; import consulo.virtualFileSystem.VirtualFile; - import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; + import java.io.File; /** * @author yole */ @ExtensionImpl -public class PythonRunConfigurationProducer extends RunConfigurationProducer -{ - - public PythonRunConfigurationProducer() - { - super(PythonConfigurationType.getInstance().getFactory()); - } - - @Override - protected boolean setupConfigurationFromContext(PythonRunConfiguration configuration, ConfigurationContext context, Ref sourceElement) - { +public class PythonRunConfigurationProducer extends RunConfigurationProducer { + public PythonRunConfigurationProducer() { + super(PythonConfigurationType.getInstance().getFactory()); + } - final Location location = context.getLocation(); - if(location == null) - { - return false; - } - final PsiFile script = location.getPsiElement().getContainingFile(); - if(!isAvailable(location, script)) - { - return false; - } + @Override + @RequiredReadAction + protected boolean setupConfigurationFromContext( + PythonRunConfiguration configuration, + ConfigurationContext context, + SimpleReference sourceElement + ) { + Location location = context.getLocation(); + if (location == null) { + return false; + } + PsiFile script = location.getPsiElement().getContainingFile(); + if (!isAvailable(location, script)) { + return false; + } - final VirtualFile vFile = script.getVirtualFile(); - if(vFile == null) - { - return false; - } - configuration.setScriptName(vFile.getPath()); - final VirtualFile parent = vFile.getParent(); - if(parent != null) - { - configuration.setWorkingDirectory(parent.getPath()); - } - final Module module = ModuleUtilCore.findModuleForPsiElement(script); - if(module != null) - { - configuration.setUseModuleSdk(true); - configuration.setModule(module); - } - configuration.setName(configuration.suggestedName()); - return true; - } + VirtualFile vFile = script.getVirtualFile(); + if (vFile == null) { + return false; + } + configuration.setScriptName(vFile.getPath()); + VirtualFile parent = vFile.getParent(); + if (parent != null) { + configuration.setWorkingDirectory(parent.getPath()); + } + Module module = script.getModule(); + if (module != null) { + configuration.setUseModuleSdk(true); + configuration.setModule(module); + } + configuration.setName(configuration.suggestedName()); + return true; + } - @Override - public boolean isConfigurationFromContext(PythonRunConfiguration configuration, ConfigurationContext context) - { - final Location location = context.getLocation(); - if(location == null) - { - return false; - } - final PsiFile script = location.getPsiElement().getContainingFile(); - if(!isAvailable(location, script)) - { - return false; - } - final VirtualFile virtualFile = script.getVirtualFile(); - if(virtualFile == null) - { - return false; - } - if(virtualFile instanceof LightVirtualFile) - { - return false; - } - final String workingDirectory = configuration.getWorkingDirectory(); - final String scriptName = configuration.getScriptName(); - final String path = virtualFile.getPath(); - return scriptName.equals(path) || path.equals(new File(workingDirectory, scriptName).getAbsolutePath()); - } + @Override + @RequiredReadAction + public boolean isConfigurationFromContext(PythonRunConfiguration configuration, ConfigurationContext context) { + Location location = context.getLocation(); + if (location == null) { + return false; + } + PsiFile script = location.getPsiElement().getContainingFile(); + if (!isAvailable(location, script)) { + return false; + } + VirtualFile virtualFile = script.getVirtualFile(); + if (virtualFile == null) { + return false; + } + if (virtualFile instanceof LightVirtualFile) { + return false; + } + String workingDirectory = configuration.getWorkingDirectory(); + String scriptName = configuration.getScriptName(); + String path = virtualFile.getPath(); + return scriptName.equals(path) || path.equals(new File(workingDirectory, scriptName).getAbsolutePath()); + } - private static boolean isAvailable(@Nonnull final Location location, @Nullable final PsiFile script) - { - if(script == null || script.getFileType() != PythonFileType.INSTANCE) - { - return false; - } - final Module module = ModuleUtilCore.findModuleForPsiElement(script); - if(module != null) - { - for(RunnableScriptFilter f : Extensions.getExtensions(RunnableScriptFilter.EP_NAME)) - { - // Configuration producers always called by user - if(f.isRunnableScript(script, module, location, TypeEvalContext.userInitiated(location.getProject(), null))) - { - return false; - } - } - } - return true; - } + @RequiredReadAction + private static boolean isAvailable(@Nonnull Location location, @Nullable PsiFile script) { + if (script == null || script.getFileType() != PythonFileType.INSTANCE) { + return false; + } + Module module = script.getModule(); + if (module != null) { + for (RunnableScriptFilter f : RunnableScriptFilter.EP_NAME.getExtensions()) { + // Configuration producers always called by user + if (f.isRunnableScript(script, module, location, TypeEvalContext.userInitiated(location.getProject(), null))) { + return false; + } + } + } + return true; + } - @Override - public boolean isPreferredConfiguration(ConfigurationFromContext self, ConfigurationFromContext other) - { - return other.isProducedBy(PythonRunConfigurationProducer.class); - } + @Override + public boolean isPreferredConfiguration(ConfigurationFromContext self, ConfigurationFromContext other) { + return other.isProducedBy(PythonRunConfigurationProducer.class); + } } diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/testing/PythonTestConfigurationProducer.java b/python-impl/src/main/java/com/jetbrains/python/impl/testing/PythonTestConfigurationProducer.java index 707f4667..38e2d616 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/testing/PythonTestConfigurationProducer.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/testing/PythonTestConfigurationProducer.java @@ -15,319 +15,288 @@ */ package com.jetbrains.python.impl.testing; -import java.io.File; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; - -import jakarta.annotation.Nonnull; - -import consulo.python.module.extension.PyModuleExtension; -import org.jetbrains.annotations.NonNls; - -import jakarta.annotation.Nullable; import com.google.common.collect.Sets; -import consulo.execution.action.Location; +import com.jetbrains.python.impl.psi.PyUtil; +import com.jetbrains.python.impl.run.PythonRunConfigurationProducer; +import com.jetbrains.python.impl.testing.unittest.PythonUnitTestRunConfiguration; +import com.jetbrains.python.psi.*; +import com.jetbrains.python.psi.types.TypeEvalContext; +import consulo.annotation.access.RequiredReadAction; +import consulo.annotation.access.RequiredWriteAction; import consulo.execution.action.ConfigurationContext; import consulo.execution.action.ConfigurationFromContext; +import consulo.execution.action.Location; import consulo.execution.action.RunConfigurationProducer; import consulo.execution.configuration.ConfigurationFactory; import consulo.execution.configuration.RunConfiguration; +import consulo.language.psi.*; +import consulo.language.psi.util.PsiTreeUtil; +import consulo.language.util.ModuleUtilCore; import consulo.module.Module; import consulo.module.ModuleManager; -import consulo.language.util.ModuleUtilCore; -import consulo.project.Project; import consulo.module.content.ProjectRootManager; -import consulo.util.lang.ref.Ref; +import consulo.project.Project; +import consulo.python.module.extension.PyModuleExtension; import consulo.util.lang.StringUtil; +import consulo.util.lang.ref.SimpleReference; import consulo.virtualFileSystem.VirtualFile; -import consulo.language.psi.PsiDirectory; -import consulo.language.psi.PsiElement; -import consulo.language.psi.PsiFile; -import consulo.language.psi.PsiFileSystemItem; -import consulo.language.psi.PsiWhiteSpace; -import consulo.language.psi.util.PsiTreeUtil; -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 com.jetbrains.python.psi.PyStatement; -import com.jetbrains.python.impl.psi.PyUtil; -import com.jetbrains.python.psi.types.TypeEvalContext; -import com.jetbrains.python.impl.run.PythonRunConfigurationProducer; -import com.jetbrains.python.impl.testing.unittest.PythonUnitTestRunConfiguration; +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; + +import java.io.File; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; /** * User: ktisha */ -abstract public class PythonTestConfigurationProducer extends RunConfigurationProducer -{ - - public PythonTestConfigurationProducer(final ConfigurationFactory configurationFactory) - { - super(configurationFactory); - } - - @Override - public boolean isConfigurationFromContext(AbstractPythonTestRunConfiguration configuration, ConfigurationContext context) - { - final Location location = context.getLocation(); - if(location == null || !isAvailable(location)) - { - return false; - } - final PsiElement element = location.getPsiElement(); - final PsiFileSystemItem file = element.getContainingFile(); - if(file == null) - { - return false; - } - final VirtualFile virtualFile = element instanceof PsiDirectory ? ((PsiDirectory) element).getVirtualFile() : file.getVirtualFile(); - if(virtualFile == null) - { - return false; - } - final PyFunction pyFunction = PsiTreeUtil.getParentOfType(element, PyFunction.class, false); - final PyClass pyClass = PsiTreeUtil.getParentOfType(element, PyClass.class); - - final AbstractPythonTestRunConfiguration.TestType confType = configuration.getTestType(); - final String workingDirectory = configuration.getWorkingDirectory(); - - if(element instanceof PsiDirectory) - { - final String path = ((PsiDirectory) element).getVirtualFile().getPath(); - return confType == AbstractPythonTestRunConfiguration.TestType.TEST_FOLDER && path.equals(configuration.getFolderName()) || path.equals(new File(workingDirectory, configuration - .getFolderName()).getAbsolutePath()); - } - - final String scriptName = configuration.getScriptName(); - final String path = virtualFile.getPath(); - final boolean isTestFileEquals = scriptName.equals(path) || path.equals(new File(workingDirectory, scriptName).getAbsolutePath()); - - if(pyFunction != null) - { - final String methodName = configuration.getMethodName(); - if(pyFunction.getContainingClass() == null) - { - return confType == AbstractPythonTestRunConfiguration.TestType.TEST_FUNCTION && - methodName.equals(pyFunction.getName()) && isTestFileEquals; - } - else - { - final String className = configuration.getClassName(); - - return confType == AbstractPythonTestRunConfiguration.TestType.TEST_METHOD && - methodName.equals(pyFunction.getName()) && - pyClass != null && className.equals(pyClass.getName()) && isTestFileEquals; - } - } - if(pyClass != null) - { - final String className = configuration.getClassName(); - return confType == AbstractPythonTestRunConfiguration.TestType.TEST_CLASS && - className.equals(pyClass.getName()) && isTestFileEquals; - } - return confType == AbstractPythonTestRunConfiguration.TestType.TEST_SCRIPT && isTestFileEquals; - } - - @Override - protected boolean setupConfigurationFromContext(AbstractPythonTestRunConfiguration configuration, ConfigurationContext context, Ref sourceElement) - { - if(context == null) - { - return false; - } - final Location location = context.getLocation(); - if(location == null || !isAvailable(location)) - { - return false; - } - PsiElement element = location.getPsiElement(); - if(element instanceof PsiWhiteSpace) - { - element = PyUtil.findNonWhitespaceAtOffset(element.getContainingFile(), element.getTextOffset()); - } - - if(PythonUnitTestRunnableScriptFilter.isIfNameMain(location)) - { - return false; - } - final Module module = location.getModule(); - if(!isPythonModule(module)) - { - return false; - } - - if(element instanceof PsiDirectory) - { - return setupConfigurationFromFolder((PsiDirectory) element, configuration); - } - - final PyFunction pyFunction = PsiTreeUtil.getParentOfType(element, PyFunction.class, false); - if(pyFunction != null && isTestFunction(pyFunction, configuration)) - { - return setupConfigurationFromFunction(pyFunction, configuration); - } - final PyClass pyClass = PsiTreeUtil.getParentOfType(element, PyClass.class, false); - if(pyClass != null && isTestClass(pyClass, configuration, TypeEvalContext.userInitiated(pyClass.getProject(), element.getContainingFile()))) - { - return setupConfigurationFromClass(pyClass, configuration); - } - if(element == null) - { - return false; - } - final PsiFile file = element.getContainingFile(); - if(file instanceof PyFile && isTestFile((PyFile) file)) - { - return setupConfigurationFromFile((PyFile) file, configuration); - } - - return false; - } - - private boolean setupConfigurationFromFolder(@Nonnull final PsiDirectory element, @Nonnull final AbstractPythonTestRunConfiguration configuration) - { - final VirtualFile virtualFile = element.getVirtualFile(); - if(!isTestFolder(virtualFile, element.getProject())) - { - return false; - } - final String path = virtualFile.getPath(); - - configuration.setTestType(AbstractPythonTestRunConfiguration.TestType.TEST_FOLDER); - configuration.setFolderName(path); - configuration.setWorkingDirectory(path); - configuration.setGeneratedName(); - setModuleSdk(element, configuration); - return true; - } - - private static void setModuleSdk(@Nonnull final PsiElement element, @Nonnull final AbstractPythonTestRunConfiguration configuration) - { - configuration.setUseModuleSdk(true); - configuration.setModule(ModuleUtilCore.findModuleForPsiElement(element)); - } - - protected boolean setupConfigurationFromFunction(@Nonnull final PyFunction pyFunction, @Nonnull final AbstractPythonTestRunConfiguration configuration) - { - final PyClass containingClass = pyFunction.getContainingClass(); - configuration.setMethodName(pyFunction.getName()); - - if(containingClass != null) - { - configuration.setClassName(containingClass.getName()); - configuration.setTestType(AbstractPythonTestRunConfiguration.TestType.TEST_METHOD); - } - else - { - configuration.setTestType(AbstractPythonTestRunConfiguration.TestType.TEST_FUNCTION); - } - return setupConfigurationScript(configuration, pyFunction); - } - - protected boolean setupConfigurationFromClass(@Nonnull final PyClass pyClass, @Nonnull final AbstractPythonTestRunConfiguration configuration) - { - configuration.setTestType(AbstractPythonTestRunConfiguration.TestType.TEST_CLASS); - configuration.setClassName(pyClass.getName()); - return setupConfigurationScript(configuration, pyClass); - } - - protected boolean setupConfigurationFromFile(@Nonnull final PyFile pyFile, @Nonnull final AbstractPythonTestRunConfiguration configuration) - { - configuration.setTestType(AbstractPythonTestRunConfiguration.TestType.TEST_SCRIPT); - return setupConfigurationScript(configuration, pyFile); - } - - protected static boolean setupConfigurationScript(@Nonnull final AbstractPythonTestRunConfiguration cfg, @Nonnull final PyElement element) - { - final PyFile containingFile = PyUtil.getContainingPyFile(element); - if(containingFile == null) - { - return false; - } - final VirtualFile vFile = containingFile.getVirtualFile(); - if(vFile == null) - { - return false; - } - final VirtualFile parent = vFile.getParent(); - if(parent == null) - { - return false; - } - - cfg.setScriptName(vFile.getPath()); - - if(StringUtil.isEmptyOrSpaces(cfg.getWorkingDirectory())) - { - cfg.setWorkingDirectory(parent.getPath()); - } - cfg.setGeneratedName(); - setModuleSdk(element, cfg); - return true; - } - - protected boolean isTestFolder(@Nonnull final VirtualFile virtualFile, @Nonnull final Project project) - { - @NonNls final String name = virtualFile.getName(); - final HashSet roots = Sets.newHashSet(); - final Module[] modules = ModuleManager.getInstance(project).getModules(); - for(Module module : modules) - { - roots.addAll(PyUtil.getSourceRoots(module)); - } - Collections.addAll(roots, ProjectRootManager.getInstance(project).getContentRoots()); - return name.toLowerCase().contains("test") || roots.contains(virtualFile); - } - - protected boolean isAvailable(@Nonnull final Location location) - { - return false; - } - - protected boolean isTestClass(@Nonnull final PyClass pyClass, @Nullable final AbstractPythonTestRunConfiguration configuration, @Nullable final TypeEvalContext context) - { - return PythonUnitTestUtil.isTestCaseClass(pyClass, context); - } - - protected boolean isTestFunction(@Nonnull final PyFunction pyFunction, @Nullable final AbstractPythonTestRunConfiguration configuration) - { - return PythonUnitTestUtil.isTestCaseFunction(pyFunction); - } - - protected boolean isTestFile(@Nonnull final PyFile file) - { - final List testCases = getTestCaseClassesFromFile(file); - if(testCases.isEmpty()) - { - return false; - } - return true; - } - - protected static boolean isPythonModule(Module module) - { - if(module == null) - { - return false; - } - return ModuleUtilCore.getExtension(module, PyModuleExtension.class) != null; - } - - protected List getTestCaseClassesFromFile(@Nonnull final PyFile pyFile) - { - return PythonUnitTestUtil.getTestCaseClassesFromFile(pyFile, TypeEvalContext.userInitiated(pyFile.getProject(), pyFile)); - } - - @Override - public boolean isPreferredConfiguration(ConfigurationFromContext self, ConfigurationFromContext other) - { - final RunConfiguration configuration = self.getConfiguration(); - if(configuration instanceof PythonUnitTestRunConfiguration && ((PythonUnitTestRunConfiguration) configuration).getTestType() == AbstractPythonTestRunConfiguration.TestType.TEST_FOLDER) - { - return true; - } - return other.isProducedBy(PythonTestConfigurationProducer.class) || other.isProducedBy(PythonRunConfigurationProducer.class); - } +abstract public class PythonTestConfigurationProducer extends RunConfigurationProducer { + public PythonTestConfigurationProducer(ConfigurationFactory configurationFactory) { + super(configurationFactory); + } + + @Override + @RequiredReadAction + public boolean isConfigurationFromContext(AbstractPythonTestRunConfiguration configuration, ConfigurationContext context) { + Location location = context.getLocation(); + if (location == null || !isAvailable(location)) { + return false; + } + PsiElement element = location.getPsiElement(); + PsiFileSystemItem file = element.getContainingFile(); + if (file == null) { + return false; + } + VirtualFile virtualFile = element instanceof PsiDirectory directory ? directory.getVirtualFile() : file.getVirtualFile(); + if (virtualFile == null) { + return false; + } + PyFunction pyFunction = PsiTreeUtil.getParentOfType(element, PyFunction.class, false); + PyClass pyClass = PsiTreeUtil.getParentOfType(element, PyClass.class); + + AbstractPythonTestRunConfiguration.TestType confType = configuration.getTestType(); + String workingDirectory = configuration.getWorkingDirectory(); + + if (element instanceof PsiDirectory directory) { + String path = directory.getVirtualFile().getPath(); + return confType == AbstractPythonTestRunConfiguration.TestType.TEST_FOLDER && path.equals(configuration.getFolderName()) + || path.equals(new File(workingDirectory, configuration.getFolderName()).getAbsolutePath()); + } + + String scriptName = configuration.getScriptName(); + String path = virtualFile.getPath(); + boolean isTestFileEquals = scriptName.equals(path) || path.equals(new File(workingDirectory, scriptName).getAbsolutePath()); + + if (pyFunction != null) { + String methodName = configuration.getMethodName(); + if (pyFunction.getContainingClass() == null) { + return confType == AbstractPythonTestRunConfiguration.TestType.TEST_FUNCTION + && methodName.equals(pyFunction.getName()) && isTestFileEquals; + } + else { + String className = configuration.getClassName(); + + return confType == AbstractPythonTestRunConfiguration.TestType.TEST_METHOD + && methodName.equals(pyFunction.getName()) + && pyClass != null && className.equals(pyClass.getName()) && isTestFileEquals; + } + } + if (pyClass != null) { + String className = configuration.getClassName(); + return confType == AbstractPythonTestRunConfiguration.TestType.TEST_CLASS + && className.equals(pyClass.getName()) && isTestFileEquals; + } + return confType == AbstractPythonTestRunConfiguration.TestType.TEST_SCRIPT && isTestFileEquals; + } + + @Override + @RequiredWriteAction + protected boolean setupConfigurationFromContext( + AbstractPythonTestRunConfiguration configuration, + ConfigurationContext context, + SimpleReference sourceElement + ) { + if (context == null) { + return false; + } + Location location = context.getLocation(); + if (location == null || !isAvailable(location)) { + return false; + } + PsiElement element = location.getPsiElement(); + if (element instanceof PsiWhiteSpace) { + element = PyUtil.findNonWhitespaceAtOffset(element.getContainingFile(), element.getTextOffset()); + } + + if (PythonUnitTestRunnableScriptFilter.isIfNameMain(location)) { + return false; + } + Module module = location.getModule(); + if (!isPythonModule(module)) { + return false; + } + + if (element instanceof PsiDirectory directory) { + return setupConfigurationFromFolder(directory, configuration); + } + + PyFunction pyFunction = PsiTreeUtil.getParentOfType(element, PyFunction.class, false); + if (pyFunction != null && isTestFunction(pyFunction, configuration)) { + return setupConfigurationFromFunction(pyFunction, configuration); + } + PyClass pyClass = PsiTreeUtil.getParentOfType(element, PyClass.class, false); + if (pyClass != null && isTestClass( + pyClass, + configuration, + TypeEvalContext.userInitiated(pyClass.getProject(), element.getContainingFile()) + )) { + return setupConfigurationFromClass(pyClass, configuration); + } + if (element == null) { + return false; + } + PsiFile file = element.getContainingFile(); + return file instanceof PyFile pyFile && isTestFile(pyFile) && setupConfigurationFromFile(pyFile, configuration); + } + + @RequiredWriteAction + private boolean setupConfigurationFromFolder(@Nonnull PsiDirectory element, @Nonnull AbstractPythonTestRunConfiguration configuration) { + VirtualFile virtualFile = element.getVirtualFile(); + if (!isTestFolder(virtualFile, element.getProject())) { + return false; + } + String path = virtualFile.getPath(); + + configuration.setTestType(AbstractPythonTestRunConfiguration.TestType.TEST_FOLDER); + configuration.setFolderName(path); + configuration.setWorkingDirectory(path); + configuration.setGeneratedName(); + setModuleSdk(element, configuration); + return true; + } + + @RequiredWriteAction + private static void setModuleSdk(@Nonnull PsiElement element, @Nonnull AbstractPythonTestRunConfiguration configuration) { + configuration.setUseModuleSdk(true); + configuration.setModule(element.getModule()); + } + + @RequiredWriteAction + protected boolean setupConfigurationFromFunction( + @Nonnull PyFunction pyFunction, + @Nonnull AbstractPythonTestRunConfiguration configuration + ) { + PyClass containingClass = pyFunction.getContainingClass(); + configuration.setMethodName(pyFunction.getName()); + + if (containingClass != null) { + configuration.setClassName(containingClass.getName()); + configuration.setTestType(AbstractPythonTestRunConfiguration.TestType.TEST_METHOD); + } + else { + configuration.setTestType(AbstractPythonTestRunConfiguration.TestType.TEST_FUNCTION); + } + return setupConfigurationScript(configuration, pyFunction); + } + + @RequiredWriteAction + protected boolean setupConfigurationFromClass( + @Nonnull PyClass pyClass, + @Nonnull AbstractPythonTestRunConfiguration configuration + ) { + configuration.setTestType(AbstractPythonTestRunConfiguration.TestType.TEST_CLASS); + configuration.setClassName(pyClass.getName()); + return setupConfigurationScript(configuration, pyClass); + } + + @RequiredWriteAction + protected boolean setupConfigurationFromFile( + @Nonnull PyFile pyFile, + @Nonnull AbstractPythonTestRunConfiguration configuration + ) { + configuration.setTestType(AbstractPythonTestRunConfiguration.TestType.TEST_SCRIPT); + return setupConfigurationScript(configuration, pyFile); + } + + @RequiredWriteAction + protected static boolean setupConfigurationScript( + @Nonnull AbstractPythonTestRunConfiguration cfg, + @Nonnull PyElement element + ) { + PyFile containingFile = PyUtil.getContainingPyFile(element); + if (containingFile == null) { + return false; + } + VirtualFile vFile = containingFile.getVirtualFile(); + if (vFile == null) { + return false; + } + VirtualFile parent = vFile.getParent(); + if (parent == null) { + return false; + } + + cfg.setScriptName(vFile.getPath()); + + if (StringUtil.isEmptyOrSpaces(cfg.getWorkingDirectory())) { + cfg.setWorkingDirectory(parent.getPath()); + } + cfg.setGeneratedName(); + setModuleSdk(element, cfg); + return true; + } + + @RequiredReadAction + protected boolean isTestFolder(@Nonnull VirtualFile virtualFile, @Nonnull Project project) { + String name = virtualFile.getName(); + HashSet roots = Sets.newHashSet(); + Module[] modules = ModuleManager.getInstance(project).getModules(); + for (Module module : modules) { + roots.addAll(PyUtil.getSourceRoots(module)); + } + Collections.addAll(roots, ProjectRootManager.getInstance(project).getContentRoots()); + return name.toLowerCase().contains("test") || roots.contains(virtualFile); + } + + protected boolean isAvailable(@Nonnull Location location) { + return false; + } + + protected boolean isTestClass( + @Nonnull PyClass pyClass, + @Nullable AbstractPythonTestRunConfiguration configuration, + @Nullable TypeEvalContext context + ) { + return PythonUnitTestUtil.isTestCaseClass(pyClass, context); + } + + protected boolean isTestFunction( + @Nonnull PyFunction pyFunction, + @Nullable AbstractPythonTestRunConfiguration configuration + ) { + return PythonUnitTestUtil.isTestCaseFunction(pyFunction); + } + + protected boolean isTestFile(@Nonnull PyFile file) { + List testCases = getTestCaseClassesFromFile(file); + return !testCases.isEmpty(); + } + + protected static boolean isPythonModule(Module module) { + return module != null && ModuleUtilCore.getExtension(module, PyModuleExtension.class) != null; + } + + protected List getTestCaseClassesFromFile(@Nonnull PyFile pyFile) { + return PythonUnitTestUtil.getTestCaseClassesFromFile(pyFile, TypeEvalContext.userInitiated(pyFile.getProject(), pyFile)); + } + + @Override + public boolean isPreferredConfiguration(ConfigurationFromContext self, ConfigurationFromContext other) { + RunConfiguration configuration = self.getConfiguration(); + return configuration instanceof PythonUnitTestRunConfiguration unitTestRunConfiguration + && unitTestRunConfiguration.getTestType() == AbstractPythonTestRunConfiguration.TestType.TEST_FOLDER + || other.isProducedBy(PythonTestConfigurationProducer.class) + || other.isProducedBy(PythonRunConfigurationProducer.class); + } } \ No newline at end of file diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/testing/attest/PythonAtTestConfigurationProducer.java b/python-impl/src/main/java/com/jetbrains/python/impl/testing/attest/PythonAtTestConfigurationProducer.java index 77e2f67f..69dd7ea8 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/testing/attest/PythonAtTestConfigurationProducer.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/testing/attest/PythonAtTestConfigurationProducer.java @@ -21,6 +21,7 @@ import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.types.PyClassLikeType; import com.jetbrains.python.psi.types.TypeEvalContext; +import consulo.annotation.access.RequiredReadAction; import consulo.annotation.component.ExtensionImpl; import consulo.content.bundle.Sdk; import consulo.execution.action.Location; @@ -30,101 +31,91 @@ import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; + import java.util.List; /** * User: catherine */ @ExtensionImpl -public class PythonAtTestConfigurationProducer extends PythonTestConfigurationProducer -{ - public PythonAtTestConfigurationProducer() - { - super(PythonTestConfigurationType.getInstance().PY_ATTEST_FACTORY); - } +public class PythonAtTestConfigurationProducer extends PythonTestConfigurationProducer { + public PythonAtTestConfigurationProducer() { + super(PythonTestConfigurationType.getInstance().PY_ATTEST_FACTORY); + } - protected boolean isAvailable(@Nonnull final Location location) - { - final PsiElement element = location.getPsiElement(); - Module module = location.getModule(); - if(module == null) - { - module = ModuleUtilCore.findModuleForPsiElement(element); - } + @Override + @RequiredReadAction + protected boolean isAvailable(@Nonnull Location location) { + PsiElement element = location.getPsiElement(); + Module module = location.getModule(); + if (module == null) { + module = ModuleUtilCore.findModuleForPsiElement(element); + } - final Sdk sdk = PythonSdkType.findPythonSdk(module); - return module != null && TestRunnerService.getInstance(module).getProjectConfiguration().equals(PythonTestConfigurationsModel.PYTHONS_ATTEST_NAME) && sdk != null; - } + Sdk sdk = PythonSdkType.findPythonSdk(module); + return module != null && TestRunnerService.getInstance(module) + .getProjectConfiguration() + .equals(PythonTestConfigurationsModel.PYTHONS_ATTEST_NAME) && sdk != null; + } - @Override - protected boolean isTestClass(@Nonnull final PyClass pyClass, @Nullable final AbstractPythonTestRunConfiguration configuration, @Nullable final TypeEvalContext context) - { - for(PyClassLikeType type : pyClass.getAncestorTypes(TypeEvalContext.codeInsightFallback(pyClass.getProject()))) - { - if(type != null && "TestBase".equals(type.getName()) && hasTestFunction(pyClass)) - { - return true; - } - } - return false; - } + @Override + protected boolean isTestClass( + @Nonnull PyClass pyClass, + @Nullable AbstractPythonTestRunConfiguration configuration, + @Nullable TypeEvalContext context + ) { + for (PyClassLikeType type : pyClass.getAncestorTypes(TypeEvalContext.codeInsightFallback(pyClass.getProject()))) { + if (type != null && "TestBase".equals(type.getName()) && hasTestFunction(pyClass)) { + return true; + } + } + return false; + } - private static boolean hasTestFunction(@Nonnull final PyClass pyClass) - { - PyFunction[] methods = pyClass.getMethods(); - for(PyFunction function : methods) - { - PyDecoratorList decorators = function.getDecoratorList(); - if(decorators == null) - { - continue; - } - for(PyDecorator decorator : decorators.getDecorators()) - { - if("test".equals(decorator.getName()) || "test_if".equals(decorator.getName())) - { - return true; - } - } - } - return false; - } + private static boolean hasTestFunction(@Nonnull PyClass pyClass) { + PyFunction[] methods = pyClass.getMethods(); + for (PyFunction function : methods) { + PyDecoratorList decorators = function.getDecoratorList(); + if (decorators == null) { + continue; + } + for (PyDecorator decorator : decorators.getDecorators()) { + if ("test".equals(decorator.getName()) || "test_if".equals(decorator.getName())) { + return true; + } + } + } + return false; + } - protected boolean isTestFunction(@Nonnull final PyFunction pyFunction, @Nullable final AbstractPythonTestRunConfiguration configuration) - { - PyDecoratorList decorators = pyFunction.getDecoratorList(); - if(decorators == null) - { - return false; - } - for(PyDecorator decorator : decorators.getDecorators()) - { - if("test".equals(decorator.getName()) || "test_if".equals(decorator.getName())) - { - return true; - } - } - return false; - } + @Override + protected boolean isTestFunction(@Nonnull PyFunction pyFunction, @Nullable AbstractPythonTestRunConfiguration configuration) { + PyDecoratorList decorators = pyFunction.getDecoratorList(); + if (decorators == null) { + return false; + } + for (PyDecorator decorator : decorators.getDecorators()) { + if ("test".equals(decorator.getName()) || "test_if".equals(decorator.getName())) { + return true; + } + } + return false; + } - protected List getTestCaseClassesFromFile(@Nonnull final PyFile file) - { - List result = Lists.newArrayList(); - for(PyClass cls : file.getTopLevelClasses()) - { - if(isTestClass(cls, null, null)) - { - result.add(cls); - } - } + @Override + protected List getTestCaseClassesFromFile(@Nonnull PyFile file) { + List result = Lists.newArrayList(); + for (PyClass cls : file.getTopLevelClasses()) { + if (isTestClass(cls, null, null)) { + result.add(cls); + } + } - for(PyFunction cls : file.getTopLevelFunctions()) - { - if(isTestFunction(cls, null)) - { - result.add(cls); - } - } - return result; - } + for (PyFunction cls : file.getTopLevelFunctions()) { + if (isTestFunction(cls, null)) { + result.add(cls); + } + } + return result; + } } \ No newline at end of file diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/testing/doctest/PythonDocTestConfigurationProducer.java b/python-impl/src/main/java/com/jetbrains/python/impl/testing/doctest/PythonDocTestConfigurationProducer.java index 80189ffa..51045226 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/testing/doctest/PythonDocTestConfigurationProducer.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/testing/doctest/PythonDocTestConfigurationProducer.java @@ -27,6 +27,7 @@ import com.jetbrains.python.psi.PyFile; import com.jetbrains.python.psi.PyFunction; import com.jetbrains.python.psi.types.TypeEvalContext; +import consulo.annotation.access.RequiredReadAction; import consulo.annotation.component.ExtensionImpl; import consulo.execution.action.Location; import consulo.language.psi.PsiElement; @@ -38,86 +39,79 @@ import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; + import java.util.List; @ExtensionImpl -public class PythonDocTestConfigurationProducer extends PythonTestConfigurationProducer -{ - - public PythonDocTestConfigurationProducer() - { - super(PythonTestConfigurationType.getInstance().PY_DOCTEST_FACTORY); - } - - @Override - protected boolean isTestFunction(@Nonnull final PyFunction pyFunction, @Nullable final AbstractPythonTestRunConfiguration configuration) - { - return PythonDocTestUtil.isDocTestFunction(pyFunction); - } +public class PythonDocTestConfigurationProducer extends PythonTestConfigurationProducer { + public PythonDocTestConfigurationProducer() { + super(PythonTestConfigurationType.getInstance().PY_DOCTEST_FACTORY); + } - @Override - protected boolean isTestClass(@Nonnull PyClass pyClass, @Nullable final AbstractPythonTestRunConfiguration configuration, @Nullable final TypeEvalContext context) - { - return PythonDocTestUtil.isDocTestClass(pyClass); - } + @Override + protected boolean isTestFunction( + @Nonnull PyFunction pyFunction, + @Nullable AbstractPythonTestRunConfiguration configuration + ) { + return PythonDocTestUtil.isDocTestFunction(pyFunction); + } - @Override - protected boolean isTestFile(@Nonnull PyFile file) - { - final List testCases = PythonDocTestUtil.getDocTestCasesFromFile(file); - return !testCases.isEmpty(); - } + @Override + protected boolean isTestClass( + @Nonnull PyClass pyClass, + @Nullable AbstractPythonTestRunConfiguration configuration, + @Nullable TypeEvalContext context + ) { + return PythonDocTestUtil.isDocTestClass(pyClass); + } - protected boolean isAvailable(@Nonnull final Location location) - { - final Module module = location.getModule(); - if(!isPythonModule(module)) - { - return false; - } - final PsiElement element = location.getPsiElement(); - if(element instanceof PsiFile) - { - final PyDocTestVisitor visitor = new PyDocTestVisitor(); - element.accept(visitor); - return visitor.hasTests; - } - else - { - return true; - } - } + @Override + protected boolean isTestFile(@Nonnull PyFile file) { + List testCases = PythonDocTestUtil.getDocTestCasesFromFile(file); + return !testCases.isEmpty(); + } - private static class PyDocTestVisitor extends PsiRecursiveElementVisitor - { - boolean hasTests = false; + @Override + protected boolean isAvailable(@Nonnull Location location) { + Module module = location.getModule(); + if (!isPythonModule(module)) { + return false; + } + PsiElement element = location.getPsiElement(); + if (element instanceof PsiFile) { + PyDocTestVisitor visitor = new PyDocTestVisitor(); + element.accept(visitor); + return visitor.hasTests; + } + else { + return true; + } + } - @Override - public void visitFile(PsiFile node) - { - if(node instanceof PyFile) - { - List testClasses = PythonDocTestUtil.getDocTestCasesFromFile((PyFile) node); - if(!testClasses.isEmpty()) - { - hasTests = true; - } - } - else - { - final String text = node.getText(); - if(PythonDocTestUtil.hasExample(text)) - { - hasTests = true; - } - } - } - } + private static class PyDocTestVisitor extends PsiRecursiveElementVisitor { + boolean hasTests = false; - @Override - protected boolean isTestFolder(@Nonnull VirtualFile virtualFile, @Nonnull Project project) - { - return false; - } + @Override + @RequiredReadAction + public void visitFile(PsiFile node) { + if (node instanceof PyFile pyFile) { + List testClasses = PythonDocTestUtil.getDocTestCasesFromFile(pyFile); + if (!testClasses.isEmpty()) { + hasTests = true; + } + } + else { + String text = node.getText(); + if (PythonDocTestUtil.hasExample(text)) { + hasTests = true; + } + } + } + } + @Override + @RequiredReadAction + protected boolean isTestFolder(@Nonnull VirtualFile virtualFile, @Nonnull Project project) { + return false; + } } \ No newline at end of file diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/testing/pytest/PyTestConfigurationProducer.java b/python-impl/src/main/java/com/jetbrains/python/impl/testing/pytest/PyTestConfigurationProducer.java index 5601829b..1e398d04 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/testing/pytest/PyTestConfigurationProducer.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/testing/pytest/PyTestConfigurationProducer.java @@ -15,16 +15,17 @@ */ package com.jetbrains.python.impl.testing.pytest; +import com.jetbrains.python.impl.packaging.PyPackageUtil; +import com.jetbrains.python.impl.sdk.PythonSdkType; import com.jetbrains.python.impl.testing.*; import com.jetbrains.python.packaging.PyPackage; import com.jetbrains.python.packaging.PyPackageManager; -import com.jetbrains.python.impl.packaging.PyPackageUtil; import com.jetbrains.python.psi.PyClass; import com.jetbrains.python.psi.PyFile; import com.jetbrains.python.psi.PyFunction; import com.jetbrains.python.psi.PyStatement; import com.jetbrains.python.psi.types.TypeEvalContext; -import com.jetbrains.python.impl.sdk.PythonSdkType; +import consulo.annotation.access.RequiredReadAction; import consulo.annotation.component.ExtensionImpl; import consulo.content.bundle.Sdk; import consulo.execution.action.ConfigurationContext; @@ -33,164 +34,166 @@ import consulo.language.psi.PsiElement; import consulo.language.psi.PsiFileSystemItem; import consulo.language.psi.util.PsiTreeUtil; -import consulo.language.util.ModuleUtilCore; import consulo.module.Module; import consulo.repository.ui.PackageVersionComparator; import consulo.util.lang.StringUtil; -import consulo.util.lang.ref.Ref; +import consulo.util.lang.ref.SimpleReference; import consulo.virtualFileSystem.VirtualFile; - import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; + import java.io.File; import java.util.List; @ExtensionImpl -public class PyTestConfigurationProducer extends PythonTestConfigurationProducer -{ - - public PyTestConfigurationProducer() { - super(PythonTestConfigurationType.getInstance().PY_PYTEST_FACTORY); - } - - @Override - protected boolean setupConfigurationFromContext(AbstractPythonTestRunConfiguration configuration, - ConfigurationContext context, - Ref sourceElement) { - final PsiElement element = sourceElement.get(); - final Module module = ModuleUtilCore.findModuleForPsiElement(element); - if (!(configuration instanceof PyTestRunConfiguration)) { - return false; - } - if (module == null) { - return false; - } - if (!(TestRunnerService.getInstance(module).getProjectConfiguration().equals(PythonTestConfigurationsModel.PY_TEST_NAME))) { - return false; - } +public class PyTestConfigurationProducer extends PythonTestConfigurationProducer { + public PyTestConfigurationProducer() { + super(PythonTestConfigurationType.getInstance().PY_PYTEST_FACTORY); + } + + @Override + @RequiredReadAction + protected boolean setupConfigurationFromContext( + AbstractPythonTestRunConfiguration configuration, + ConfigurationContext context, + SimpleReference sourceElement + ) { + PsiElement element = sourceElement.get(); + Module module = element.getModule(); + if (!(configuration instanceof PyTestRunConfiguration)) { + return false; + } + if (module == null) { + return false; + } + if (!(TestRunnerService.getInstance(module).getProjectConfiguration().equals(PythonTestConfigurationsModel.PY_TEST_NAME))) { + return false; + } - final PsiFileSystemItem file = element instanceof PsiDirectory ? (PsiDirectory)element : element.getContainingFile(); - if (file == null) { - return false; - } - final VirtualFile virtualFile = file.getVirtualFile(); - if (virtualFile == null) { - return false; - } + PsiFileSystemItem file = element instanceof PsiDirectory directory ? directory : element.getContainingFile(); + if (file == null) { + return false; + } + VirtualFile virtualFile = file.getVirtualFile(); + if (virtualFile == null) { + return false; + } - if (file instanceof PyFile || file instanceof PsiDirectory) { - final List testCases = - PyTestUtil.getPyTestCasesFromFile(file, TypeEvalContext.userInitiated(element.getProject(), element.getContainingFile())); - if (testCases.isEmpty()) { - return false; - } - } - else { - return false; - } + if (file instanceof PyFile || file instanceof PsiDirectory) { + List testCases = + PyTestUtil.getPyTestCasesFromFile(file, TypeEvalContext.userInitiated(element.getProject(), element.getContainingFile())); + if (testCases.isEmpty()) { + return false; + } + } + else { + return false; + } - final Sdk sdk = PythonSdkType.findPythonSdk(context.getModule()); - if (sdk == null) { - return false; - } + Sdk sdk = PythonSdkType.findPythonSdk(context.getModule()); + if (sdk == null) { + return false; + } - configuration.setUseModuleSdk(true); - configuration.setModule(ModuleUtilCore.findModuleForPsiElement(element)); - ((PyTestRunConfiguration)configuration).setTestToRun(virtualFile.getPath()); + configuration.setUseModuleSdk(true); + configuration.setModule(element.getModule()); + ((PyTestRunConfiguration)configuration).setTestToRun(virtualFile.getPath()); - final String keywords = getKeywords(element, sdk); - if (keywords != null) { - ((PyTestRunConfiguration)configuration).useKeyword(true); - ((PyTestRunConfiguration)configuration).setKeywords(keywords); - configuration.setName("py.test in " + keywords); - } - else { - configuration.setName("py.test in " + file.getName()); - } - return true; - } - - @Nullable - private static String getKeywords(@Nonnull final PsiElement element, @Nonnull final Sdk sdk) { - final PyFunction pyFunction = findTestFunction(element); - final PyClass pyClass = PsiTreeUtil.getParentOfType(element, PyClass.class, false); - String keywords = null; - if (pyFunction != null) { - keywords = pyFunction.getName(); - if (pyClass != null) { - final List packages = PyPackageManager.getInstance(sdk).getPackages(); - final PyPackage pytestPackage = packages != null ? PyPackageUtil.findPackage(packages, "pytest") : null; - if (pytestPackage != null && PackageVersionComparator.VERSION_COMPARATOR.compare( - pytestPackage.getVersion(), - "2.3.3") >= 0) { - keywords = pyClass.getName() + " and " + keywords; + String keywords = getKeywords(element, sdk); + if (keywords != null) { + ((PyTestRunConfiguration)configuration).useKeyword(true); + ((PyTestRunConfiguration)configuration).setKeywords(keywords); + configuration.setName("py.test in " + keywords); } else { - keywords = pyClass.getName() + "." + keywords; + configuration.setName("py.test in " + file.getName()); } - } - } - else if (pyClass != null) { - keywords = pyClass.getName(); - } - return keywords; - } - - @Nullable - private static PyFunction findTestFunction(PsiElement element) { - final PyFunction function = PsiTreeUtil.getParentOfType(element, PyFunction.class); - if (function != null) { - final String name = function.getName(); - if (name != null && name.startsWith("test")) { - return function; - } - } - return null; - } - - @Override - public boolean isConfigurationFromContext(AbstractPythonTestRunConfiguration configuration, ConfigurationContext context) { - final Location location = context.getLocation(); - if (location == null) { - return false; - } - if (!(configuration instanceof PyTestRunConfiguration)) { - return false; + return true; + } + + @Nullable + @RequiredReadAction + private static String getKeywords(@Nonnull PsiElement element, @Nonnull Sdk sdk) { + PyFunction pyFunction = findTestFunction(element); + PyClass pyClass = PsiTreeUtil.getParentOfType(element, PyClass.class, false); + String keywords = null; + if (pyFunction != null) { + keywords = pyFunction.getName(); + if (pyClass != null) { + List packages = PyPackageManager.getInstance(sdk).getPackages(); + PyPackage pytestPackage = packages != null ? PyPackageUtil.findPackage(packages, "pytest") : null; + if (pytestPackage != null + && PackageVersionComparator.VERSION_COMPARATOR.compare(pytestPackage.getVersion(), "2.3.3") >= 0) { + keywords = pyClass.getName() + " and " + keywords; + } + else { + keywords = pyClass.getName() + "." + keywords; + } + } + } + else if (pyClass != null) { + keywords = pyClass.getName(); + } + return keywords; + } + + @Nullable + @RequiredReadAction + private static PyFunction findTestFunction(PsiElement element) { + PyFunction function = PsiTreeUtil.getParentOfType(element, PyFunction.class); + if (function != null) { + String name = function.getName(); + if (name != null && name.startsWith("test")) { + return function; + } + } + return null; } - final PsiElement element = location.getPsiElement(); - final PsiFileSystemItem file = element instanceof PsiDirectory ? (PsiDirectory)element : element.getContainingFile(); - if (file == null) { - return false; - } - final VirtualFile virtualFile = file.getVirtualFile(); - if (virtualFile == null) { - return false; - } + @Override + @RequiredReadAction + public boolean isConfigurationFromContext(AbstractPythonTestRunConfiguration configuration, ConfigurationContext context) { + Location location = context.getLocation(); + if (location == null) { + return false; + } + if (!(configuration instanceof PyTestRunConfiguration)) { + return false; + } + PsiElement element = location.getPsiElement(); - if (file instanceof PyFile || file instanceof PsiDirectory) { - final List testCases = - PyTestUtil.getPyTestCasesFromFile(file, TypeEvalContext.userInitiated(element.getProject(), element.getContainingFile())); - if (testCases.isEmpty()) { - return false; - } - } - else { - return false; - } + PsiFileSystemItem file = element instanceof PsiDirectory directory ? directory : element.getContainingFile(); + if (file == null) { + return false; + } + VirtualFile virtualFile = file.getVirtualFile(); + if (virtualFile == null) { + return false; + } + + if (file instanceof PyFile || file instanceof PsiDirectory) { + List testCases = + PyTestUtil.getPyTestCasesFromFile(file, TypeEvalContext.userInitiated(element.getProject(), element.getContainingFile())); + if (testCases.isEmpty()) { + return false; + } + } + else { + return false; + } + + Sdk sdk = PythonSdkType.findPythonSdk(context.getModule()); + if (sdk == null) { + return false; + } + String keywords = getKeywords(element, sdk); + String scriptName = ((PyTestRunConfiguration)configuration).getTestToRun(); + String workingDirectory = configuration.getWorkingDirectory(); + String path = virtualFile.getPath(); + boolean isTestFileEquals = scriptName.equals(path) || path.equals(new File(workingDirectory, scriptName).getAbsolutePath()); - final Sdk sdk = PythonSdkType.findPythonSdk(context.getModule()); - if (sdk == null) { - return false; + String configurationKeywords = ((PyTestRunConfiguration)configuration).getKeywords(); + return isTestFileEquals && (configurationKeywords.equals(keywords) + || StringUtil.isEmptyOrSpaces(((PyTestRunConfiguration)configuration).getKeywords()) && keywords == null); } - final String keywords = getKeywords(element, sdk); - final String scriptName = ((PyTestRunConfiguration)configuration).getTestToRun(); - final String workingDirectory = configuration.getWorkingDirectory(); - final String path = virtualFile.getPath(); - final boolean isTestFileEquals = scriptName.equals(path) || path.equals(new File(workingDirectory, scriptName).getAbsolutePath()); - - final String configurationKeywords = ((PyTestRunConfiguration)configuration).getKeywords(); - return isTestFileEquals && (configurationKeywords.equals(keywords) || StringUtil.isEmptyOrSpaces(((PyTestRunConfiguration)configuration) - .getKeywords()) && keywords == null); - } } \ No newline at end of file diff --git a/python-impl/src/main/java/com/jetbrains/python/impl/testing/unittest/PythonUnitTestConfigurationProducer.java b/python-impl/src/main/java/com/jetbrains/python/impl/testing/unittest/PythonUnitTestConfigurationProducer.java index a92c39e7..3a252d93 100644 --- a/python-impl/src/main/java/com/jetbrains/python/impl/testing/unittest/PythonUnitTestConfigurationProducer.java +++ b/python-impl/src/main/java/com/jetbrains/python/impl/testing/unittest/PythonUnitTestConfigurationProducer.java @@ -27,65 +27,61 @@ import com.jetbrains.python.psi.PyFunction; import com.jetbrains.python.psi.PyStatement; import com.jetbrains.python.psi.types.TypeEvalContext; +import consulo.annotation.access.RequiredReadAction; import consulo.annotation.component.ExtensionImpl; import consulo.execution.action.Location; import consulo.language.psi.PsiElement; -import consulo.language.util.ModuleUtilCore; import consulo.module.Module; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; + import java.util.List; @ExtensionImpl -public class PythonUnitTestConfigurationProducer extends PythonTestConfigurationProducer -{ - public PythonUnitTestConfigurationProducer() - { - super(PythonTestConfigurationType.getInstance().PY_UNITTEST_FACTORY); - } +public class PythonUnitTestConfigurationProducer extends PythonTestConfigurationProducer { + public PythonUnitTestConfigurationProducer() { + super(PythonTestConfigurationType.getInstance().PY_UNITTEST_FACTORY); + } - protected boolean isAvailable(@Nonnull final Location location) - { - PsiElement element = location.getPsiElement(); - final Module module = ModuleUtilCore.findModuleForPsiElement(element); - if(module == null) - { - return false; - } - if((TestRunnerService.getInstance(module).getProjectConfiguration().equals(PythonTestConfigurationsModel.PYTHONS_UNITTEST_NAME))) - { - return true; - } - return false; - } + @Override + @RequiredReadAction + protected boolean isAvailable(@Nonnull final Location location) { + PsiElement element = location.getPsiElement(); + final Module module = element.getModule(); + return module != null && TestRunnerService.getInstance(module) + .getProjectConfiguration() + .equals(PythonTestConfigurationsModel.PYTHONS_UNITTEST_NAME); + } - @Override - protected boolean isTestFunction(@Nonnull final PyFunction pyFunction, @Nullable final AbstractPythonTestRunConfiguration configuration) - { - final boolean isTestFunction = super.isTestFunction(pyFunction, configuration); - return isTestFunction || (configuration instanceof PythonUnitTestRunConfiguration && !((PythonUnitTestRunConfiguration) configuration).isPureUnittest()); - } + @Override + protected boolean isTestFunction( + @Nonnull final PyFunction pyFunction, + @Nullable final AbstractPythonTestRunConfiguration configuration + ) { + final boolean isTestFunction = super.isTestFunction(pyFunction, configuration); + return isTestFunction + || (configuration instanceof PythonUnitTestRunConfiguration unitTestRunConfig && !unitTestRunConfig.isPureUnittest()); + } - @Override - protected boolean isTestClass(@Nonnull PyClass pyClass, @Nullable final AbstractPythonTestRunConfiguration configuration, TypeEvalContext context) - { - final boolean isTestClass = super.isTestClass(pyClass, configuration, context); - return isTestClass || (configuration instanceof PythonUnitTestRunConfiguration && !((PythonUnitTestRunConfiguration) configuration).isPureUnittest()); - } + @Override + protected boolean isTestClass( + @Nonnull PyClass pyClass, + @Nullable final AbstractPythonTestRunConfiguration configuration, + TypeEvalContext context + ) { + final boolean isTestClass = super.isTestClass(pyClass, configuration, context); + return isTestClass + || (configuration instanceof PythonUnitTestRunConfiguration unitTestRunConfig && !unitTestRunConfig.isPureUnittest()); + } - @Override - protected boolean isTestFile(@Nonnull final PyFile file) - { - if(PyNames.SETUP_DOT_PY.equals(file.getName())) - { - return true; - } - final List testCases = getTestCaseClassesFromFile(file); - if(testCases.isEmpty()) - { - return false; - } - return true; - } + @Override + @RequiredReadAction + protected boolean isTestFile(@Nonnull final PyFile file) { + if (PyNames.SETUP_DOT_PY.equals(file.getName())) { + return true; + } + final List testCases = getTestCaseClassesFromFile(file); + return !testCases.isEmpty(); + } } \ No newline at end of file