IDEA-154312 IDE becomes unresponsive on file save/sync

Make sure text layout cache is not 'polluted' with useless data:
* don't put subchunks into cache
* remove chunks for invalidated lines from cache
This commit is contained in:
Dmitry Batrak
2016-05-19 16:06:46 +03:00
parent ea99120065
commit d19424b54e
2 changed files with 50 additions and 12 deletions
@@ -15,6 +15,7 @@
*/
package com.intellij.openapi.editor.impl.view;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.bidi.BidiRegionsSeparator;
import com.intellij.openapi.editor.bidi.LanguageBidiRegionsSeparator;
@@ -36,6 +37,7 @@ import java.awt.font.FontRenderContext;
import java.text.Bidi;
import java.util.*;
import java.util.List;
import java.util.stream.Stream;
/**
* Layout of a single line of document text. Consists of a series of BidiRuns, which, in turn, consist of TextFragments.
@@ -43,6 +45,8 @@ import java.util.List;
* for required Chunks.
*/
abstract class LineLayout {
private static final Logger LOG = Logger.getInstance(LineLayout.class);
private LineLayout() {}
/**
@@ -280,6 +284,8 @@ abstract class LineLayout {
return new VisualOrderIterator(view, line, startX, startVisualColumn, startLogicalColumn, startOffset, runs);
}
abstract Stream<Chunk> getChunksInLogicalOrder();
float getWidth() {
throw new RuntimeException("This LineLayout instance doesn't have precalculated width");
}
@@ -301,6 +307,11 @@ abstract class LineLayout {
myChunk = chunk;
}
@Override
Stream<Chunk> getChunksInLogicalOrder() {
return myChunk == null ? Stream.empty() : Stream.of(myChunk);
}
@Override
boolean isLtr() {
return true;
@@ -349,6 +360,11 @@ abstract class LineLayout {
}
}
@Override
Stream<Chunk> getChunksInLogicalOrder() {
return Stream.of(myBidiRunsInLogicalOrder).flatMap((BidiRun r) -> r.chunks == null ? Stream.empty() : Stream.of(r.chunks));
}
@Override
boolean isLtr() {
return myBidiRunsInLogicalOrder.length == 0 || myBidiRunsInLogicalOrder.length == 1 && !myBidiRunsInLogicalOrder[0].isRtl();
@@ -421,6 +437,11 @@ abstract class LineLayout {
return x;
}
@Override
Stream<Chunk> getChunksInLogicalOrder() {
return myDelegate.getChunksInLogicalOrder();
}
@Override
float getWidth() {
return myWidth;
@@ -525,10 +546,12 @@ abstract class LineLayout {
view.getTextLayoutCache().onChunkAccess(this);
}
if (fragments != null) return;
assert isReal();
fragments = new ArrayList<>();
int lineStartOffset = view.getEditor().getDocument().getLineStartOffset(line);
int start = lineStartOffset + startOffset;
int end = lineStartOffset + endOffset;
if (LOG.isDebugEnabled()) LOG.debug("Text layout for " + view.getEditor().getVirtualFile() + " (" + start + "-" + end + ")");
IterationState it = new IterationState(view.getEditor(), start, end, false, false, true, false, false);
FontPreferences fontPreferences = view.getEditor().getColorsScheme().getFontPreferences();
char[] chars = CharArrayUtil.fromSequence(view.getEditor().getDocument().getImmutableCharSequence(), start, end);
@@ -557,19 +580,25 @@ abstract class LineLayout {
private Chunk subChunk(EditorView view, BidiRun run, int line, int targetStartOffset, int targetEndOffset,
@Nullable Runnable quickEvaluationListener) {
assert isReal();
assert targetStartOffset < endOffset;
assert targetEndOffset > startOffset;
int start = Math.max(startOffset, targetStartOffset);
int end = Math.min(endOffset, targetEndOffset);
if (quickEvaluationListener != null && fragments == null) {
quickEvaluationListener.run();
return new ApproximationChunk(view, line, start, end);
Chunk chunk = new SyntheticChunk(start, end);
int startColumn = view.getLogicalPositionCache().offsetToLogicalColumn(line, start);
int endColumn = view.getLogicalPositionCache().offsetToLogicalColumn(line, end);
chunk.fragments = Collections.singletonList(new ApproximationFragment(end - start, endColumn - startColumn,
view.getMaxCharWidth()));
return chunk;
}
if (start == startOffset && end == this.endOffset) {
return this;
}
ensureLayout(view, run, line);
Chunk chunk = new Chunk(start, end);
Chunk chunk = new SyntheticChunk(start, end);
chunk.fragments = new ArrayList<>();
int offset = startOffset;
for (LineFragment fragment : fragments) {
@@ -591,13 +620,10 @@ abstract class LineLayout {
fragments = null;
}
}
private static class ApproximationChunk extends Chunk {
private ApproximationChunk(@NotNull EditorView view, int line, int start, int end) {
super(start, end);
int startColumn = view.getLogicalPositionCache().offsetToLogicalColumn(line, start);
int endColumn = view.getLogicalPositionCache().offsetToLogicalColumn(line, end);
fragments = Collections.singletonList(new ApproximationFragment(end - start, endColumn - startColumn, view.getMaxCharWidth()));
private static class SyntheticChunk extends Chunk {
private SyntheticChunk(int startOffset, int endOffset) {
super(startOffset, endOffset);
}
@Override
@@ -605,7 +631,7 @@ abstract class LineLayout {
return false;
}
}
private static class VisualOrderIterator implements Iterator<VisualFragment> {
private final EditorView myView;
private final int myLine;
@@ -29,6 +29,7 @@ import org.jetbrains.annotations.NotNull;
import java.awt.*;
import java.util.*;
import java.util.List;
/**
* Editor text layout storage. Layout is stored on a per-logical-line basis,
@@ -54,6 +55,7 @@ class TextLayoutCache implements PrioritizedDocumentListener, Disposable {
@Override
protected boolean removeEldestEntry(Map.Entry<LineLayout.Chunk, Object> eldest) {
if (size() > getChunkCacheSizeLimit()) {
if (LOG.isDebugEnabled()) LOG.debug("Clearing chunk for " + myView.getEditor().getVirtualFile());
eldest.getKey().clearCache();
return true;
}
@@ -116,13 +118,18 @@ class TextLayoutCache implements PrioritizedDocumentListener, Disposable {
for (int line = startLine; line <= endLine; line++) {
LineLayout lineLayout = myLines.get(line);
if (lineLayout != null) {
removeChunksFromCache(lineLayout);
myLines.set(line, keepBidiNotRequiredState && lineLayout.isLtr() ? myBidiNotRequiredMarker : null);
}
}
if (oldEndLine < newEndLine) {
myLines.addAll(oldEndLine + 1, Collections.nCopies(newEndLine - oldEndLine, (LineLayout)null));
myLines.addAll(oldEndLine + 1, Collections.nCopies(newEndLine - oldEndLine, null));
} else if (oldEndLine > newEndLine) {
myLines.subList(newEndLine + 1, oldEndLine + 1).clear();
List<LineLayout> layouts = myLines.subList(newEndLine + 1, oldEndLine + 1);
for (LineLayout layout : layouts) {
removeChunksFromCache(layout);
}
layouts.clear();
}
}
@@ -151,12 +158,17 @@ class TextLayoutCache implements PrioritizedDocumentListener, Disposable {
myLaidOutChunks.put(chunk, null);
}
private void removeChunksFromCache(LineLayout layout) {
layout.getChunksInLogicalOrder().forEach(myLaidOutChunks::remove);
}
private void trimChunkCache() {
int limit = getChunkCacheSizeLimit();
if (myLaidOutChunks.size() > limit) {
Iterator<LineLayout.Chunk> it = myLaidOutChunks.keySet().iterator();
while (myLaidOutChunks.size() > limit) {
LineLayout.Chunk chunk = it.next();
if (LOG.isDebugEnabled()) LOG.debug("Clearing chunk for " + myView.getEditor().getVirtualFile());
chunk.clearCache();
it.remove();
}