mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-18 09:34:34 +07:00
PY-25001 Implement Complete Current Statement for collection literals
It works by automatically inserting a trailing comma before a line break in multiline collection literals. Additionally, a colon is inserted between a key and a value in dict literals. Because of the language ambiguity regarding syntax of some incomplete collection literals, colon is not inserted after the first key of a dict literal (it's indistinguishable from a set literal with one item), and comma is not inserted after the first item of a parenthesized tuple (it's indistinguishable from a parenthesized expression). The idea was taken from such implementation of Complete Current Statement for JSON. GitOrigin-RevId: 49ff9857d8c12476bfa9aacaed0b49faa99810fd
This commit is contained in:
committed by
intellij-monorepo-bot
parent
3ec7d70b0d
commit
16eebe034e
@@ -98,7 +98,7 @@ public final class PyPsiUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds first non-whitespace sibling after given PSI element but stops at first whitespace containing line feed.
|
||||
* Returns the first non-whitespace sibling following the given element but within its line boundaries.
|
||||
*/
|
||||
@Nullable
|
||||
public static PsiElement getNextNonWhitespaceSiblingOnSameLine(@NotNull PsiElement element) {
|
||||
@@ -115,6 +115,24 @@ public final class PyPsiUtils {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first non-whitespace sibling preceding the given element but within its line boundaries.
|
||||
*/
|
||||
@Nullable
|
||||
public static PsiElement getPrevNonWhitespaceSiblingOnSameLine(@NotNull PsiElement element) {
|
||||
PsiElement cur = element.getPrevSibling();
|
||||
while (cur != null) {
|
||||
if (!(cur instanceof PsiWhiteSpace)) {
|
||||
return cur;
|
||||
}
|
||||
else if (cur.textContains('\n')) {
|
||||
break;
|
||||
}
|
||||
cur = cur.getPrevSibling();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds first non-whitespace sibling after given AST node.
|
||||
*/
|
||||
|
||||
+1
@@ -48,6 +48,7 @@ public class PySmartEnterProcessor extends SmartEnterProcessor {
|
||||
.add(new PyFunctionFixer())
|
||||
.add(new PyClassFixer())
|
||||
.add(new PyWithFixer())
|
||||
.add(new PyCollectionLiteralFixer())
|
||||
.build();
|
||||
private static final List<EnterProcessor> ourProcessors = ImmutableList.of(new PyCommentBreakerEnterProcessor(),
|
||||
new PyPlainEnterProcessor());
|
||||
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
// 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.
|
||||
package com.jetbrains.python.codeInsight.editorActions.smartEnter.fixers;
|
||||
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.jetbrains.python.PyPsiBundle;
|
||||
import com.jetbrains.python.codeInsight.editorActions.smartEnter.PySmartEnterProcessor;
|
||||
import com.jetbrains.python.codeInsight.editorActions.smartEnter.SmartEnterUtil;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import com.jetbrains.python.psi.impl.PyPsiUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import static com.jetbrains.python.psi.PyUtil.as;
|
||||
|
||||
public class PyCollectionLiteralFixer extends PyFixer<PySequenceExpression> {
|
||||
public PyCollectionLiteralFixer() {
|
||||
super(PySequenceExpression.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isApplicable(@NotNull Editor editor, @NotNull PySequenceExpression element) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doApply(@NotNull Editor editor, @NotNull PySmartEnterProcessor processor, @NotNull PySequenceExpression collection) {
|
||||
int caretOffset = editor.getCaretModel().getOffset();
|
||||
PsiElement collectionItemAnchor = collection.getContainingFile().findElementAt(caretOffset);
|
||||
assert collectionItemAnchor != null;
|
||||
if (collectionItemAnchor instanceof PsiWhiteSpace) {
|
||||
collectionItemAnchor = PyPsiUtils.getPrevNonWhitespaceSiblingOnSameLine(collectionItemAnchor);
|
||||
}
|
||||
if (collectionItemAnchor != null && isMissingColonError(collectionItemAnchor)) {
|
||||
collectionItemAnchor = collectionItemAnchor.getPrevSibling();
|
||||
}
|
||||
// Since surrounding parentheses don't belong to PyTupleExpression expression,
|
||||
// whitespace after its last element is a sibling of the tuple itself
|
||||
if (collection instanceof PyTupleExpression && collectionItemAnchor == collection) {
|
||||
collectionItemAnchor = ArrayUtil.getLastElement(collection.getElements());
|
||||
}
|
||||
if (collectionItemAnchor == null) {
|
||||
return;
|
||||
}
|
||||
PsiElement collectionItem = PyPsiUtils.getParentRightBefore(collectionItemAnchor, collection);
|
||||
if (!(collectionItem instanceof PyExpression)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Document document = editor.getDocument();
|
||||
int collectionItemLastLine = document.getLineNumber(collectionItem.getTextRange().getEndOffset());
|
||||
int caretLine = document.getLineNumber(caretOffset);
|
||||
if (collectionItemLastLine != caretLine) {
|
||||
return;
|
||||
}
|
||||
|
||||
PsiElement nextOnSameLine = PyPsiUtils.getNextNonWhitespaceSiblingOnSameLine(collectionItem);
|
||||
if (nextOnSameLine != null && isMissingColonError(nextOnSameLine)) {
|
||||
nextOnSameLine = PyPsiUtils.getNextNonWhitespaceSiblingOnSameLine(nextOnSameLine);
|
||||
}
|
||||
if (nextOnSameLine == null || nextOnSameLine instanceof PsiComment) {
|
||||
int separatorOffset = collectionItem.getTextRange().getEndOffset();
|
||||
PyKeyValueExpression keyValuePair = as(collectionItem, PyKeyValueExpression.class);
|
||||
if (collection instanceof PyDictLiteralExpression && keyValuePair == null && !(collectionItem instanceof PyDoubleStarExpression)) {
|
||||
document.insertString(separatorOffset, ": ");
|
||||
processor.registerUnresolvedError(separatorOffset + 2);
|
||||
}
|
||||
// Our parser can't handle "'key': ," sequence
|
||||
else if (!(keyValuePair != null && keyValuePair.getValue() == null)) {
|
||||
document.insertString(separatorOffset, ",");
|
||||
editor.getCaretModel().moveToOffset(separatorOffset + 1);
|
||||
SmartEnterUtil.plainEnter(editor);
|
||||
// The default behavior is to move the caret after the containing statement, unless other caretOffset is specified explicitly
|
||||
processor.registerUnresolvedError(editor.getCaretModel().getOffset());
|
||||
// Forcibly commit the document to prevent other fixers and enter processors from being run
|
||||
PsiDocumentManager.getInstance(collection.getProject()).commitDocument(document);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isMissingColonError(@NotNull PsiElement element) {
|
||||
PsiErrorElement errorElement = as(element, PsiErrorElement.class);
|
||||
return errorElement != null && PyPsiBundle.message("PARSE.expected.colon").equals(errorElement.getErrorDescription());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
def func(**kwargs):
|
||||
d = {
|
||||
**kwargs<caret>
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
def func(**kwargs):
|
||||
d = {
|
||||
**kwargs,
|
||||
<caret>
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
d = {
|
||||
'key': <caret>
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
d = {
|
||||
'key':
|
||||
<caret>
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
d = {
|
||||
'key1': 1,
|
||||
'key2'<caret>
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
d = {
|
||||
'key1': 1,
|
||||
'key2': <caret>
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
d = {
|
||||
'key1': 1,
|
||||
'key2<caret>'
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
d = {
|
||||
'key1': 1,
|
||||
'key2': <caret>
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
d = {
|
||||
'key': <caret>
|
||||
42
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
d = {
|
||||
'key':
|
||||
<caret>
|
||||
42
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
d = {
|
||||
'key': 42<caret>
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
d = {
|
||||
'key': 42,
|
||||
<caret>
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
xs = [
|
||||
1<caret>
|
||||
]
|
||||
@@ -0,0 +1,4 @@
|
||||
xs = [
|
||||
1,
|
||||
<caret>
|
||||
]
|
||||
@@ -0,0 +1,3 @@
|
||||
xs = [
|
||||
<caret>
|
||||
]
|
||||
@@ -0,0 +1,4 @@
|
||||
xs = [
|
||||
|
||||
<caret>
|
||||
]
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
xs = [
|
||||
42 # com<caret>ment
|
||||
]
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
xs = [
|
||||
42 # comment
|
||||
# <caret>
|
||||
]
|
||||
@@ -0,0 +1,3 @@
|
||||
xs = [
|
||||
# com<caret>ment
|
||||
]
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
xs = [
|
||||
# comment
|
||||
# <caret>
|
||||
]
|
||||
@@ -0,0 +1,3 @@
|
||||
xs = [
|
||||
4<caret>2
|
||||
]
|
||||
@@ -0,0 +1,4 @@
|
||||
xs = [
|
||||
42,
|
||||
<caret>
|
||||
]
|
||||
@@ -0,0 +1,5 @@
|
||||
xs = [
|
||||
oct(
|
||||
42<caret>
|
||||
)
|
||||
]
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
xs = [
|
||||
oct(
|
||||
42
|
||||
<caret>
|
||||
)
|
||||
]
|
||||
@@ -0,0 +1,3 @@
|
||||
xs = [
|
||||
42<caret> # comment
|
||||
]
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
xs = [
|
||||
42, # comment
|
||||
<caret>
|
||||
]
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
xs = ['foo'<caret>
|
||||
]
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
xs = ['foo',
|
||||
<caret>
|
||||
]
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
xs = [
|
||||
oct(
|
||||
42
|
||||
<caret>)
|
||||
]
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
xs = [
|
||||
oct(
|
||||
42
|
||||
),
|
||||
<caret>
|
||||
]
|
||||
@@ -0,0 +1,3 @@
|
||||
xs = {
|
||||
1<caret>
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
xs = {
|
||||
1,
|
||||
<caret>
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
xs = (
|
||||
1,
|
||||
2<caret>
|
||||
3
|
||||
)
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
xs = (
|
||||
1,
|
||||
2,
|
||||
<caret>
|
||||
3
|
||||
)
|
||||
@@ -0,0 +1,4 @@
|
||||
xs = (
|
||||
1,
|
||||
2 <caret>
|
||||
)
|
||||
@@ -0,0 +1,5 @@
|
||||
xs = (
|
||||
1,
|
||||
2,
|
||||
<caret>
|
||||
)
|
||||
@@ -117,6 +117,96 @@ public class PySmartEnterTest extends PyTestCase {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-25001
|
||||
public void testMultilineListLiteralInsideItem() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-25001
|
||||
public void testMultilineListLiteralAfterItem() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-25001
|
||||
public void testMultilineListLiteralInsideMultilineItem() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-25001
|
||||
public void testMultilineListLiteralOnLastLineOfMultilineItem() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-25001
|
||||
public void testMultilineListLiteralInsideCommentFollowingItem() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-25001
|
||||
public void testMultilineListLiteralEmptyLine() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-25001
|
||||
public void testMultilineListLiteralInsideCommentedLine() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-25001
|
||||
public void testMultilineListLiteralItemFollowedByComment() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-25001
|
||||
public void testMultilineListLiteralItemFollowsOpeningBracket() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-25001
|
||||
public void testMultilineSetLiteral() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-25001
|
||||
public void testMultilineTupleLiteralLastElement() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-25001
|
||||
public void testMultilineTupleLiteralIntermediateElement() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-25001
|
||||
public void testMultilineDictLiteralAfterKeyWithoutColon() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-25001
|
||||
public void testMultilineDictLiteralInsideKeyWithoutColon() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-25001
|
||||
public void testMultilineDictLiteralAfterKeyWithColonButNoValue() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-25001
|
||||
public void testMultilineDictLiteralValueOnSameLine() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-25001
|
||||
public void testMultilineDictLiteralValueOnOtherLine() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-25001
|
||||
public void testMultilineDictAfterUnpacking() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testDocRest() {
|
||||
CodeInsightSettings codeInsightSettings = CodeInsightSettings.getInstance();
|
||||
boolean oldStubOnEnter = codeInsightSettings.JAVADOC_STUB_ON_ENTER;
|
||||
|
||||
Reference in New Issue
Block a user