PY-48009 Add automatic indent inside match statements

Namely, for their case clauses and inner comments.

Right now, each of them is indented on its own, as a separate formatting block.
It's still not entirely clear whether we should have a dedicated indented
container element for case clauses, similar to PyStatementList for statements.
It might simplify the formatter and some editing actions, but cause confusion
between the two container elements.

GitOrigin-RevId: 69184d2f8f78e2e113e8f40a310bb13ac0b5e71a
This commit is contained in:
Mikhail Golubev
2021-05-05 15:28:50 +03:00
committed by intellij-monorepo-bot
parent 8a40ce95d7
commit 98d196aec6
19 changed files with 135 additions and 4 deletions

View File

@@ -217,6 +217,9 @@ public class PyBlock implements ASTBlock {
childIndent = Indent.getNormalIndent();
}
}
else if (childType == PyElementTypes.CASE_CLAUSE) {
childIndent = Indent.getNormalIndent();
}
else if (childType == PyElementTypes.IMPORT_ELEMENT) {
if (parentType == PyElementTypes.FROM_IMPORT_STATEMENT) {
childWrap = myFromImportWrapping;
@@ -226,7 +229,8 @@ public class PyBlock implements ASTBlock {
}
childIndent = Indent.getNormalIndent();
}
if (childType == PyTokenTypes.END_OF_LINE_COMMENT && parentType == PyElementTypes.FROM_IMPORT_STATEMENT) {
if (childType == PyTokenTypes.END_OF_LINE_COMMENT && (parentType == PyElementTypes.FROM_IMPORT_STATEMENT ||
parentType == PyElementTypes.MATCH_STATEMENT)) {
childIndent = Indent.getNormalIndent();
}
@@ -1058,6 +1062,7 @@ public class PyBlock implements ASTBlock {
@NotNull
private Indent getChildIndent(int newChildIndex) {
final IElementType parentType = myNode.getElementType();
final ASTNode afterNode = getAfterNode(newChildIndex);
final ASTNode lastChild = getLastNonSpaceChild(myNode, false);
if (lastChild != null && lastChild.getElementType() == PyElementTypes.STATEMENT_LIST && mySubBlocks.size() >= newChildIndex) {
@@ -1078,6 +1083,9 @@ public class PyBlock implements ASTBlock {
return Indent.getNormalIndent();
}
}
if (parentType == PyElementTypes.MATCH_STATEMENT && afterNode != null && afterNode.getElementType() == PyTokenTypes.COLON) {
return Indent.getNormalIndent();
}
if (afterNode != null && afterNode.getElementType() == PyElementTypes.KEY_VALUE_EXPRESSION) {
final PyKeyValueExpression keyValue = (PyKeyValueExpression)afterNode.getPsi();
@@ -1086,7 +1094,6 @@ public class PyBlock implements ASTBlock {
}
}
final IElementType parentType = myNode.getElementType();
// constructs that imply indent for their children
final PyCodeStyleSettings settings = myContext.getPySettings();
if ((parentType == PyElementTypes.PARAMETER_LIST && settings.USE_CONTINUATION_INDENT_FOR_PARAMETERS) ||

View File

@@ -260,12 +260,12 @@ public class PythonEnterHandler extends EnterHandlerDelegateAdapter {
@Nullable
private static PsiElement findStatementBeforeCaret(ASTNode node) {
return findBeforeCaret(node, PyStatement.class);
return findBeforeCaret(node, PyStatement.class, PyStatementPart.class);
}
@Nullable
private static PsiElement findStatementAfterCaret(ASTNode node) {
return findAfterCaret(node, PyStatement.class);
return findAfterCaret(node, PyStatement.class, PyStatementPart.class);
}
private static PsiElement findBeforeCaret(ASTNode atCaret, Class<? extends PsiElement>... classes) {

View File

@@ -0,0 +1,4 @@
match x:
case 42:
<caret>
pass

View File

@@ -0,0 +1,3 @@
match x:
case 42:<caret>
pass

View File

@@ -0,0 +1,3 @@
match x:
case 42:
<caret>

View File

@@ -0,0 +1,2 @@
match x:
case 42:<caret>

View File

@@ -0,0 +1,4 @@
match x:
<caret>
case 42:
pass

View File

@@ -0,0 +1,3 @@
match x:<caret>
case 42:
pass

View File

@@ -0,0 +1,2 @@
match x:
<caret>

View File

@@ -0,0 +1 @@
match x:<caret>

View File

@@ -0,0 +1,5 @@
match x:
case 1:
pass
case 2:
pass

View File

@@ -0,0 +1,5 @@
match x:
case 1:
pass
case 2:
pass

View File

@@ -0,0 +1,12 @@
match x:
# first 1
# first 2
case 1:
pass
# intermediate 1
# intermediate 2
case 2:
pass
# trailing 1
# trailing 2
# unrelated

View File

@@ -0,0 +1,12 @@
match x:
# first 1
# first 2
case 1:
pass
# intermediate 1
# intermediate 2
case 2:
pass
# trailing 1
# trailing 2
# unrelated

View File

@@ -0,0 +1,7 @@
match x:
# match leading comment
case 1:
pass
# case trailing comment
# match trailing comment
# unrelated comment

View File

@@ -0,0 +1,27 @@
PyFile:PatternMatchingLeadingAndTrailingComments.py
PyMatchStatement
PsiElement(Py:MATCH_KEYWORD)('match')
PsiWhiteSpace(' ')
PyReferenceExpression: x
PsiElement(Py:IDENTIFIER)('x')
PsiElement(Py:COLON)(':')
PsiWhiteSpace('\n ')
PsiComment(Py:END_OF_LINE_COMMENT)('# match leading comment')
PsiWhiteSpace('\n ')
PyCaseClause
PsiElement(Py:CASE_KEYWORD)('case')
PsiWhiteSpace(' ')
PyLiteralPattern
PyNumericLiteralExpression
PsiElement(Py:INTEGER_LITERAL)('1')
PsiElement(Py:COLON)(':')
PsiWhiteSpace('\n ')
PyStatementList
PyPassStatement
PsiElement(Py:PASS_KEYWORD)('pass')
PsiWhiteSpace('\n ')
PsiComment(Py:END_OF_LINE_COMMENT)('# case trailing comment')
PsiWhiteSpace('\n ')
PsiComment(Py:END_OF_LINE_COMMENT)('# match trailing comment')
PsiWhiteSpace('\n')
PsiComment(Py:END_OF_LINE_COMMENT)('# unrelated comment')

View File

@@ -832,6 +832,26 @@ public class PyEditingTest extends PyTestCase {
"s = f'{42:<caret>:3d}'");
}
// PY-48009
public void testEnterAfterColonOfMatchStatementWithoutClauses() {
doTypingTest('\n');
}
// PY-48009
public void testEnterAfterColonOfMatchStatementWithClauses() {
doTypingTest('\n');
}
// PY-48009
public void testEnterAfterColonOfCaseClauseWithoutBody() {
doTypingTest('\n');
}
// PY-48009
public void testEnterAfterColonOfCaseClauseWithBody() {
doTypingTest('\n');
}
@NotNull
private PyCodeStyleSettings getPythonCodeStyleSettings() {
return getCodeStyleSettings().getCustomSettings(PyCodeStyleSettings.class);

View File

@@ -1073,4 +1073,14 @@ public class PyFormatterTest extends PyTestCase {
public void testModuleLevelDunderWithImports() {
doTest();
}
// PY-48009
public void testIndentOfCaseClausesInsideMatchStatement() {
doTest();
}
// PY-48009
public void testIndentOfCommentsInsideMatchStatement() {
doTest();
}
}

View File

@@ -1139,6 +1139,10 @@ public class PythonParsingTest extends ParsingTestCase {
doTest(LanguageLevel.PYTHON310);
}
public void testPatternMatchingLeadingAndTrailingComments() {
doTest(LanguageLevel.PYTHON310);
}
public void doTest() {
doTest(LanguageLevel.PYTHON26);
}