mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IDEA-63443 Formatter: Improve formatter performance during operations that require big number of small changes
1. Formatter performs whole document text replacement instead of applying thousand of small changes if necessary; 2. Common algorithm of line translation between two text changes is moved to util class in order to be reused during bookmarks restoring on bulk formatter processing; 3. EditorImpl is document bulk processing aware now (size container recalculation is adapted); 4. Soft wrap model caches information necessary for 'is EDT' check;
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2000-2010 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.intellij.formatting;
|
||||
|
||||
import com.intellij.openapi.editor.TextChange;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Encapsulates logic of merging set of changes into particular text.
|
||||
* <p/>
|
||||
* Thread-safe.
|
||||
*
|
||||
* @author Denis Zhdanov
|
||||
* @since 12/22/10 12:02 PM
|
||||
*/
|
||||
public class BulkChangesMerger {
|
||||
|
||||
/**
|
||||
* Merges given changes within the given text and returns result.
|
||||
*
|
||||
* @param text text to apply given changes for
|
||||
* @param changes changes to apply to the given text. It's assumed that there are no intersections between them and that they
|
||||
* are sorted by offsets in ascending order
|
||||
* @return merge result
|
||||
*/
|
||||
@SuppressWarnings({"MethodMayBeStatic"})
|
||||
public CharSequence merge(@NotNull char[] text, @NotNull List<TextChange> changes) {
|
||||
int newLength = text.length;
|
||||
for (TextChange change : changes) {
|
||||
newLength += change.getText().length() - (change.getEnd() - change.getStart());
|
||||
}
|
||||
char[] data = new char[newLength];
|
||||
int oldEndOffset = text.length;
|
||||
int newEndOffset = data.length;
|
||||
for (int i = changes.size() - 1; i >= 0; i--) {
|
||||
TextChange change = changes.get(i);
|
||||
|
||||
// Copy all unprocessed symbols from initial text that lay after the changed offset.
|
||||
int symbolsToMoveNumber = oldEndOffset - change.getEnd();
|
||||
System.arraycopy(text, change.getEnd(), data, newEndOffset - symbolsToMoveNumber, symbolsToMoveNumber);
|
||||
newEndOffset -= symbolsToMoveNumber;
|
||||
|
||||
// Copy all change symbols.
|
||||
char[] changeSymbols = change.getChars();
|
||||
newEndOffset -= changeSymbols.length;
|
||||
System.arraycopy(changeSymbols, 0, data, newEndOffset, changeSymbols.length);
|
||||
oldEndOffset = change.getStart();
|
||||
}
|
||||
|
||||
if (oldEndOffset > 0) {
|
||||
System.arraycopy(text, 0, data, 0, oldEndOffset);
|
||||
}
|
||||
|
||||
return new String(data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright 2000-2010 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.intellij.formatting;
|
||||
|
||||
import com.intellij.ide.bookmarks.Bookmark;
|
||||
import com.intellij.ide.bookmarks.BookmarkManager;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.util.diff.Diff;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* There is a possible case that we understand that formatting introduces big number of changes to the underlying document.
|
||||
* That number may be big enough for that their subsequent appliance is much slower than building resulting text directly
|
||||
* and replacing the whole document text.
|
||||
* <p/>
|
||||
* Current class defines a contract for a listener interested in such situations.
|
||||
* <p/>
|
||||
* Not thread-safe.
|
||||
*
|
||||
* @author Denis Zhdanov
|
||||
* @since 12/23/10 11:38 AM
|
||||
*/
|
||||
public class BulkReformatListener {
|
||||
|
||||
private final List<BookmarkData> myBookmarks = new ArrayList<BookmarkData>();
|
||||
|
||||
private final Project myProject;
|
||||
private final VirtualFile myVirtualFile;
|
||||
|
||||
public BulkReformatListener(@NotNull Project project, VirtualFile virtualFile) {
|
||||
myProject = project;
|
||||
myVirtualFile = virtualFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is expected to be called before bulk processing.
|
||||
*
|
||||
* @param document document which text is about to be reformatted at bulk mode
|
||||
* @param newText formatted text that is about to replace the text at the given document
|
||||
*/
|
||||
public void beforeProcessing(@NotNull Document document, @NotNull CharSequence newText) {
|
||||
reset();
|
||||
|
||||
Diff.Change change = Diff.buildChanges(document.getCharsSequence(), newText);
|
||||
if (change == null) {
|
||||
return;
|
||||
}
|
||||
BookmarkManager manager = BookmarkManager.getInstance(myProject);
|
||||
for (Bookmark bookmark : manager.getValidBookmarks()) {
|
||||
if (bookmark.isValid() && bookmark.getDocument() == document) {
|
||||
int newLine = Diff.translateLine(change, bookmark.getLine());
|
||||
if (newLine >= 0) {
|
||||
myBookmarks.add(new BookmarkData(bookmark.getDescription(), bookmark.getLine()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Is expected to be called just after bulk reformat processing.
|
||||
*/
|
||||
public void afterProcessing() {
|
||||
if (myBookmarks.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final BookmarkManager manager = BookmarkManager.getInstance(myProject);
|
||||
for (BookmarkData bookmark : myBookmarks) {
|
||||
manager.addTextBookmark(myVirtualFile, bookmark.line, bookmark.description);
|
||||
}
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
private void reset() {
|
||||
myBookmarks.clear();
|
||||
}
|
||||
|
||||
private static class BookmarkData {
|
||||
public final String description;
|
||||
public final int line;
|
||||
|
||||
BookmarkData(String description, int line) {
|
||||
this.description = description;
|
||||
this.line = line;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,9 @@ package com.intellij.formatting;
|
||||
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.editor.TextChange;
|
||||
import com.intellij.openapi.editor.ex.DocumentEx;
|
||||
import com.intellij.openapi.editor.impl.softwrap.TextChangeImpl;
|
||||
import com.intellij.openapi.fileTypes.StdFileTypes;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
@@ -32,7 +34,16 @@ import java.util.*;
|
||||
|
||||
class FormatProcessor {
|
||||
|
||||
/**
|
||||
* There is a possible case that formatting introduced big number of changes to the underlying document. That number may be
|
||||
* big enough for that their subsequent appliance is much slower than direct replacing of the whole document text.
|
||||
* <p/>
|
||||
* Current constant holds minimum number of changes that should trigger such <code>'replace whole text'</code> optimization.
|
||||
*/
|
||||
private static final int BULK_REPLACE_OPTIMIZATION_CRITERIA = 3000;
|
||||
|
||||
private static final Logger LOG = Logger.getInstance("#com.intellij.formatting.FormatProcessor");
|
||||
private static final BulkChangesMerger ourBulkChangesMerger = new BulkChangesMerger();
|
||||
|
||||
private LeafBlockWrapper myCurrentBlock;
|
||||
|
||||
@@ -74,7 +85,7 @@ class FormatProcessor {
|
||||
|
||||
private LeafBlockWrapper myFirstTokenBlock;
|
||||
private LeafBlockWrapper myLastTokenBlock;
|
||||
|
||||
|
||||
private SortedMap<TextRange, Pair<AbstractBlockWrapper, Boolean>> myPreviousDependencies =
|
||||
new TreeMap<TextRange, Pair<AbstractBlockWrapper, Boolean>>(new Comparator<TextRange>() {
|
||||
public int compare(final TextRange o1, final TextRange o2) {
|
||||
@@ -91,6 +102,8 @@ class FormatProcessor {
|
||||
private WhiteSpace myLastWhiteSpace;
|
||||
private boolean myDisposed;
|
||||
private CodeStyleSettings.IndentOptions myJavaIndentOptions;
|
||||
private BulkReformatListener myBulkReformatListener;
|
||||
|
||||
|
||||
public FormatProcessor(final FormattingDocumentModel docModel,
|
||||
Block rootBlock,
|
||||
@@ -198,15 +211,31 @@ class FormatProcessor {
|
||||
myJavaIndentOptions = javaIndentOptions;
|
||||
}
|
||||
|
||||
private static void doModify(final List<LeafBlockWrapper> blocksToModify, final FormattingModel model,
|
||||
/**
|
||||
* @param bulkReformatListener new listener to use; <code>null</code> to reset any listener registered before
|
||||
*/
|
||||
public void setBulkReformatListener(@Nullable BulkReformatListener bulkReformatListener) {
|
||||
myBulkReformatListener = bulkReformatListener;
|
||||
}
|
||||
|
||||
private void doModify(final List<LeafBlockWrapper> blocksToModify, final FormattingModel model,
|
||||
CodeStyleSettings.IndentOptions indentOption, CodeStyleSettings.IndentOptions javaOptions) {
|
||||
final int blocksToModifyCount = blocksToModify.size();
|
||||
final boolean bulkReformat = blocksToModifyCount > 50;
|
||||
final DocumentEx updatedDocument = bulkReformat ? getAffectedDocument(model) : null;
|
||||
if (updatedDocument != null) {
|
||||
updatedDocument.setInBulkUpdate(true);
|
||||
}
|
||||
DocumentEx updatedDocument = null;
|
||||
|
||||
try {
|
||||
if (blocksToModifyCount > BULK_REPLACE_OPTIMIZATION_CRITERIA) {
|
||||
if (applyChangesAtBulkMode(blocksToModify, model, indentOption)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
final boolean bulkReformat = blocksToModifyCount > 50;
|
||||
updatedDocument = bulkReformat ? getAffectedDocument(model) : null;
|
||||
if (updatedDocument != null) {
|
||||
updatedDocument.setInBulkUpdate(true);
|
||||
}
|
||||
|
||||
int shift = 0;
|
||||
for (int i = 0; i < blocksToModifyCount; ++i) {
|
||||
final LeafBlockWrapper block = blocksToModify.get(i);
|
||||
@@ -226,6 +255,54 @@ class FormatProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decides whether applying formatter changes should be applied incrementally one-by-one or merge result should be
|
||||
* constructed locally and the whole document text should be replaced. Performs such single bulk change if necessary.
|
||||
*
|
||||
* @param blocksToModify changes introduced by formatter
|
||||
* @param model current formatting model
|
||||
* @param indentOption indent options to use
|
||||
* @return <code>true</code> if given changes are applied to the document (i.e. no further processing is required);
|
||||
* <code>false</code> otherwise
|
||||
* @see BulkReformatListener
|
||||
*/
|
||||
private boolean applyChangesAtBulkMode(final List<LeafBlockWrapper> blocksToModify, final FormattingModel model,
|
||||
CodeStyleSettings.IndentOptions indentOption)
|
||||
{
|
||||
FormattingDocumentModel documentModel = model.getDocumentModel();
|
||||
Document document = documentModel.getDocument();
|
||||
if (document == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<TextChange> changes = new ArrayList<TextChange>();
|
||||
for (LeafBlockWrapper block : blocksToModify) {
|
||||
WhiteSpace whiteSpace = block.getWhiteSpace();
|
||||
CharSequence newWs = documentModel.adjustWhiteSpaceIfNecessary(
|
||||
whiteSpace.generateWhiteSpace(indentOption), whiteSpace.getStartOffset(), whiteSpace.getEndOffset()
|
||||
);
|
||||
changes.add(new TextChangeImpl(newWs, whiteSpace.getStartOffset(), whiteSpace.getEndOffset()));
|
||||
}
|
||||
CharSequence mergeResult = ourBulkChangesMerger.merge(document.getChars(), changes);
|
||||
if (myBulkReformatListener != null) {
|
||||
myBulkReformatListener.beforeProcessing(document, mergeResult);
|
||||
}
|
||||
document.replaceString(0, document.getTextLength(), mergeResult);
|
||||
cleanupBlocks(blocksToModify);
|
||||
if (myBulkReformatListener != null) {
|
||||
myBulkReformatListener.afterProcessing();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void cleanupBlocks(List<LeafBlockWrapper> blocks) {
|
||||
for (LeafBlockWrapper block : blocks) {
|
||||
block.getParent().dispose();
|
||||
block.dispose();
|
||||
}
|
||||
blocks.clear();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static DocumentEx getAffectedDocument(final FormattingModel model) {
|
||||
if (model instanceof DocumentBasedFormattingModel) {
|
||||
|
||||
@@ -19,9 +19,12 @@ package com.intellij.formatting;
|
||||
import com.intellij.openapi.components.ApplicationComponent;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.fileTypes.FileType;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.codeStyle.CodeStyleSettings;
|
||||
import com.intellij.psi.formatter.DocumentBasedFormattingModel;
|
||||
import com.intellij.psi.formatter.FormattingDocumentModelImpl;
|
||||
import com.intellij.psi.formatter.PsiBasedFormattingModel;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
@@ -131,13 +134,40 @@ public class FormatterImpl extends FormatterEx
|
||||
FormatTextRanges affectedRanges) throws IncorrectOperationException {
|
||||
disableFormatting();
|
||||
try {
|
||||
new FormatProcessor(model.getDocumentModel(), model.getRootBlock(), settings, indentOptions, affectedRanges).format(model);
|
||||
FormatProcessor processor =
|
||||
new FormatProcessor(model.getDocumentModel(), model.getRootBlock(), settings, indentOptions, affectedRanges);
|
||||
setupFormatProcessorIfPossible(processor, model);
|
||||
processor.format(model);
|
||||
} finally {
|
||||
enableFormatting();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static void setupFormatProcessorIfPossible(@NotNull FormatProcessor processor, @NotNull FormattingModel model) {
|
||||
if (!(model instanceof DocumentBasedFormattingModel)) {
|
||||
return;
|
||||
}
|
||||
|
||||
DocumentBasedFormattingModel formattingModel = (DocumentBasedFormattingModel)model;
|
||||
Project project = formattingModel.getProject();
|
||||
if (project == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
PsiFile file = formattingModel.getFile();
|
||||
if (file == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
VirtualFile virtualFile = file.getVirtualFile();
|
||||
if (virtualFile == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
processor.setBulkReformatListener(new BulkReformatListener(project, virtualFile));
|
||||
}
|
||||
|
||||
public void formatWithoutModifications(FormattingDocumentModel model,
|
||||
Block rootBlock,
|
||||
CodeStyleSettings settings,
|
||||
|
||||
+12
-5
@@ -35,12 +35,13 @@ import org.jetbrains.annotations.Nullable;
|
||||
* @author lesya
|
||||
*/
|
||||
public class DocumentBasedFormattingModel implements FormattingModel {
|
||||
private final Block myRootBlock;
|
||||
private final Block myRootBlock;
|
||||
private final FormattingDocumentModel myDocumentModel;
|
||||
private final Document myDocument;
|
||||
private final Project myProject;
|
||||
private final CodeStyleSettings mySettings;
|
||||
private final FileType myFileType;
|
||||
private final Document myDocument;
|
||||
private final Project myProject;
|
||||
private final CodeStyleSettings mySettings;
|
||||
private final FileType myFileType;
|
||||
private final PsiFile myFile;
|
||||
|
||||
public DocumentBasedFormattingModel(final Block rootBlock,
|
||||
final Document document,
|
||||
@@ -53,6 +54,7 @@ public class DocumentBasedFormattingModel implements FormattingModel {
|
||||
myProject = project;
|
||||
mySettings = settings;
|
||||
myFileType = fileType;
|
||||
myFile = file;
|
||||
myDocumentModel = new FormattingDocumentModelImpl(document,file);
|
||||
}
|
||||
|
||||
@@ -65,6 +67,7 @@ public class DocumentBasedFormattingModel implements FormattingModel {
|
||||
myProject = project;
|
||||
mySettings = settings;
|
||||
myFileType = fileType;
|
||||
myFile = file;
|
||||
myDocumentModel = FormattingDocumentModelImpl.createOn(file);
|
||||
myDocument = myDocumentModel.getDocument();
|
||||
}
|
||||
@@ -213,6 +216,10 @@ public class DocumentBasedFormattingModel implements FormattingModel {
|
||||
return myProject;
|
||||
}
|
||||
|
||||
public PsiFile getFile() {
|
||||
return myFile;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static String mergeWsWithCdataMarker(String whiteSpace, final String s, final int cdataPos) {
|
||||
final int firstCrInGeneratedWs = whiteSpace.indexOf('\n');
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2000-2010 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.intellij.formatting;
|
||||
|
||||
import com.intellij.openapi.editor.TextChange;
|
||||
import com.intellij.openapi.editor.impl.softwrap.TextChangeImpl;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* @author Denis Zhdanov
|
||||
* @since 12/22/2010
|
||||
*/
|
||||
public class BulkChangesMergerTest {
|
||||
|
||||
private BulkChangesMerger myMerger;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
myMerger = new BulkChangesMerger();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void simpleReplaceInTheMiddle() {
|
||||
doTest("abcd", "a123d", c("123", 1, 3));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void disjointInserts() {
|
||||
doTest("abcd", "a1b2c3d45", c("1", 1), c("2", 2), c("3", 3), c("45", 4));
|
||||
}
|
||||
|
||||
private static TextChange c(String text, int offset) {
|
||||
return c(text, offset, offset);
|
||||
}
|
||||
|
||||
private static TextChange c(String text, int start, int end) {
|
||||
return new TextChangeImpl(text, start, end);
|
||||
}
|
||||
|
||||
private void doTest(String initial, String expected, TextChange ... changes) {
|
||||
CharSequence actual = myMerger.merge(initial.toCharArray(), Arrays.asList(changes));
|
||||
assertEquals(expected, actual.toString());
|
||||
}
|
||||
}
|
||||
@@ -147,7 +147,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
|
||||
private final Key<Object> MOUSE_DRAGGED_GROUP = Key.create("MouseDraggedGroup");
|
||||
|
||||
private final DocumentListener myEditorDocumentAdapter;
|
||||
private final DocumentListener myEditorDocumentAdapter;
|
||||
|
||||
private final SettingsImpl mySettings;
|
||||
|
||||
@@ -270,6 +270,8 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
myCommandProcessor = CommandProcessor.getInstance();
|
||||
|
||||
myEditorDocumentAdapter = new EditorDocumentAdapter();
|
||||
DocumentBulkUpdateListener editorDocumentBulkAdapter = new EditorDocumentBulkUpdateAdapter();
|
||||
project.getMessageBus().connect().subscribe(DocumentBulkUpdateListener.TOPIC, editorDocumentBulkAdapter);
|
||||
myMouseMotionListeners = ContainerUtil.createEmptyCOWList();
|
||||
|
||||
myMarkupModelListener = new MarkupModelListener() {
|
||||
@@ -1316,13 +1318,35 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), height);
|
||||
}
|
||||
|
||||
private void beforeChangedUpdate(DocumentEvent e) {
|
||||
if (!myDocument.isInBulkUpdate()) {
|
||||
Rectangle visibleArea = getScrollingModel().getVisibleArea();
|
||||
Point pos = visualPositionToXY(getCaretModel().getVisualPosition());
|
||||
myCaretUpdateVShift = pos.y - visibleArea.y;
|
||||
private void bulkUpdateStarted() {
|
||||
}
|
||||
|
||||
private void bulkUpdateFinished() {
|
||||
if (myScrollPane == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
stopOptimizedScrolling();
|
||||
mySelectionModel.removeBlockSelection();
|
||||
|
||||
mySizeContainer.reset();
|
||||
validateSize();
|
||||
|
||||
updateGutterSize();
|
||||
repaintToScreenBottom(0);
|
||||
updateCaretCursor();
|
||||
}
|
||||
|
||||
private void beforeChangedUpdate(DocumentEvent e) {
|
||||
if (myDocument.isInBulkUpdate()) {
|
||||
// Assuming that the job is done at bulk listener callback methods.
|
||||
return;
|
||||
}
|
||||
|
||||
Rectangle visibleArea = getScrollingModel().getVisibleArea();
|
||||
Point pos = visualPositionToXY(getCaretModel().getVisualPosition());
|
||||
myCaretUpdateVShift = pos.y - visibleArea.y;
|
||||
|
||||
// We assume that size container is already notified with the visual line widths during soft wraps processing
|
||||
if (!mySoftWrapModel.isSoftWrappingEnabled()) {
|
||||
mySizeContainer.beforeChange(e);
|
||||
@@ -1330,7 +1354,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
}
|
||||
|
||||
private void changedUpdate(DocumentEvent e) {
|
||||
if (myScrollPane == null) return;
|
||||
if (myScrollPane == null || myDocument.isInBulkUpdate()) return;
|
||||
|
||||
stopOptimizedScrolling();
|
||||
mySelectionModel.removeBlockSelection();
|
||||
@@ -5129,6 +5153,18 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
}
|
||||
}
|
||||
|
||||
class EditorDocumentBulkUpdateAdapter implements DocumentBulkUpdateListener {
|
||||
@Override
|
||||
public void updateStarted(Document doc) {
|
||||
bulkUpdateStarted();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFinished(Document doc) {
|
||||
bulkUpdateFinished();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"FieldAccessedSynchronizedAndUnsynchronized"})
|
||||
private class EditorSizeContainer {
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import com.intellij.openapi.editor.impl.softwrap.*;
|
||||
import com.intellij.openapi.editor.impl.softwrap.mapping.CachingSoftWrapDataMapper;
|
||||
import com.intellij.openapi.editor.impl.softwrap.mapping.SoftWrapApplianceManager;
|
||||
import com.intellij.openapi.editor.impl.softwrap.mapping.SoftWrapAwareVisualSizeManager;
|
||||
import com.intellij.reference.SoftReference;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -72,6 +73,12 @@ public class SoftWrapModelImpl implements SoftWrapModelEx, PrioritizedDocumentLi
|
||||
private final SoftWrapAwareVisualSizeManager myVisualSizeManager;
|
||||
|
||||
private final EditorEx myEditor;
|
||||
|
||||
/**
|
||||
* We don't want to use soft wraps-aware processing from non-EDT and profiling shows that 'is EDT' check that is called too
|
||||
* often is rather expensive. Hence, we use caching here for performance improvement.
|
||||
*/
|
||||
private SoftReference<Thread> myLastEdt = new SoftReference<Thread>(null);
|
||||
/** Holds number of 'active' calls, i.e. number of methods calls of the current object within the current call stack. */
|
||||
private int myActive;
|
||||
private boolean myUseSoftWraps;
|
||||
@@ -142,8 +149,16 @@ public class SoftWrapModelImpl implements SoftWrapModelEx, PrioritizedDocumentLi
|
||||
|
||||
// We check that current thread is EDT because attempt to retrieve information about visible area width may fail otherwise
|
||||
Application application = ApplicationManager.getApplication();
|
||||
if (!application.isDispatchThread()) {
|
||||
return false;
|
||||
Thread lastEdt = myLastEdt.get();
|
||||
Thread currentThread = Thread.currentThread();
|
||||
if (lastEdt != currentThread) {
|
||||
if (application.isDispatchThread()) {
|
||||
myLastEdt = new SoftReference<Thread>(currentThread);
|
||||
}
|
||||
else {
|
||||
myLastEdt = new SoftReference<Thread>(null);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle visibleArea = myEditor.getScrollingModel().getVisibleArea();
|
||||
|
||||
+3
-22
@@ -17,7 +17,6 @@ package com.intellij.openapi.editor.impl.event;
|
||||
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.editor.event.DocumentEvent;
|
||||
import com.intellij.openapi.util.text.LineTokenizer;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.util.diff.Diff;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -165,33 +164,15 @@ public class DocumentEventImpl extends DocumentEvent {
|
||||
|
||||
public int translateLineViaDiffStrict(int line) {
|
||||
if (myChange == null) buildDiff();
|
||||
if (myChange == null) return line;
|
||||
|
||||
Diff.Change change = myChange;
|
||||
|
||||
int newLine = line;
|
||||
|
||||
while (change != null) {
|
||||
if (line < change.line0) break;
|
||||
if (line >= change.line0 + change.deleted) {
|
||||
newLine += change.inserted - change.deleted;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
|
||||
change = change.link;
|
||||
}
|
||||
|
||||
return newLine;
|
||||
if (change == null) return line;
|
||||
return Diff.translateLine(change, line);
|
||||
}
|
||||
|
||||
private void buildDiff() {
|
||||
final String[] strings1 = LineTokenizer.tokenize(myOldString, false);
|
||||
final String[] strings2 = LineTokenizer.tokenize(myNewString, false);
|
||||
|
||||
//Diff diff = new Diff(strings1, strings2);
|
||||
//myChange = diff.diff_2(false);
|
||||
myChange = Diff.buildChanges(strings1, strings2);
|
||||
myChange = Diff.buildChanges(myOldString, myNewString);
|
||||
}
|
||||
|
||||
public int getOptimizedLineShift() {
|
||||
|
||||
@@ -16,9 +16,12 @@
|
||||
package com.intellij.util.diff;
|
||||
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.util.text.LineTokenizer;
|
||||
import com.intellij.util.containers.Enumerator;
|
||||
import gnu.trove.TObjectHashingStrategy;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@@ -28,6 +31,13 @@ import java.util.ArrayList;
|
||||
public class Diff {
|
||||
private static final Logger LOG = Logger.getInstance("#com.intellij.util.diff.Diff");
|
||||
|
||||
@Nullable
|
||||
public static Change buildChanges(@NotNull CharSequence before, @NotNull CharSequence after) {
|
||||
final String[] strings1 = LineTokenizer.tokenize(before, false);
|
||||
final String[] strings2 = LineTokenizer.tokenize(after, false);
|
||||
return buildChanges(strings1, strings2);
|
||||
}
|
||||
|
||||
public static <T> Change buildChanges(T[] objects1, T[] objects2) {
|
||||
// Old variant of enumerator worked incorrectly with null values.
|
||||
// This check is to ensure that the corrected version does not introduce bugs.
|
||||
@@ -46,6 +56,48 @@ public class Diff {
|
||||
return builder.getFirstChange();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to translate given line that pointed to the text before change to the line that points to the same text after the change.
|
||||
*
|
||||
* @param before text before change
|
||||
* @param after text after change
|
||||
* @param line target line before change
|
||||
* @return translated line if the processing is ok; negative value otherwise
|
||||
*/
|
||||
public static int translateLine(@NotNull CharSequence before, @NotNull CharSequence after, int line) {
|
||||
Change change = buildChanges(before, after);
|
||||
if (change == null) {
|
||||
return -1;
|
||||
}
|
||||
return translateLine(change, line);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to translate given line that pointed to the text before change to the line that points to the same text after the change.
|
||||
*
|
||||
* @param change target change
|
||||
* @param line target line before change
|
||||
* @return translated line if the processing is ok; negative value otherwise
|
||||
*/
|
||||
public static int translateLine(@NotNull Change change, int line) {
|
||||
int result = line;
|
||||
|
||||
Change currentChange = change;
|
||||
|
||||
while (currentChange != null) {
|
||||
if (line < currentChange.line0) break;
|
||||
if (line >= currentChange.line0 + currentChange.deleted) {
|
||||
result += currentChange.inserted - currentChange.deleted;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
|
||||
currentChange = currentChange.link;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static class Change {
|
||||
// todo remove. Return lists instead.
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user