mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-06 03:21:12 +07:00
PY-20138 Use existing indent of pasted fragment if caret is at first column
Unless this indentation is going to break existing block structure, e.g. by splitting the containing function in the middle. Additionally I improved detection of an empty statement list when the caret is at the end of file.
This commit is contained in:
@@ -118,10 +118,11 @@ public class PythonCopyPasteProcessor implements CopyPastePreProcessor {
|
||||
if (PsiTreeUtil.getParentOfType(element, PyStringLiteralExpression.class) != null) return text;
|
||||
|
||||
text = addLeadingSpacesToNormalizeSelection(project, text);
|
||||
final String indentText = getIndentText(file, document, caretOffset, lineNumber);
|
||||
final String fragmentIndent = PyIndentUtil.findCommonIndent(text, false);
|
||||
final String newIndent = inferBestIndent(file, document, caretOffset, lineNumber, fragmentIndent);
|
||||
|
||||
final String line = document.getText(TextRange.create(lineStartOffset, lineEndOffset));
|
||||
if (StringUtil.isEmptyOrSpaces(indentText) && shouldPasteOnPreviousLine(file, text, caretOffset)) {
|
||||
if (StringUtil.isEmptyOrSpaces(newIndent) && shouldPasteOnPreviousLine(file, text, caretOffset)) {
|
||||
caretModel.moveToOffset(lineStartOffset);
|
||||
editor.getSelectionModel().setSelection(lineStartOffset, selectionModel.getSelectionEnd());
|
||||
|
||||
@@ -131,8 +132,8 @@ public class PythonCopyPasteProcessor implements CopyPastePreProcessor {
|
||||
}
|
||||
|
||||
String newText;
|
||||
if (StringUtil.isEmptyOrSpaces(indentText)) {
|
||||
newText = PyIndentUtil.changeIndent(text, false, indentText);
|
||||
if (StringUtil.isEmptyOrSpaces(newIndent)) {
|
||||
newText = PyIndentUtil.changeIndent(text, false, newIndent);
|
||||
}
|
||||
else {
|
||||
newText = text;
|
||||
@@ -177,10 +178,11 @@ public class PythonCopyPasteProcessor implements CopyPastePreProcessor {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String getIndentText(@NotNull final PsiFile file,
|
||||
@NotNull final Document document,
|
||||
int caretOffset,
|
||||
int lineNumber) {
|
||||
private static String inferBestIndent(@NotNull PsiFile file,
|
||||
@NotNull Document document,
|
||||
int caretOffset,
|
||||
int lineNumber,
|
||||
@NotNull String fragmentIndent) {
|
||||
|
||||
PsiElement nonWS = PyUtil.findNextAtOffset(file, caretOffset, PsiWhiteSpace.class);
|
||||
if (nonWS != null) {
|
||||
@@ -196,37 +198,44 @@ public class PythonCopyPasteProcessor implements CopyPastePreProcessor {
|
||||
return PyIndentUtil.getLineIndent(document, lineNumber);
|
||||
}
|
||||
|
||||
int lineStartOffset = getLineStartSafeOffset(document, lineNumber);
|
||||
final int lineStartOffset = getLineStartSafeOffset(document, lineNumber);
|
||||
final PsiElement ws = file.findElementAt(lineStartOffset);
|
||||
final String userIndent = document.getText(TextRange.create(lineStartOffset, caretOffset));
|
||||
if (ws != null) {
|
||||
PyStatementList statementList = findEmptyStatementListNearby(ws);
|
||||
if (statementList != null) {
|
||||
return PyIndentUtil.getElementIndent(statementList);
|
||||
}
|
||||
|
||||
final PyStatementList statementList = findEmptyStatementListNearby(file, lineStartOffset);
|
||||
if (statementList != null) {
|
||||
return PyIndentUtil.getElementIndent(statementList);
|
||||
}
|
||||
|
||||
final String smallestIndent = PyIndentUtil.getElementIndent(ws);
|
||||
final PyStatementListContainer parentBlock = PsiTreeUtil.getParentOfType(ws, PyStatementListContainer.class);
|
||||
final PyStatementListContainer deepestBlock = getDeepestPossibleParentBlock(ws);
|
||||
final String greatestIndent;
|
||||
if (deepestBlock != null && (parentBlock == null || PsiTreeUtil.isAncestor(parentBlock, deepestBlock, true))) {
|
||||
greatestIndent = PyIndentUtil.getElementIndent(deepestBlock.getStatementList());
|
||||
}
|
||||
else {
|
||||
greatestIndent = smallestIndent;
|
||||
}
|
||||
if (smallestIndent.startsWith(userIndent)) {
|
||||
return smallestIndent;
|
||||
}
|
||||
if (userIndent.startsWith(greatestIndent)) {
|
||||
return greatestIndent;
|
||||
}
|
||||
final String smallestIndent = ws == null? "" : PyIndentUtil.getElementIndent(ws);
|
||||
final PyStatementListContainer parentBlock = PsiTreeUtil.getParentOfType(ws, PyStatementListContainer.class);
|
||||
final PyStatementListContainer deepestBlock = getDeepestPossibleParentBlock(file, caretOffset);
|
||||
final String greatestIndent;
|
||||
if (deepestBlock != null && (parentBlock == null || PsiTreeUtil.isAncestor(parentBlock, deepestBlock, true))) {
|
||||
greatestIndent = PyIndentUtil.getElementIndent(deepestBlock.getStatementList());
|
||||
}
|
||||
else {
|
||||
greatestIndent = smallestIndent;
|
||||
}
|
||||
if (caretOffset == lineStartOffset && fragmentIndent.startsWith(smallestIndent) && greatestIndent.startsWith(fragmentIndent)) {
|
||||
return fragmentIndent;
|
||||
}
|
||||
if (smallestIndent.startsWith(userIndent)) {
|
||||
return smallestIndent;
|
||||
}
|
||||
if (userIndent.startsWith(greatestIndent)) {
|
||||
return greatestIndent;
|
||||
}
|
||||
return userIndent;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PyStatementList findEmptyStatementListNearby(@NotNull PsiElement whitespace) {
|
||||
private static PyStatementList findEmptyStatementListNearby(@NotNull PsiFile file, int offset) {
|
||||
final PsiWhiteSpace whitespace = findWhitespaceAtCaret(file, offset);
|
||||
if (whitespace == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
PyStatementList statementList = ObjectUtils.chooseNotNull(as(whitespace.getNextSibling(), PyStatementList.class),
|
||||
as(whitespace.getPrevSibling(), PyStatementList.class));
|
||||
if (statementList == null) {
|
||||
@@ -239,7 +248,16 @@ public class PythonCopyPasteProcessor implements CopyPastePreProcessor {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PyStatementListContainer getDeepestPossibleParentBlock(@NotNull PsiElement whitespace) {
|
||||
private static PsiWhiteSpace findWhitespaceAtCaret(@NotNull PsiFile file, int offset) {
|
||||
return as(file.findElementAt(offset == file.getTextLength() && offset > 0 ? offset - 1 : offset), PsiWhiteSpace.class);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PyStatementListContainer getDeepestPossibleParentBlock(@NotNull PsiFile file, int offset) {
|
||||
final PsiWhiteSpace whitespace = findWhitespaceAtCaret(file, offset);
|
||||
if (whitespace == null) {
|
||||
return null;
|
||||
}
|
||||
final PsiElement prevLeaf = getPrevNonCommentLeaf(whitespace);
|
||||
return PsiTreeUtil.getParentOfType(prevLeaf, PyStatementListContainer.class);
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ import com.intellij.psi.PsiWhiteSpace;
|
||||
import com.intellij.psi.codeStyle.CodeStyleSettings;
|
||||
import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.util.Function;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.jetbrains.python.PythonFileType;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
@@ -215,6 +214,11 @@ public class PyIndentUtil {
|
||||
return result;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static String findCommonIndent(@NotNull String s, boolean ignoreFirstLine) {
|
||||
return findCommonIndent(LineTokenizer.tokenizeIntoList(s, false, false), ignoreFirstLine);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds maximum common indentation of the given lines. Indentation of empty lines and lines containing only whitespaces is ignored unless
|
||||
* they're the only lines provided. In the latter case common indentation for such lines is returned. If mix of tabs and spaces was used
|
||||
|
||||
@@ -7,10 +7,7 @@ def foo():
|
||||
|
||||
bar(7)
|
||||
|
||||
|
||||
def bar(num):
|
||||
for _ in range(num):
|
||||
print 'bar'
|
||||
|
||||
|
||||
bar(7)
|
||||
def bar(num):
|
||||
for _ in range(num):
|
||||
print 'bar'
|
||||
bar(7)
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
def f():
|
||||
x = 42
|
||||
@@ -0,0 +1,2 @@
|
||||
def f():
|
||||
<caret>
|
||||
@@ -0,0 +1 @@
|
||||
<selection>x = 42</selection>
|
||||
@@ -7,10 +7,8 @@ def foo():
|
||||
|
||||
bar(7)
|
||||
|
||||
def bar(num):
|
||||
for _ in range(num):
|
||||
print 'bar'
|
||||
|
||||
def bar(num):
|
||||
for _ in range(num):
|
||||
print 'bar'
|
||||
|
||||
|
||||
bar(7)
|
||||
bar(7)
|
||||
|
||||
@@ -6,10 +6,7 @@ def foo():
|
||||
print 'bar'
|
||||
bar(7)
|
||||
|
||||
|
||||
def bar(num):
|
||||
for _ in range(num):
|
||||
print 'bar'
|
||||
|
||||
|
||||
bar(7)
|
||||
def bar(num):
|
||||
for _ in range(num):
|
||||
print 'bar'
|
||||
bar(7)
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
def f():
|
||||
def g():
|
||||
a = 1
|
||||
a = 1
|
||||
b = 2
|
||||
|
||||
b = 2
|
||||
@@ -0,0 +1,5 @@
|
||||
def f():
|
||||
def g():
|
||||
a = 1
|
||||
<caret>
|
||||
b = 2
|
||||
@@ -0,0 +1,3 @@
|
||||
<selection>a = 1
|
||||
b = 2
|
||||
</selection>
|
||||
@@ -0,0 +1,7 @@
|
||||
def foo():
|
||||
a = 1
|
||||
b = 2
|
||||
a = 1
|
||||
b = 2
|
||||
|
||||
x = 42
|
||||
@@ -0,0 +1,5 @@
|
||||
def foo():
|
||||
a = 1
|
||||
b = 2
|
||||
<caret>
|
||||
x = 42
|
||||
@@ -0,0 +1,5 @@
|
||||
def foo():
|
||||
<selection> a = 1
|
||||
b = 2
|
||||
</selection>
|
||||
x = 42
|
||||
@@ -0,0 +1,5 @@
|
||||
def foo():
|
||||
a = 1
|
||||
b = 2
|
||||
a = 1
|
||||
b = 2
|
||||
@@ -0,0 +1,4 @@
|
||||
def foo():
|
||||
a = 1
|
||||
b = 2
|
||||
<caret>
|
||||
@@ -0,0 +1,4 @@
|
||||
def foo():
|
||||
<selection> a = 1
|
||||
b = 2
|
||||
</selection>
|
||||
@@ -2,9 +2,7 @@ class C:
|
||||
def foo(self):
|
||||
x = 1
|
||||
y = 2
|
||||
|
||||
|
||||
def foo(self):
|
||||
x = 1
|
||||
y = 2
|
||||
def foo(self):
|
||||
x = 1
|
||||
y = 2
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ class C:
|
||||
x = 1
|
||||
y = 2
|
||||
|
||||
|
||||
x = 1
|
||||
x = 1
|
||||
def foo():
|
||||
pass
|
||||
@@ -393,6 +393,10 @@ public class PyCopyPasteTest extends PyTestCase {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testEmptyFunctionCaretAtEndOfFile() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-19053
|
||||
public void testSimpleExpressionPartCaretAtLineEnd() {
|
||||
doTest();
|
||||
@@ -442,4 +446,19 @@ public class PyCopyPasteTest extends PyTestCase {
|
||||
public void testAsyncFunctionWithBadSelection() {
|
||||
runWithLanguageLevel(LanguageLevel.PYTHON35, this::doTest);
|
||||
}
|
||||
|
||||
// PY-20138
|
||||
public void testUseExistingIndentWhenCaretAtFirstColumn() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-20138
|
||||
public void testUseExistingIndentWhenCaretAtFirstColumnEndOfFile() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-20138
|
||||
public void testInvalidExistingIndentWhenCaretAtFirstColumn() {
|
||||
doTest();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user