diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/LineLayout.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/LineLayout.java index 32389ee4f1c1..cdb6e3ae709a 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/LineLayout.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/LineLayout.java @@ -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 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 getChunksInLogicalOrder() { + return myChunk == null ? Stream.empty() : Stream.of(myChunk); + } + @Override boolean isLtr() { return true; @@ -349,6 +360,11 @@ abstract class LineLayout { } } + @Override + Stream 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 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 { private final EditorView myView; private final int myLine; diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/TextLayoutCache.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/TextLayoutCache.java index a1cfa0fa9ea6..cb7e7a6c7f45 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/TextLayoutCache.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/TextLayoutCache.java @@ -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 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 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 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(); }