IDEA-113977 Formatter: caret is moved on next line if closing brace has wrong indent [CR-IC-3034]

This commit is contained in:
Yaroslav Lepenkin
2013-11-21 16:27:28 +04:00
parent 119488b9e4
commit dd55e706f0
2 changed files with 160 additions and 58 deletions
@@ -97,6 +97,39 @@ public class JavaFormatterInEditorTest extends LightPlatformCodeInsightTestCase
doTest(before, after);
}
public void testCaretLineAndPositionPreserved_WhenBracketOnNextLineWillBeFormatted() throws IOException {
String before = "public class Test {\n" +
" int a;\n" +
" \n" +
" public static void main(String[] args) {\n" +
" <caret>\n" +
" }\n" +
"\n" +
" static final long j = 2;\n" +
"}";
String after = "public class Test {\n" +
" int a;\n" +
"\n" +
" public static void main(String[] args) {\n" +
" <caret>\n" +
" }\n" +
"\n" +
" static final long j = 2;\n" +
"}";
doTest(before, after);
before = "public class Test {\n" +
" int a;\n" +
" \n" +
" public static void main(String[] args) {\n" +
" <caret> \n" +
" }\n" +
"\n" +
" static final long j = 2;\n" +
"}";
doTest(before, after);
}
public void doTest(@NotNull String before, @NotNull String after) throws IOException {
configureFromFileText(getTestName(false) + ".java", before);
CodeStyleManager.getInstance(getProject()).reformatText(getFile(), 0, getEditor().getDocument().getTextLength());
@@ -173,37 +173,9 @@ public class CodeStyleManagerImpl extends CodeStyleManager {
editor = PsiUtilBase.findEditor(file);
}
// There is a possible case that cursor is located at the end of the line that contains only white spaces. For example:
// public void foo() {
// <caret>
// }
// Formatter removes such white spaces, i.e. keeps only line feed symbol. But we want to preserve caret position then.
// So, if 'virtual space in editor' is enabled, we save target visual column. Caret indent is ensured otherwise
int visualColumnToRestore = -1;
String caretIndentToRestore = null;
RangeMarker beforeCaretRangeMarker = null;
CaretPositionKeeper caretKeeper = null;
if (editor != null) {
Document document = editor.getDocument();
int caretOffset = editor.getCaretModel().getOffset();
caretOffset = Math.max(Math.min(caretOffset, document.getTextLength() - 1), 0);
CharSequence text = document.getCharsSequence();
int caretLine = document.getLineNumber(caretOffset);
int lineStartOffset = document.getLineStartOffset(caretLine);
int lineEndOffset = document.getLineEndOffset(caretLine);
boolean fixCaretPosition = true;
for (int i = lineStartOffset; i < lineEndOffset; i++) {
char c = text.charAt(i);
if (c != ' ' && c != '\t' && c != '\n') {
fixCaretPosition = false;
break;
}
}
if (fixCaretPosition) {
visualColumnToRestore = editor.getCaretModel().getVisualPosition().column;
caretIndentToRestore = document.getText(TextRange.create(lineStartOffset, caretOffset));
beforeCaretRangeMarker = document.createRangeMarker(0, lineStartOffset);
}
caretKeeper = new CaretPositionKeeper(editor);
}
final SmartPointerManager smartPointerManager = SmartPointerManager.getInstance(getProject());
@@ -243,34 +215,8 @@ public class CodeStyleManagerImpl extends CodeStyleManager {
if (info.endPointer != null) smartPointerManager.removePointer(info.endPointer);
}
if (editor == null) {
return;
}
boolean virtualSpaceEnabled = editor.getSettings().isVirtualSpace();
if (virtualSpaceEnabled) {
if (visualColumnToRestore < 0) {
editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
return;
}
CaretModel caretModel = editor.getCaretModel();
VisualPosition position = caretModel.getVisualPosition();
if (visualColumnToRestore != position.column) {
caretModel.moveToVisualPosition(new VisualPosition(position.line, visualColumnToRestore));
}
}
else {
if (beforeCaretRangeMarker == null || !beforeCaretRangeMarker.isValid() || caretIndentToRestore == null) {
return;
}
int offset = beforeCaretRangeMarker.getEndOffset();
beforeCaretRangeMarker.dispose();
if (editor.getCaretModel().getVisualPosition().column == visualColumnToRestore) {
return;
}
Document document = editor.getDocument();
document.replaceString(document.getLineStartOffset(document.getLineNumber(offset)), offset, caretIndentToRestore);
if (caretKeeper != null) {
caretKeeper.restoreCaretPosition();
}
}
@@ -735,4 +681,127 @@ public class CodeStyleManagerImpl extends CodeStyleManager {
this.toEnd = toEnd;
}
}
// There is a possible case that cursor is located at the end of the line that contains only white spaces. For example:
// public void foo() {
// <caret>
// }
// Formatter removes such white spaces, i.e. keeps only line feed symbol. But we want to preserve caret position then.
// So, if 'virtual space in editor' is enabled, we save target visual column. Caret indent is ensured otherwise
private static class CaretPositionKeeper {
Editor myEditor;
Document myDocument;
CaretModel myCaretModel;
RangeMarker myBeforeCaretRangeMarker;
String myCaretIndentToRestore;
int myVisualColumnToRestore = -1;
CaretPositionKeeper(@NotNull Editor editor) {
myEditor = editor;
myCaretModel = editor.getCaretModel();
myDocument = editor.getDocument();
int caretOffset = getCaretOffset();
int lineStartOffset = getLineStartOffsetByTotalOffset(caretOffset);
int lineEndOffset = getLineEndOffsetByTotalOffset(caretOffset);
boolean shouldFixCaretPosition = rangeHasWhiteSpaceSymbolsOnly(myDocument.getCharsSequence(), lineStartOffset, lineEndOffset);
if (shouldFixCaretPosition) {
initRestoreInfo(caretOffset);
}
}
private void initRestoreInfo(int caretOffset) {
int lineStartOffset = getLineStartOffsetByTotalOffset(caretOffset);
myVisualColumnToRestore = myCaretModel.getVisualPosition().column;
myCaretIndentToRestore = myDocument.getText(TextRange.create(lineStartOffset, caretOffset));
myBeforeCaretRangeMarker = myDocument.createRangeMarker(0, lineStartOffset);
}
public void restoreCaretPosition() {
if (isVirtualSpaceEnabled()) {
restoreVisualPosition();
}
else {
restorePositionByIndentInsertion();
}
}
private void restorePositionByIndentInsertion() {
if (myBeforeCaretRangeMarker == null || !myBeforeCaretRangeMarker.isValid() || myCaretIndentToRestore == null) {
return;
}
int newCaretLineStartOffset = myBeforeCaretRangeMarker.getEndOffset();
myBeforeCaretRangeMarker.dispose();
if (myCaretModel.getVisualPosition().column == myVisualColumnToRestore) {
return;
}
insertWhiteSpaceIndentIfNeeded(newCaretLineStartOffset);
}
private void restoreVisualPosition() {
if (myVisualColumnToRestore < 0) {
myEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
return;
}
VisualPosition position = myCaretModel.getVisualPosition();
if (myVisualColumnToRestore != position.column) {
myCaretModel.moveToVisualPosition(new VisualPosition(position.line, myVisualColumnToRestore));
}
}
private void insertWhiteSpaceIndentIfNeeded(int caretLineOffset) {
int lineToInsertIndent = myDocument.getLineNumber(caretLineOffset);
if (!lineContainsWhiteSpaceSymbolsOnly(lineToInsertIndent))
return;
int lineToInsertStartOffset = myDocument.getLineStartOffset(lineToInsertIndent);
if (lineToInsertIndent != getCurrentCaretLine()) {
myCaretModel.moveToOffset(lineToInsertStartOffset);
}
myDocument.replaceString(lineToInsertStartOffset, caretLineOffset, myCaretIndentToRestore);
}
private boolean rangeHasWhiteSpaceSymbolsOnly(CharSequence text, int lineStartOffset, int lineEndOffset) {
for (int i = lineStartOffset; i < lineEndOffset; i++) {
char c = text.charAt(i);
if (c != ' ' && c != '\t' && c != '\n') {
return false;
}
}
return true;
}
private boolean isVirtualSpaceEnabled() {
return myEditor.getSettings().isVirtualSpace();
}
private int getLineStartOffsetByTotalOffset(int offset) {
int line = myDocument.getLineNumber(offset);
return myDocument.getLineStartOffset(line);
}
private int getLineEndOffsetByTotalOffset(int offset) {
int line = myDocument.getLineNumber(offset);
return myDocument.getLineEndOffset(line);
}
private int getCaretOffset() {
int caretOffset = myCaretModel.getOffset();
caretOffset = Math.max(Math.min(caretOffset, myDocument.getTextLength() - 1), 0);
return caretOffset;
}
private boolean lineContainsWhiteSpaceSymbolsOnly(int lineNumber) {
int startOffset = myDocument.getLineStartOffset(lineNumber);
int endOffset = myDocument.getLineEndOffset(lineNumber);
return rangeHasWhiteSpaceSymbolsOnly(myDocument.getCharsSequence(), startOffset, endOffset);
}
private int getCurrentCaretLine() {
return myDocument.getLineNumber(myCaretModel.getOffset());
}
}
}