mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IDEA-67496 Regression: Reformat on paste: Indent Block is not working when clipboard starts with comment
Indent adjustment algorithm is corrected for 'indent fragile' environment (e.g. Python source)
This commit is contained in:
+140
-16
@@ -38,7 +38,7 @@ class Test {
|
||||
|
||||
def toPaste =
|
||||
'''\
|
||||
foo();
|
||||
foo();
|
||||
foo();\
|
||||
'''
|
||||
|
||||
@@ -48,7 +48,7 @@ class Test {
|
||||
void test() {
|
||||
if (true) {
|
||||
foo();
|
||||
foo();
|
||||
foo();
|
||||
}
|
||||
}
|
||||
}\
|
||||
@@ -56,6 +56,48 @@ class Test {
|
||||
doTest(before, toPaste, expected)
|
||||
}
|
||||
|
||||
void testJavaComplexBlockWithDecreasedIndent() {
|
||||
def before = '''\
|
||||
class Test {
|
||||
void test() {
|
||||
if (true) {
|
||||
i = 1;
|
||||
} else {
|
||||
i = 2;
|
||||
}
|
||||
<caret>
|
||||
}
|
||||
}\
|
||||
'''
|
||||
|
||||
def toPaste =
|
||||
'''\
|
||||
if (true) {
|
||||
i = 1;
|
||||
} else {
|
||||
i = 2;
|
||||
}\
|
||||
'''
|
||||
|
||||
def expected = '''\
|
||||
class Test {
|
||||
void test() {
|
||||
if (true) {
|
||||
i = 1;
|
||||
} else {
|
||||
i = 2;
|
||||
}
|
||||
if (true) {
|
||||
i = 1;
|
||||
} else {
|
||||
i = 2;
|
||||
}
|
||||
}
|
||||
}\
|
||||
'''
|
||||
doTest(before, toPaste, expected)
|
||||
}
|
||||
|
||||
void testJavaBlockIncreasedIndentOnTwoLinesPasting() {
|
||||
def before = '''\
|
||||
class Test {
|
||||
@@ -70,7 +112,7 @@ class Test {
|
||||
def toPaste =
|
||||
'''\
|
||||
foo();
|
||||
foo();\
|
||||
foo();\
|
||||
'''
|
||||
|
||||
|
||||
@@ -79,7 +121,7 @@ class Test {
|
||||
void test() {
|
||||
if (true) {
|
||||
foo();
|
||||
foo();
|
||||
foo();
|
||||
}
|
||||
}
|
||||
}\
|
||||
@@ -100,7 +142,7 @@ class Test {
|
||||
|
||||
def toPaste =
|
||||
'''\
|
||||
foo();
|
||||
foo();
|
||||
foo();\
|
||||
'''
|
||||
|
||||
@@ -110,7 +152,7 @@ class Test {
|
||||
void test() {
|
||||
if (true) {
|
||||
foo();
|
||||
foo();
|
||||
foo();
|
||||
}
|
||||
}
|
||||
}\
|
||||
@@ -131,7 +173,7 @@ class Test {
|
||||
|
||||
def toPaste =
|
||||
'''\
|
||||
foo();
|
||||
foo();
|
||||
foo();
|
||||
foo();\
|
||||
'''
|
||||
@@ -175,8 +217,8 @@ class Test {
|
||||
void test() {
|
||||
if (true) {
|
||||
foo();
|
||||
foo();
|
||||
foo();
|
||||
foo();
|
||||
foo();
|
||||
}
|
||||
}
|
||||
}\
|
||||
@@ -229,20 +271,20 @@ class Test {
|
||||
|
||||
def toPaste =
|
||||
'''\
|
||||
// this is a comment
|
||||
foo();
|
||||
foo();
|
||||
foo();\
|
||||
// this is a comment
|
||||
foo();
|
||||
foo();
|
||||
foo();\
|
||||
'''
|
||||
|
||||
|
||||
def expected = '''\
|
||||
class Test {
|
||||
void test() {
|
||||
if (true) { // this is a comment
|
||||
foo();
|
||||
if (true) {// this is a comment
|
||||
foo();
|
||||
foo();
|
||||
foo();
|
||||
foo();
|
||||
}
|
||||
}
|
||||
}\
|
||||
@@ -250,6 +292,88 @@ class Test {
|
||||
doTest(before, toPaste, expected)
|
||||
}
|
||||
|
||||
void testPasteAtZeroColumnAfterBlankLineWithWhiteSpaces() {
|
||||
def before = '''\
|
||||
class Test {
|
||||
void test() {
|
||||
if (true) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<caret>\
|
||||
'''
|
||||
|
||||
def toPaste =
|
||||
'''\
|
||||
class Test {
|
||||
void test() {
|
||||
if (true) {
|
||||
}
|
||||
}
|
||||
}\
|
||||
'''
|
||||
|
||||
|
||||
def expected = '''\
|
||||
class Test {
|
||||
void test() {
|
||||
if (true) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Test {
|
||||
void test() {
|
||||
if (true) {
|
||||
}
|
||||
}
|
||||
}\
|
||||
'''
|
||||
doTest(before, toPaste, expected)
|
||||
}
|
||||
|
||||
void testPasteAtNonZeroColumnAfterBlankLineWithWhiteSpaces() {
|
||||
def before = '''\
|
||||
class Test {
|
||||
void test() {
|
||||
if (true) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<caret>\
|
||||
'''
|
||||
|
||||
def toPaste =
|
||||
'''\
|
||||
class Test {
|
||||
void test() {
|
||||
if (true) {
|
||||
}
|
||||
}
|
||||
}\
|
||||
'''
|
||||
|
||||
|
||||
def expected = '''\
|
||||
class Test {
|
||||
void test() {
|
||||
if (true) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Test {
|
||||
void test() {
|
||||
if (true) {
|
||||
}
|
||||
}
|
||||
}\
|
||||
'''
|
||||
doTest(before, toPaste, expected)
|
||||
}
|
||||
|
||||
def testPlainTextPaste() {
|
||||
def before = '''\
|
||||
line1
|
||||
|
||||
@@ -352,74 +352,102 @@ public class PasteHandler extends EditorActionHandler {
|
||||
}
|
||||
|
||||
private static void indentBlockWithFormatter(Project project, Document document, int startOffset, int endOffset, PsiFile file) {
|
||||
|
||||
// Algorithm: the main idea is to process the first line of the pasted block, adjust its indent if necessary, calculate indent
|
||||
// adjustment string and apply to each line of the pasted block starting from the second one.
|
||||
//
|
||||
// We differentiate three possible states here:
|
||||
// --- pasted block doesn't start new line, i.e. there are non-white space symbols before it at the first line.
|
||||
// Example:
|
||||
// old content [pasted line 1
|
||||
// pasted line 2]
|
||||
// Indent adjustment string is just the first line indent then.
|
||||
//
|
||||
// --- pasted block is located at the new line and starts with white space symbols.
|
||||
// Example:
|
||||
// [ pasted line 1
|
||||
// pasted line 2]
|
||||
// We parse existing indents of the pasted block then, adjust its first line via formatter and adjust indent of the pasted lines
|
||||
// starting from the second one in order to preserve old indentation.
|
||||
//
|
||||
// --- pasted block is located at the new line but doesn't start with white space symbols.
|
||||
// Example:
|
||||
// [pasted line 1
|
||||
// pasted line 2]
|
||||
// We adjust the first line via formatter then and apply first line's indent to all subsequent pasted lines.
|
||||
|
||||
CharSequence chars = document.getCharsSequence();
|
||||
final int firstLine = document.getLineNumber(startOffset);
|
||||
final int firstLineStart = document.getLineStartOffset(firstLine);
|
||||
final int lastLine = document.getLineNumber(endOffset);
|
||||
final int i = CharArrayUtil.shiftBackward(chars, startOffset - 1, " \t");
|
||||
|
||||
// Handle situation when pasted block doesn't start new line.
|
||||
if (chars.charAt(i) != '\n') {
|
||||
int firstNonWsOffset = CharArrayUtil.shiftForward(chars, firstLineStart, " \t");
|
||||
if (firstNonWsOffset > firstLineStart) {
|
||||
CharSequence toInsert = chars.subSequence(firstLineStart, firstNonWsOffset);
|
||||
for (int line = firstLine + 1; line <= lastLine; line++) {
|
||||
document.insertString(document.getLineStartOffset(line), toInsert);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Sync document and PSI for correct formatting processing.
|
||||
PsiDocumentManager.getInstance(project).commitAllDocuments();
|
||||
if (file == null) {
|
||||
return;
|
||||
}
|
||||
CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(project);
|
||||
|
||||
final int startLine = document.getLineNumber(startOffset);
|
||||
final int startLineStartOffset = document.getLineStartOffset(startLine);
|
||||
int indentAdjustmentAnchorLine = -1; // Line which indent change will be applied to all subsequent pasted lines.
|
||||
int pastedLinesAfterIndentLine = 0;
|
||||
|
||||
final int nonWsOffset = CharArrayUtil.shiftBackward(chars, startOffset - 1, " \t");
|
||||
boolean onNewLine = nonWsOffset < 0 || chars.charAt(nonWsOffset) == '\n';
|
||||
|
||||
int diffShift = 0;
|
||||
if (onNewLine && (chars.charAt(startOffset) == ' ' || chars.charAt(startOffset) == '\n')) {
|
||||
indentAdjustmentAnchorLine = startLine;
|
||||
diffShift += startOffset - startLineStartOffset;
|
||||
final int j = CharArrayUtil.shiftForward(chars, startOffset, " \t");
|
||||
codeStyleManager.adjustLineIndent(file, startOffset);
|
||||
|
||||
// Handle situation when pasted block starts with non-white space symbols.
|
||||
if (j == startOffset) {
|
||||
int indentOffset = CharArrayUtil.shiftForward(chars, firstLineStart, " \t");
|
||||
if (indentOffset > firstLineStart) {
|
||||
CharSequence toInsert = chars.subSequence(firstLineStart, indentOffset);
|
||||
for (int line = firstLine + 1; line <= lastLine; line++) {
|
||||
document.insertString(document.getLineStartOffset(line), toInsert);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (int line = startLine + 1, max = document.getLineCount(); line < max; line++) {
|
||||
int lineStartOffset = document.getLineStartOffset(line);
|
||||
if (lineStartOffset >= endOffset) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (indentAdjustmentAnchorLine >= 0) {
|
||||
pastedLinesAfterIndentLine++;
|
||||
continue;
|
||||
}
|
||||
|
||||
int j = CharArrayUtil.shiftForward(chars, lineStartOffset, " \t");
|
||||
if (j < document.getLineEndOffset(line)) {
|
||||
// Non-empty line is found
|
||||
indentAdjustmentAnchorLine = line;
|
||||
}
|
||||
// Handle situation when pasted block starts from white space symbols. Assume that the pasted text started at the line start,
|
||||
// i.e. correct indentation level is stored at the blocks structure.
|
||||
final int firstNonWsOffset = CharArrayUtil.shiftForward(chars, firstLineStart, " \t");
|
||||
final int diff = firstNonWsOffset - j;
|
||||
if (diff == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (indentAdjustmentAnchorLine < 0) {
|
||||
codeStyleManager.adjustLineIndent(file, startOffset);
|
||||
if (diff > 0) {
|
||||
CharSequence toInsert = chars.subSequence(firstLineStart, startOffset + diff);
|
||||
for (int line = firstLine + 1; line <= lastLine; line++) {
|
||||
document.insertString(document.getLineStartOffset(line), toInsert);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
int lineAdjustmentStartOffset; // Start offset of the range which indent will be adjusted
|
||||
final int anchorLineStart = document.getLineStartOffset(indentAdjustmentAnchorLine);
|
||||
int lineAdjustmentEndOffset = CharArrayUtil.shiftForward(chars, anchorLineStart, " \t");
|
||||
|
||||
if (onNewLine) {
|
||||
lineAdjustmentStartOffset = nonWsOffset + 1;
|
||||
if (-diff == startOffset - firstLineStart) {
|
||||
return;
|
||||
}
|
||||
else {
|
||||
lineAdjustmentStartOffset = anchorLineStart;
|
||||
}
|
||||
codeStyleManager.adjustLineIndent(file, new TextRange(lineAdjustmentStartOffset, lineAdjustmentEndOffset));
|
||||
int diff = lineAdjustmentEndOffset - CharArrayUtil.shiftForward(chars, anchorLineStart, " \t") - diffShift;
|
||||
if (diff > 0) {
|
||||
// Indent was cut.
|
||||
for (int line = indentAdjustmentAnchorLine + 1, max = line + pastedLinesAfterIndentLine; line < max; line++) {
|
||||
int lineStartOffset = document.getLineStartOffset(line);
|
||||
int indentOffset = CharArrayUtil.shiftForward(chars, lineStartOffset, " \t");
|
||||
int symbolsToCutNow = Math.min(diff, indentOffset - lineStartOffset);
|
||||
document.deleteString(lineStartOffset, lineStartOffset + symbolsToCutNow);
|
||||
if (-diff > startOffset - firstLineStart) {
|
||||
int desiredSymbolsToRemove = -diff - (startOffset - firstLineStart);
|
||||
for (int line = firstLine + 1; line <= lastLine; line++) {
|
||||
int currentLineStart = document.getLineStartOffset(line);
|
||||
int currentLineIndentOffset = CharArrayUtil.shiftForward(chars, currentLineStart, " \t");
|
||||
int symbolsToRemove = Math.min(currentLineIndentOffset - currentLineStart, desiredSymbolsToRemove);
|
||||
if (symbolsToRemove > 0) {
|
||||
document.deleteString(currentLineStart, currentLineStart + symbolsToRemove);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (diff < 0) {
|
||||
final CharSequence toInsert = chars.subSequence(anchorLineStart, anchorLineStart - diff);
|
||||
for (int line = indentAdjustmentAnchorLine + 1, max = line + pastedLinesAfterIndentLine; line < max; line++) {
|
||||
else {
|
||||
CharSequence toInsert = chars.subSequence(firstLineStart, firstLineStart + startOffset - firstLineStart + diff);
|
||||
for (int line = firstLine + 1; line <= lastLine; line++) {
|
||||
document.insertString(document.getLineStartOffset(line), toInsert);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user