IDEA-132554 automatic window scroll after CTRL-ALT-L

This commit is contained in:
Dmitry Batrak
2015-04-24 12:24:46 +03:00
parent 116743c7c7
commit 499766eea0
4 changed files with 99 additions and 10 deletions
@@ -17,7 +17,6 @@ package com.intellij.openapi.editor.impl;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.TextChange;
import com.intellij.util.text.CharArrayCharSequence;
import com.intellij.util.text.StringFactory;
import org.jetbrains.annotations.NotNull;
@@ -174,6 +173,20 @@ public class BulkChangesMerger {
data[i + offset] = text.charAt(i);
}
}
/**
* Given an offset of some location in the document, returns offset of this location after application of given changes. List of changes
* is supposed to satisfy the same constraints as required by {@link #mergeToCharSequence(char[], int, List)} method.
*/
public int updateOffset(int originalOffset, @NotNull List<? extends TextChange> changes) {
int offset = originalOffset;
for (TextChange change : changes) {
if (originalOffset > change.getStart()) {
offset += change.getText().length() - (change.getEnd() - change.getStart());
}
}
return offset;
}
private static class Context {
@@ -20,6 +20,8 @@ import com.intellij.codeInsight.CodeInsightBundle;
import com.intellij.formatting.FormattingProgressTask;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.editor.SelectionModel;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
@@ -34,8 +36,11 @@ import com.intellij.util.diff.FilesTooBigForDiffException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.awt.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
@@ -106,8 +111,6 @@ public class ReformatCodeProcessor extends AbstractLayoutCodeProcessor {
throws IncorrectOperationException
{
return new FutureTask<Boolean>(new Callable<Boolean>() {
private Document myDocument;
@Override
public Boolean call() throws Exception {
FormattingProgressTask.FORMATTING_CANCELLED_FLAG.set(false);
@@ -115,16 +118,18 @@ public class ReformatCodeProcessor extends AbstractLayoutCodeProcessor {
Collection<TextRange> ranges = getRangesToFormat(processChangedTextOnly, file);
CharSequence before = null;
Document document = PsiDocumentManager.getInstance(myProject).getDocument(file);
if (getInfoCollector() != null) {
myDocument = PsiDocumentManager.getInstance(myProject).getDocument(file);
LOG.assertTrue(myDocument != null);
before = myDocument.getImmutableCharSequence();
LOG.assertTrue(document != null);
before = document.getImmutableCharSequence();
}
CaretVisualPositionKeeper caretPositionKeeper = new CaretVisualPositionKeeper(document);
CodeStyleManager.getInstance(myProject).reformatText(file, ranges);
caretPositionKeeper.restoreOriginalLocation();
if (before != null) {
prepareUserNotificationMessage(myDocument, before);
prepareUserNotificationMessage(document, before);
}
return !FormattingProgressTask.FORMATTING_CANCELLED_FLAG.get();
@@ -165,4 +170,32 @@ public class ReformatCodeProcessor extends AbstractLayoutCodeProcessor {
return !myRanges.isEmpty() ? myRanges : ContainerUtil.newArrayList(file.getTextRange());
}
private static class CaretVisualPositionKeeper {
private final Map<Editor, Integer> myCaretRelativeVerticalPositions = new HashMap<Editor, Integer>();
private CaretVisualPositionKeeper(@Nullable Document document) {
if (document == null) return;
Editor[] editors = EditorFactory.getInstance().getEditors(document);
for (Editor editor : editors) {
Rectangle visibleArea = editor.getScrollingModel().getVisibleArea();
Point pos = editor.visualPositionToXY(editor.getCaretModel().getVisualPosition());
int relativePosition = pos.y - visibleArea.y;
myCaretRelativeVerticalPositions.put(editor, relativePosition);
}
}
private void restoreOriginalLocation() {
for (Map.Entry<Editor, Integer> e : myCaretRelativeVerticalPositions.entrySet()) {
Editor editor = e.getKey();
int relativePosition = e.getValue();
Point caretLocation = editor.visualPositionToXY(editor.getCaretModel().getVisualPosition());
int scrollOffset = caretLocation.y - relativePosition;
editor.getScrollingModel().disableAnimation();
editor.getScrollingModel().scrollVertically(scrollOffset);
editor.getScrollingModel().enableAnimation();
}
}
}
}
@@ -20,6 +20,8 @@ import com.intellij.lang.ASTNode;
import com.intellij.lang.Language;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.editor.TextChange;
import com.intellij.openapi.editor.ex.DocumentEx;
import com.intellij.openapi.editor.impl.BulkChangesMerger;
@@ -349,9 +351,7 @@ class FormatProcessor {
{
FormattingDocumentModel documentModel = model.getDocumentModel();
Document document = documentModel.getDocument();
if (document == null) {
return false;
}
CaretOffsetUpdater caretOffsetUpdater = new CaretOffsetUpdater(document);
List<TextChange> changes = new ArrayList<TextChange>();
int shift = 0;
@@ -363,6 +363,7 @@ class FormatProcessor {
whiteSpace.getEndOffset(), block.getNode(), false
);
if (changes.size() > 10000) {
caretOffsetUpdater.update(changes);
CharSequence mergeResult = BulkChangesMerger.INSTANCE.mergeToCharSequence(document.getChars(), document.getTextLength(), changes);
document.replaceString(0, document.getTextLength(), mergeResult);
shift += currentIterationShift;
@@ -373,8 +374,10 @@ class FormatProcessor {
currentIterationShift += change.getDiff();
changes.add(change);
}
caretOffsetUpdater.update(changes);
CharSequence mergeResult = BulkChangesMerger.INSTANCE.mergeToCharSequence(document.getChars(), document.getTextLength(), changes);
document.replaceString(0, document.getTextLength(), mergeResult);
caretOffsetUpdater.restoreCaretLocations();
cleanupBlocks(blocksToModify);
return true;
}
@@ -1487,4 +1490,28 @@ class FormatProcessor {
}
}
}
private static class CaretOffsetUpdater {
private final Map<Editor, Integer> myCaretOffsets = new HashMap<Editor, Integer>();
private CaretOffsetUpdater(@NotNull Document document) {
Editor[] editors = EditorFactory.getInstance().getEditors(document);
for (Editor editor : editors) {
myCaretOffsets.put(editor, editor.getCaretModel().getOffset());
}
}
private void update(@NotNull List<? extends TextChange> changes) {
BulkChangesMerger merger = BulkChangesMerger.INSTANCE;
for (Map.Entry<Editor, Integer> entry : myCaretOffsets.entrySet()) {
entry.setValue(merger.updateOffset(entry.getValue(), changes));
}
}
private void restoreCaretLocations() {
for (Map.Entry<Editor, Integer> entry : myCaretOffsets.entrySet()) {
entry.getKey().getCaretModel().moveToOffset(entry.getValue());
}
}
}
}
@@ -127,6 +127,18 @@ public class BulkChangesMergerTest {
doTest("0123456789ABC", "0abc1358d9eBC", c("abc", 1), c("", 2, 3), c("", 4, 5), c("", 6, 8), c("d", 9), c("e", 10, 11));
}
@Test
public void updateOffset() {
doTestUpdateOffset(5, 5, c("a", 6));
doTestUpdateOffset(5, 6, c("a", 3));
doTestUpdateOffset(5, 5, c("a", 6, 7));
doTestUpdateOffset(5, 5, c("a", 3, 4));
doTestUpdateOffset(5, 4, c("", 3, 4));
doTestUpdateOffset(5, 5, c("", 6, 7));
doTestUpdateOffset(5, 2, c("", 0, 1), c("", 2, 3), c("", 3, 4));
}
//@Config(inplace = true)
//@Test
//public void client() {
@@ -176,6 +188,10 @@ public class BulkChangesMergerTest {
assertEquals(expected, actual.toString());
}
}
private void doTestUpdateOffset(int initialOffset, int expectedOffset, TextChangeImpl... changes) {
assertEquals(expectedOffset, myMerger.updateOffset(initialOffset, Arrays.asList(changes)));
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)