move python formatter extensions to python.psi.impl module

GitOrigin-RevId: 452346cbd12e9c21c636dc8dbcab9a29813bc67b
This commit is contained in:
Petr Golubev
2022-11-03 02:01:38 +01:00
committed by intellij-monorepo-bot
parent 77da7682df
commit 68f080743c
24 changed files with 249 additions and 199 deletions

View File

@@ -9,5 +9,7 @@
serviceImplementation="com.intellij.util.download.impl.DownloadableFileServiceImpl"/>
<applicationService serviceInterface="com.intellij.notification.NotificationGroupManager"
serviceImplementation="com.intellij.notification.impl.NotificationGroupManagerImpl"/>
<applicationService serviceImplementation="com.intellij.ide.GeneralSettings" preload="notHeadless"/>
<applicationService serviceImplementation="com.intellij.openapi.editor.ex.EditorSettingsExternalizable"/>
</extensions>
</idea-plugin>

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.ide;
import com.intellij.ide.ui.UINumericRange;
@@ -8,6 +8,7 @@ import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.*;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.util.ObjectUtils;
import com.intellij.util.PlatformUtils;
import com.intellij.util.xmlb.XmlSerializerUtil;
@@ -42,7 +43,7 @@ public final class GeneralSettings implements PersistentStateComponent<GeneralSe
private static final String SHOW_TIPS_ON_STARTUP_DEFAULT_VALUE_PROPERTY = "ide.show.tips.on.startup.default.value";
private String myBrowserPath = BrowserUtil.getDefaultAlternativeBrowserPath();
private String myBrowserPath = getDefaultAlternativeBrowserPath();
private boolean myShowTipsOnStartup = Boolean.parseBoolean(System.getProperty(SHOW_TIPS_ON_STARTUP_DEFAULT_VALUE_PROPERTY, "true"));
private boolean myReopenLastProject = true;
private boolean mySupportScreenReaders = ObjectUtils.chooseNotNull(SUPPORT_SCREEN_READERS_OVERRIDDEN, false);
@@ -281,4 +282,19 @@ public final class GeneralSettings implements PersistentStateComponent<GeneralSe
public void setDefaultProjectDirectory(@SystemDependent String defaultProjectDirectory) {
myDefaultProjectDirectory = defaultProjectDirectory;
}
private static @NotNull String getDefaultAlternativeBrowserPath() {
if (SystemInfo.isWindows) {
return "C:\\Program Files\\Internet Explorer\\IExplore.exe";
}
else if (SystemInfo.isMac) {
return "open";
}
else if (SystemInfo.isUnix) {
return "/usr/bin/firefox";
}
else {
return "";
}
}
}

View File

@@ -126,21 +126,6 @@ public final class BrowserUtil {
return command;
}
public static @NotNull String getDefaultAlternativeBrowserPath() {
if (SystemInfo.isWindows) {
return "C:\\Program Files\\Internet Explorer\\IExplore.exe";
}
else if (SystemInfo.isMac) {
return "open";
}
else if (SystemInfo.isUnix) {
return "/usr/bin/firefox";
}
else {
return "";
}
}
//<editor-fold desc="Deprecated stuff.">
/** @deprecated the result is a pain to deal with; please use {@link #getOpenBrowserCommand(String, String, List, boolean)} instead */
@Deprecated(forRemoval = true)

View File

@@ -160,8 +160,6 @@
<virtualFileSystem implementationClass="com.intellij.openapi.vfs.impl.jar.JarFileSystemImpl" key="jar" physical="true"/>
<virtualFileSystem implementationClass="com.intellij.openapi.vfs.ex.temp.TempFileSystem" key="temp" physical="true"/>
<applicationService serviceImplementation="com.intellij.ide.GeneralSettings" preload="notHeadless"/>
<applicationService serviceImplementation="com.intellij.ui.jcef.JBCefStartup" preload="notHeadless" os="mac"/>
<cachesInvalidator implementation="com.intellij.ui.jcef.JBCefAppCacheInvalidator" order="last" />
@@ -638,7 +636,6 @@
<applicationService serviceInterface="com.intellij.openapi.actionSystem.AbbreviationManager"
serviceImplementation="com.intellij.openapi.actionSystem.impl.AbbreviationManagerImpl"/>
<applicationService serviceImplementation="com.intellij.openapi.editor.ex.EditorSettingsExternalizable"/>
<applicationService serviceImplementation="com.intellij.openapi.editor.ex.EditorSettingsExternalizable$OsSpecificState"/>
<applicationConfigurable
groupId="editor"

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python;
import com.intellij.formatting.WrapType;
@@ -7,15 +7,16 @@ import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.util.PsiTreeUtil;
import com.jetbrains.python.fixtures.PyTestCase;
import com.jetbrains.python.fixture.PythonCommonTestCase;
import com.jetbrains.python.formatter.PyCodeStyleSettings;
import com.jetbrains.python.psi.LanguageLevel;
import com.jetbrains.python.psi.PyElementGenerator;
import com.jetbrains.python.psi.PyStatement;
import org.jetbrains.annotations.NotNull;
public abstract class PythonCommonFormatterTest extends PythonCommonTestCase {
protected static final LanguageLevel LANGUAGE_LEVEL = LanguageLevel.getLatest();
public class PyFormatterTest extends PyTestCase {
@NotNull
private PyCodeStyleSettings getPythonCodeStyleSettings() {
return getCodeStyleSettings().getCustomSettings(PyCodeStyleSettings.class);
@@ -522,7 +523,7 @@ public class PyFormatterTest extends PyTestCase {
public void testAlignmentOfClosingBraceInDictLiteralWhenNoHangingIndent() {
doTest();
}
public void testNoAlignmentClosingBraceInDictLiteralWhenOpeningBraceIsForcedOnNewLine() {
getPythonCodeStyleSettings().DICT_NEW_LINE_AFTER_LEFT_BRACE = true;
doTest();
@@ -671,7 +672,7 @@ public class PyFormatterTest extends PyTestCase {
getPythonCodeStyleSettings().DICT_NEW_LINE_BEFORE_RIGHT_BRACE = true;
doTest();
}
public void testForceNewLineAfterLeftParenInMethodParameters() { // PY-33060
getCommonCodeStyleSettings().METHOD_PARAMETERS_LPAREN_ON_NEXT_LINE = true;
doTest();
@@ -714,7 +715,7 @@ public class PyFormatterTest extends PyTestCase {
public void testHangingIndentDetectionIgnoresComments() {
doTest();
}
// PY-15530
public void testAlignmentInArgumentListWhereFirstArgumentIsEmptyCall() {
doTest();
@@ -814,7 +815,7 @@ public class PyFormatterTest extends PyTestCase {
doTest();
}
// PY-10182
// PY-10182
public void testHangClosingParenthesisInFunctionDefinition() {
getPythonCodeStyleSettings().HANG_CLOSING_BRACKETS = true;
doTest();
@@ -831,7 +832,7 @@ public class PyFormatterTest extends PyTestCase {
getPythonCodeStyleSettings().HANG_CLOSING_BRACKETS = false;
doTest();
}
// PY-15874
public void testHangClosingOnComprehensionsAndGeneratorExpressions() {
getPythonCodeStyleSettings().HANG_CLOSING_BRACKETS = true;
@@ -853,7 +854,7 @@ public class PyFormatterTest extends PyTestCase {
getCodeStyleSettings().setRightMargin(PythonLanguage.INSTANCE, 35);
doTest();
}
// PY-20633
public void testFromImportParenthesesPlacementHangClosingParenthesis() {
getPythonCodeStyleSettings().FROM_IMPORT_NEW_LINE_AFTER_LEFT_PARENTHESIS = true;
@@ -1157,17 +1158,17 @@ public class PyFormatterTest extends PyTestCase {
getPythonCodeStyleSettings().HANG_CLOSING_BRACKETS = true;
doTest();
}
// PY-48009
public void testItemAlignmentInMappingPatterns() {
doTest();
}
// PY-48009
public void testItemIndentInMappingPatterns() {
doTest();
}
// PY-48009
public void testHangingClosingBracketInMappingPatterns() {
getPythonCodeStyleSettings().HANG_CLOSING_BRACKETS = true;

View File

@@ -26,6 +26,8 @@ interface PythonCommonCodeInsightTestFixture {
val testRootDisposable: Disposable
val caretOffset: Int
fun setUp() {
}

View File

@@ -1,5 +1,6 @@
package com.jetbrains.python.fixture;
import com.intellij.application.options.CodeStyle;
import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.projectRoots.SdkModificator;
@@ -8,6 +9,9 @@ import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiFile;
import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
import com.jetbrains.python.PythonLanguage;
import com.jetbrains.python.documentation.PyDocumentationSettings;
import com.jetbrains.python.documentation.docstrings.DocStringFormat;
import com.jetbrains.python.psi.LanguageLevel;
@@ -90,6 +94,21 @@ public abstract class PythonCommonTestCase extends TestCase {
}
}
@NotNull
protected CommonCodeStyleSettings getCommonCodeStyleSettings() {
return getCodeStyleSettings().getCommonSettings(PythonLanguage.getInstance());
}
@NotNull
protected CodeStyleSettings getCodeStyleSettings() {
return CodeStyle.getSettings(myFixture.getProject());
}
@NotNull
protected CommonCodeStyleSettings.IndentOptions getIndentOptions() {
return getCommonCodeStyleSettings().getIndentOptions();
}
protected void assertProjectFilesNotParsed(@NotNull PsiFile currentFile) {
assertRootNotParsed(currentFile, myFixture.getTempDirRoot(), null);
}

View File

@@ -18,5 +18,7 @@
<orderEntry type="library" scope="TEST" name="Guava" level="project" />
<orderEntry type="library" scope="TEST" name="StreamEx" level="project" />
<orderEntry type="module" module-name="intellij.platform.analysis.impl" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.codeStyle" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.editor" scope="TEST" />
</component>
</module>

View File

@@ -51,6 +51,14 @@
implementationClass="com.jetbrains.python.psi.impl.references.PyKeywordPatternManipulator"/>
<lang.parserDefinition language="Python" implementationClass="com.jetbrains.python.PythonParserDefinition"/>
<lang.formatter language="Python" implementationClass="com.jetbrains.python.formatter.PythonFormattingModelBuilder"/>
<preFormatProcessor implementation="com.jetbrains.python.formatter.PyPreFormatProcessor"/>
<postFormatProcessor implementation="com.jetbrains.python.formatter.PyTrailingBlankLinesPostFormatProcessor"/>
<postFormatProcessor implementation="com.jetbrains.python.formatter.PyFromImportPostFormatProcessor"/>
<lang.whiteSpaceFormattingStrategy language="Python"
implementationClass="com.jetbrains.python.formatter.PyWhiteSpaceFormattingStrategy"/>
<lang.lineWrapStrategy language="Python" implementationClass="com.jetbrains.python.formatter.PyLineWrapPositionStrategy"/>
<!-- PyFunctionTypeAnnotation -->
<lang.parserDefinition language="PyFunctionTypeComment"
implementationClass="com.jetbrains.python.codeInsight.functionTypeComments.PyFunctionTypeAnnotationParserDefinition"/>

View File

@@ -0,0 +1,166 @@
package com.jetbrains.python.editor;
import com.intellij.lang.ASTNode;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.tree.TreeUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.jetbrains.python.PyTokenTypes;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyPsiUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class PyWhiteSpaceUtil {
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) {
if (PsiTreeUtil.getParentOfType(nodeAtCaret.getPsi(), PyFStringFragment.class) != null) {
return false;
}
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 (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
}
final int offset = nodeAtCaret.getTextRange().getStartOffset();
if (inFromImportParentheses(statementBefore, offset) || inWithItemsParentheses(statementBefore, offset)) {
return false;
}
PsiElement wrappableBefore = findWrappable(nodeAtCaret, true);
PsiElement wrappableAfter = findWrappable(nodeAtCaret, false);
if (!(wrappableBefore instanceof PsiComment)) {
while (wrappableBefore != null) {
PsiElement next = PsiTreeUtil.getParentOfType(wrappableBefore, PyEditorHandlerConfig.WRAPPABLE_CLASSES);
if (next == null) {
break;
}
wrappableBefore = next;
}
}
if (!(wrappableAfter instanceof PsiComment)) {
while (wrappableAfter != null) {
PsiElement next = PsiTreeUtil.getParentOfType(wrappableAfter, PyEditorHandlerConfig.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;
}
@Nullable
private static PsiElement findWrappable(ASTNode nodeAtCaret, boolean before) {
PsiElement wrappable = before
? findBeforeCaret(nodeAtCaret, PyEditorHandlerConfig.WRAPPABLE_CLASSES)
: findAfterCaret(nodeAtCaret, PyEditorHandlerConfig.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;
}
@Nullable
private static PsiElement findStatementBeforeCaret(ASTNode node) {
return findBeforeCaret(node, PyStatement.class, PyStatementPart.class);
}
@Nullable
private static PsiElement findStatementAfterCaret(ASTNode node) {
return findAfterCaret(node, PyStatement.class, PyStatementPart.class);
}
private static PsiElement findBeforeCaret(ASTNode atCaret, Class<? extends PsiElement>... classes) {
while (atCaret != null) {
atCaret = TreeUtil.prevLeaf(atCaret);
if (atCaret != null && atCaret.getElementType() != TokenType.WHITE_SPACE) {
return getNonStrictParentOfType(atCaret.getPsi(), classes);
}
}
return null;
}
private static PsiElement findAfterCaret(ASTNode atCaret, Class<? extends PsiElement>... classes) {
while (atCaret != null) {
if (atCaret.getElementType() != TokenType.WHITE_SPACE) {
return getNonStrictParentOfType(atCaret.getPsi(), classes);
}
atCaret = TreeUtil.nextLeaf(atCaret);
}
return null;
}
@Nullable
private static <T extends PsiElement> T getNonStrictParentOfType(@NotNull PsiElement element, Class<? extends T> @NotNull ... classes) {
PsiElement run = element;
while (run != null) {
for (Class<? extends T> aClass : classes) {
if (aClass.isInstance(run)) return (T)run;
}
if (run instanceof PsiFile || run instanceof PyStatementList) break;
run = run.getParent();
}
return null;
}
private static boolean inFromImportParentheses(PsiElement statement, int offset) {
if (!(statement instanceof PyFromImportStatement)) {
return false;
}
PyFromImportStatement fromImportStatement = (PyFromImportStatement)statement;
PsiElement leftParen = fromImportStatement.getLeftParen();
if (leftParen != null && offset >= leftParen.getTextRange().getEndOffset()) {
return true;
}
return false;
}
private static boolean inWithItemsParentheses(@NotNull PsiElement statement, int offset) {
if (!(statement instanceof PyWithStatement)) {
return false;
}
final PsiElement leftParen = PyPsiUtils.getFirstChildOfType(statement, PyTokenTypes.LPAR);
return leftParen != null && offset >= leftParen.getTextRange().getEndOffset();
}
}

View File

@@ -6,7 +6,7 @@ import com.intellij.openapi.util.text.Strings;
import com.intellij.psi.PsiElement;
import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.psi.formatter.StaticSymbolWhiteSpaceDefinitionStrategy;
import com.jetbrains.python.editor.PythonEnterHandler;
import com.jetbrains.python.editor.PyWhiteSpaceUtil;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntIterator;
@@ -25,7 +25,7 @@ public class PyWhiteSpaceFormattingStrategy extends StaticSymbolWhiteSpaceDefini
CodeStyleSettings codeStyleSettings) {
CharSequence whiteSpace = super.adjustWhiteSpaceIfNecessary(whiteSpaceText, startElement, startOffset, endOffset, codeStyleSettings);
if (whiteSpace.length() > 0 && whiteSpace.charAt(0) == '\n' && !Strings.contains(whiteSpace, 0, whiteSpace.length(), '\\') &&
PythonEnterHandler.needInsertBackslash(startElement.getContainingFile(), startOffset, false)) {
PyWhiteSpaceUtil.needInsertBackslash(startElement.getContainingFile(), startOffset, false)) {
return addBackslashPrefix(whiteSpace, codeStyleSettings);
}
return whiteSpace;
@@ -60,7 +60,7 @@ public class PyWhiteSpaceFormattingStrategy extends StaticSymbolWhiteSpaceDefini
Int2IntMap initialBackSlashes = countBackSlashes(text, startOffset, endOffset);
if (initialBackSlashes.isEmpty()) {
if (nodeAfter != null && whiteSpaceText.length() > 0 && whiteSpaceText.charAt(0) == '\n' &&
PythonEnterHandler.needInsertBackslash(nodeAfter, false)) {
PyWhiteSpaceUtil.needInsertBackslash(nodeAfter, false)) {
return addBackslashPrefix(whiteSpaceText, codeStyleSettings);
}
return whiteSpaceText;

View File

@@ -46,13 +46,6 @@
<lang.commenter language="Python" implementationClass="com.jetbrains.python.PythonCommenter"/>
<lang.foldingBuilder language="Python" implementationClass="com.jetbrains.python.PythonFoldingBuilder"/>
<lang.findUsagesProvider language="Python" implementationClass="com.jetbrains.python.findUsages.PythonFindUsagesProvider"/>
<lang.formatter language="Python" implementationClass="com.jetbrains.python.formatter.PythonFormattingModelBuilder"/>
<preFormatProcessor implementation="com.jetbrains.python.formatter.PyPreFormatProcessor"/>
<postFormatProcessor implementation="com.jetbrains.python.formatter.PyTrailingBlankLinesPostFormatProcessor"/>
<postFormatProcessor implementation="com.jetbrains.python.formatter.PyFromImportPostFormatProcessor"/>
<lang.whiteSpaceFormattingStrategy language="Python"
implementationClass="com.jetbrains.python.formatter.PyWhiteSpaceFormattingStrategy"/>
<lang.lineWrapStrategy language="Python" implementationClass="com.jetbrains.python.formatter.PyLineWrapPositionStrategy"/>
<lang.namesValidator language="Python" implementationClass="com.jetbrains.python.refactoring.rename.PythonNamesValidator"/>
<lang.surroundDescriptor language="Python"
implementationClass="com.jetbrains.python.refactoring.surround.PyStatementSurroundDescriptor"/>

View File

@@ -17,7 +17,6 @@ import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.LineTokenizer;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.tree.TreeUtil;
import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiTreeUtil;
@@ -25,9 +24,7 @@ import com.jetbrains.python.PyTokenTypes;
import com.jetbrains.python.codeInsight.PyCodeInsightSettings;
import com.jetbrains.python.documentation.docstrings.*;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyPsiUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.regex.Matcher;
@@ -145,85 +142,13 @@ public class PythonEnterHandler extends EnterHandlerDelegateAdapter {
Document doc) {
boolean autoWrapInProgress = DataManager.getInstance().loadFromDataContext(dataContext,
AutoHardWrapHandler.AUTO_WRAP_LINE_IN_PROGRESS_KEY) != null;
if (needInsertBackslash(file, offset, autoWrapInProgress)) {
if (PyWhiteSpaceUtil.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) {
if (PsiTreeUtil.getParentOfType(nodeAtCaret.getPsi(), PyFStringFragment.class) != null) {
return false;
}
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 (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
}
final int offset = nodeAtCaret.getTextRange().getStartOffset();
if (inFromImportParentheses(statementBefore, offset) || inWithItemsParentheses(statementBefore, offset)) {
return false;
}
PsiElement wrappableBefore = findWrappable(nodeAtCaret, true);
PsiElement wrappableAfter = findWrappable(nodeAtCaret, false);
if (!(wrappableBefore instanceof PsiComment)) {
while (wrappableBefore != null) {
PsiElement next = PsiTreeUtil.getParentOfType(wrappableBefore, PyEditorHandlerConfig.WRAPPABLE_CLASSES);
if (next == null) {
break;
}
wrappableBefore = next;
}
}
if (!(wrappableAfter instanceof PsiComment)) {
while (wrappableAfter != null) {
PsiElement next = PsiTreeUtil.getParentOfType(wrappableAfter, PyEditorHandlerConfig.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) {
@@ -244,87 +169,6 @@ public class PythonEnterHandler extends EnterHandlerDelegateAdapter {
}
}
@Nullable
private static PsiElement findWrappable(ASTNode nodeAtCaret, boolean before) {
PsiElement wrappable = before
? findBeforeCaret(nodeAtCaret, PyEditorHandlerConfig.WRAPPABLE_CLASSES)
: findAfterCaret(nodeAtCaret, PyEditorHandlerConfig.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;
}
@Nullable
private static PsiElement findStatementBeforeCaret(ASTNode node) {
return findBeforeCaret(node, PyStatement.class, PyStatementPart.class);
}
@Nullable
private static PsiElement findStatementAfterCaret(ASTNode node) {
return findAfterCaret(node, PyStatement.class, PyStatementPart.class);
}
private static PsiElement findBeforeCaret(ASTNode atCaret, Class<? extends PsiElement>... classes) {
while (atCaret != null) {
atCaret = TreeUtil.prevLeaf(atCaret);
if (atCaret != null && atCaret.getElementType() != TokenType.WHITE_SPACE) {
return getNonStrictParentOfType(atCaret.getPsi(), classes);
}
}
return null;
}
private static PsiElement findAfterCaret(ASTNode atCaret, Class<? extends PsiElement>... classes) {
while (atCaret != null) {
if (atCaret.getElementType() != TokenType.WHITE_SPACE) {
return getNonStrictParentOfType(atCaret.getPsi(), classes);
}
atCaret = TreeUtil.nextLeaf(atCaret);
}
return null;
}
@Nullable
private static <T extends PsiElement> T getNonStrictParentOfType(@NotNull PsiElement element, Class<? extends T> @NotNull ... classes) {
PsiElement run = element;
while (run != null) {
for (Class<? extends T> aClass : classes) {
if (aClass.isInstance(run)) return (T)run;
}
if (run instanceof PsiFile || run instanceof PyStatementList) break;
run = run.getParent();
}
return null;
}
private static boolean inFromImportParentheses(PsiElement statement, int offset) {
if (!(statement instanceof PyFromImportStatement)) {
return false;
}
PyFromImportStatement fromImportStatement = (PyFromImportStatement)statement;
PsiElement leftParen = fromImportStatement.getLeftParen();
if (leftParen != null && offset >= leftParen.getTextRange().getEndOffset()) {
return true;
}
return false;
}
private static boolean inWithItemsParentheses(@NotNull PsiElement statement, int offset) {
if (!(statement instanceof PyWithStatement)) {
return false;
}
final PsiElement leftParen = PyPsiUtils.getFirstChildOfType(statement, PyTokenTypes.LPAR);
return leftParen != null && offset >= leftParen.getTextRange().getEndOffset();
}
@Override
public Result postProcessEnter(@NotNull PsiFile file,
@NotNull Editor editor,

View File

@@ -0,0 +1,13 @@
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.python;
import com.jetbrains.python.fixture.PythonCommonCodeInsightTestFixture;
import com.jetbrains.python.fixtures.PythonPlatformCodeInsightTestFixture;
import com.jetbrains.python.psi.LanguageLevel;
public class PythonFormatterTest extends PythonCommonFormatterTest {
@Override
protected PythonCommonCodeInsightTestFixture getFixture() {
return new PythonPlatformCodeInsightTestFixture(LANGUAGE_LEVEL);
}
}

View File

@@ -42,6 +42,8 @@ class PythonPlatformCodeInsightTestFixture(languageLevel: LanguageLevel) : Pytho
get() = requireNotNull(myDelegateFixture.tempDirFixture.getFile("."))
override val testRootDisposable: Disposable
get() = myDelegateFixture.testRootDisposable
override val caretOffset: Int
get() = myDelegateFixture.caretOffset
override fun setUp() {
super.setUp()