From 7003f54c9ac0ec09320458c9cc04c641c9964467 Mon Sep 17 00:00:00 2001 From: Denis Zhdanov Date: Thu, 23 Dec 2010 15:48:35 +0300 Subject: [PATCH] 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; --- .../formatting/BulkChangesMerger.java | 71 ++++++++++++ .../formatting/BulkReformatListener.java | 106 ++++++++++++++++++ .../intellij/formatting/FormatProcessor.java | 91 +++++++++++++-- .../intellij/formatting/FormatterImpl.java | 32 +++++- .../DocumentBasedFormattingModel.java | 17 ++- .../formatting/BulkChangesMergerTest.java | 62 ++++++++++ .../openapi/editor/impl/EditorImpl.java | 50 +++++++-- .../editor/impl/SoftWrapModelImpl.java | 19 +++- .../editor/impl/event/DocumentEventImpl.java | 25 +---- .../util/src/com/intellij/util/diff/Diff.java | 52 +++++++++ 10 files changed, 481 insertions(+), 44 deletions(-) create mode 100644 platform/lang-impl/src/com/intellij/formatting/BulkChangesMerger.java create mode 100644 platform/lang-impl/src/com/intellij/formatting/BulkReformatListener.java create mode 100644 platform/lang-impl/testSrc/com/intellij/formatting/BulkChangesMergerTest.java diff --git a/platform/lang-impl/src/com/intellij/formatting/BulkChangesMerger.java b/platform/lang-impl/src/com/intellij/formatting/BulkChangesMerger.java new file mode 100644 index 000000000000..239b518ffc09 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/formatting/BulkChangesMerger.java @@ -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. + *

+ * 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 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); + } +} diff --git a/platform/lang-impl/src/com/intellij/formatting/BulkReformatListener.java b/platform/lang-impl/src/com/intellij/formatting/BulkReformatListener.java new file mode 100644 index 000000000000..89123d1d9d06 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/formatting/BulkReformatListener.java @@ -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. + *

+ * Current class defines a contract for a listener interested in such situations. + *

+ * Not thread-safe. + * + * @author Denis Zhdanov + * @since 12/23/10 11:38 AM + */ +public class BulkReformatListener { + + private final List myBookmarks = new ArrayList(); + + 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; + } + } +} diff --git a/platform/lang-impl/src/com/intellij/formatting/FormatProcessor.java b/platform/lang-impl/src/com/intellij/formatting/FormatProcessor.java index 1d34fe4bbd07..fd5a9bbddbc4 100644 --- a/platform/lang-impl/src/com/intellij/formatting/FormatProcessor.java +++ b/platform/lang-impl/src/com/intellij/formatting/FormatProcessor.java @@ -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. + *

+ * Current constant holds minimum number of changes that should trigger such 'replace whole text' 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> myPreviousDependencies = new TreeMap>(new Comparator() { 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 blocksToModify, final FormattingModel model, + /** + * @param bulkReformatListener new listener to use; null to reset any listener registered before + */ + public void setBulkReformatListener(@Nullable BulkReformatListener bulkReformatListener) { + myBulkReformatListener = bulkReformatListener; + } + + private void doModify(final List 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 true if given changes are applied to the document (i.e. no further processing is required); + * false otherwise + * @see BulkReformatListener + */ + private boolean applyChangesAtBulkMode(final List blocksToModify, final FormattingModel model, + CodeStyleSettings.IndentOptions indentOption) + { + FormattingDocumentModel documentModel = model.getDocumentModel(); + Document document = documentModel.getDocument(); + if (document == null) { + return false; + } + + List changes = new ArrayList(); + 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 blocks) { + for (LeafBlockWrapper block : blocks) { + block.getParent().dispose(); + block.dispose(); + } + blocks.clear(); + } + @Nullable private static DocumentEx getAffectedDocument(final FormattingModel model) { if (model instanceof DocumentBasedFormattingModel) { diff --git a/platform/lang-impl/src/com/intellij/formatting/FormatterImpl.java b/platform/lang-impl/src/com/intellij/formatting/FormatterImpl.java index efdad42f26d1..d464f329c784 100644 --- a/platform/lang-impl/src/com/intellij/formatting/FormatterImpl.java +++ b/platform/lang-impl/src/com/intellij/formatting/FormatterImpl.java @@ -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, diff --git a/platform/lang-impl/src/com/intellij/psi/formatter/DocumentBasedFormattingModel.java b/platform/lang-impl/src/com/intellij/psi/formatter/DocumentBasedFormattingModel.java index 64476daa9963..ca0295ebca2d 100644 --- a/platform/lang-impl/src/com/intellij/psi/formatter/DocumentBasedFormattingModel.java +++ b/platform/lang-impl/src/com/intellij/psi/formatter/DocumentBasedFormattingModel.java @@ -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'); diff --git a/platform/lang-impl/testSrc/com/intellij/formatting/BulkChangesMergerTest.java b/platform/lang-impl/testSrc/com/intellij/formatting/BulkChangesMergerTest.java new file mode 100644 index 000000000000..78a4f98d2d2f --- /dev/null +++ b/platform/lang-impl/testSrc/com/intellij/formatting/BulkChangesMergerTest.java @@ -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()); + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java index b85f435199ac..7312ed7dc4a8 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java @@ -147,7 +147,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi private final Key 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 { diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/SoftWrapModelImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/SoftWrapModelImpl.java index a2a1975a9759..fbf5193d37cf 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/SoftWrapModelImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/SoftWrapModelImpl.java @@ -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 myLastEdt = new SoftReference(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(currentThread); + } + else { + myLastEdt = new SoftReference(null); + return false; + } } Rectangle visibleArea = myEditor.getScrollingModel().getVisibleArea(); diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/event/DocumentEventImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/event/DocumentEventImpl.java index d73844a04713..ec6a5d2e614b 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/event/DocumentEventImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/event/DocumentEventImpl.java @@ -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() { diff --git a/platform/util/src/com/intellij/util/diff/Diff.java b/platform/util/src/com/intellij/util/diff/Diff.java index 67dbabf2cf22..d35e13d14743 100644 --- a/platform/util/src/com/intellij/util/diff/Diff.java +++ b/platform/util/src/com/intellij/util/diff/Diff.java @@ -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 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. /**