mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
editor new rendering: speed up opening and editing for documents with long lines
This commit is contained in:
+2
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2000-2010 JetBrains s.r.o.
|
||||
* Copyright 2000-2015 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.
|
||||
@@ -33,6 +33,7 @@ public class EditorDocumentPriorities {
|
||||
public static final int RANGE_MARKER = 40;
|
||||
|
||||
public static final int FOLD_MODEL = 60;
|
||||
public static final int LOGICAL_POSITION_CACHE = 65;
|
||||
public static final int EDITOR_TEXT_LAYOUT_CACHE = 70;
|
||||
public static final int EDITOR_TEXT_WIDTH_CACHE = 75;
|
||||
public static final int LEXER_EDITOR = 80;
|
||||
|
||||
@@ -328,7 +328,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
assertIsDispatchThread();
|
||||
myProject = project;
|
||||
myDocument = (DocumentEx)document;
|
||||
if (myDocument instanceof DocumentImpl) {
|
||||
if (myDocument instanceof DocumentImpl && !myUseNewRendering) {
|
||||
((DocumentImpl)myDocument).requestTabTracking();
|
||||
}
|
||||
myScheme = createBoundColorSchemeDelegate(null);
|
||||
@@ -888,7 +888,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
if (myConnection != null) {
|
||||
myConnection.disconnect();
|
||||
}
|
||||
if (myDocument instanceof DocumentImpl) {
|
||||
if (myDocument instanceof DocumentImpl && !myUseNewRendering) {
|
||||
((DocumentImpl)myDocument).giveUpTabTracking();
|
||||
}
|
||||
Disposer.dispose(myDisposable);
|
||||
@@ -1955,7 +1955,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
}
|
||||
|
||||
public boolean hasTabs() {
|
||||
return !(myDocument instanceof DocumentImpl) || ((DocumentImpl)myDocument).mightContainTabs();
|
||||
return myUseNewRendering || !(myDocument instanceof DocumentImpl) || ((DocumentImpl)myDocument).mightContainTabs();
|
||||
}
|
||||
|
||||
public boolean isScrollToCaret() {
|
||||
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2000-2015 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.openapi.editor.impl.view;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
/**
|
||||
* Used for quick estimation of editor's viewable area size (without full text layout)
|
||||
*/
|
||||
class ApproximationFragment implements LineFragment {
|
||||
private final int myLength;
|
||||
private final int myColumnCount;
|
||||
private final float myWidth;
|
||||
|
||||
ApproximationFragment(int length, int columnCount, float charWidth) {
|
||||
myLength = length;
|
||||
myColumnCount = columnCount;
|
||||
myWidth = charWidth * columnCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLength() {
|
||||
return myLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLogicalColumnCount(int startColumn) {
|
||||
return myColumnCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVisualColumnCount(float startX) {
|
||||
return myColumnCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int logicalToVisualColumn(float startX, int startColumn, int column) {
|
||||
return column;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int visualToLogicalColumn(float startX, int startColumn, int column) {
|
||||
return column;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float visualColumnToX(float startX, int column) {
|
||||
return column < myColumnCount ? 0 : myWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] xToVisualColumn(float startX, float x) {
|
||||
float relX = x - startX;
|
||||
int column = relX < myWidth / 2 ? 0 : myColumnCount;
|
||||
return new int[] {column, relX <= visualColumnToX(startX, column) ? 0 : 1};
|
||||
}
|
||||
|
||||
@Override
|
||||
public float offsetToX(float startX, int startOffset, int offset) {
|
||||
return offset < myLength ? 0 : myWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Graphics2D g, float x, float y, int startColumn, int endColumn) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public LineFragment subFragment(int startOffset, int endOffset) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
+8
-56
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
package com.intellij.openapi.editor.impl.view;
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.editor.*;
|
||||
import com.intellij.openapi.editor.ex.util.EditorUtil;
|
||||
import com.intellij.openapi.editor.impl.EditorImpl;
|
||||
@@ -55,58 +54,11 @@ class EditorCoordinateMapper {
|
||||
|
||||
@NotNull
|
||||
LogicalPosition offsetToLogicalPosition(int offset) {
|
||||
int textLength = myDocument.getTextLength();
|
||||
if (offset <= 0 || textLength == 0) {
|
||||
return new LogicalPosition(0, 0);
|
||||
}
|
||||
offset = Math.min(offset, textLength);
|
||||
int line = myDocument.getLineNumber(offset);
|
||||
offset = Math.min(offset, myDocument.getLineEndOffset(line));
|
||||
int column = 0;
|
||||
if (ApplicationManager.getApplication().isDispatchThread()) {
|
||||
LineLayout lineLayout = myView.getTextLayoutCache().getLineLayout(line);
|
||||
column = lineLayout.offsetToLogicalColumn(offset - myDocument.getLineStartOffset(line));
|
||||
}
|
||||
else {
|
||||
CharSequence text = myDocument.getImmutableCharSequence();
|
||||
int tabSize = myView.getTabSize();
|
||||
for (int i = myDocument.getLineStartOffset(line); i < offset; i++) {
|
||||
if (text.charAt(i) == '\t') {
|
||||
column = (column / tabSize + 1) * tabSize;
|
||||
}
|
||||
else {
|
||||
column++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return new LogicalPosition(line, column);
|
||||
return myView.getLogicalPositionCache().offsetToLogicalPosition(offset);
|
||||
}
|
||||
|
||||
int logicalPositionToOffset(@NotNull LogicalPosition pos) {
|
||||
int line = pos.line;
|
||||
if (line >= myDocument.getLineCount()) return myDocument.getTextLength();
|
||||
|
||||
int lineStartOffset = myDocument.getLineStartOffset(line);
|
||||
if (ApplicationManager.getApplication().isDispatchThread()) {
|
||||
LineLayout lineLayout = myView.getTextLayoutCache().getLineLayout(line);
|
||||
return lineStartOffset + lineLayout.logicalColumnToOffset(pos.column);
|
||||
}
|
||||
else {
|
||||
int lineEndOffset = myDocument.getLineEndOffset(line);
|
||||
CharSequence text = myDocument.getImmutableCharSequence();
|
||||
int tabSize = myView.getTabSize();
|
||||
int column = 0;
|
||||
for (int i = lineStartOffset; i < lineEndOffset; i++) {
|
||||
if (text.charAt(i) == '\t') {
|
||||
column = (column / tabSize + 1) * tabSize;
|
||||
}
|
||||
else {
|
||||
column++;
|
||||
}
|
||||
if (pos.column < column) return i;
|
||||
}
|
||||
return lineEndOffset;
|
||||
}
|
||||
return myView.getLogicalPositionCache().logicalPositionToOffset(pos);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -121,7 +73,7 @@ class EditorCoordinateMapper {
|
||||
int visualLine = offsetToVisualLine(offset, beforeSoftWrap);
|
||||
int maxVisualColumn = 0;
|
||||
int maxLogicalColumn = 0;
|
||||
for (VisualLineFragmentsIterator.Fragment fragment : VisualLineFragmentsIterator.create(myView, offset, beforeSoftWrap)) {
|
||||
for (VisualLineFragmentsIterator.Fragment fragment : VisualLineFragmentsIterator.create(myView, offset, beforeSoftWrap, false)) {
|
||||
if (!pos.leansForward && offset == fragment.getVisualLineStartOffset()) {
|
||||
return new VisualPosition(visualLine, fragment.getStartVisualColumn());
|
||||
}
|
||||
@@ -173,7 +125,7 @@ class EditorCoordinateMapper {
|
||||
int maxVisualColumn = 0;
|
||||
int maxLogicalColumn = 0;
|
||||
int maxOffset = offset;
|
||||
for (VisualLineFragmentsIterator.Fragment fragment : VisualLineFragmentsIterator.create(myView, offset, false)) {
|
||||
for (VisualLineFragmentsIterator.Fragment fragment : VisualLineFragmentsIterator.create(myView, offset, false, false)) {
|
||||
int minColumn = fragment.getStartVisualColumn();
|
||||
int maxColumn = fragment.getEndVisualColumn();
|
||||
if (column < minColumn || column == minColumn && !pos.leansRight) {
|
||||
@@ -234,7 +186,7 @@ class EditorCoordinateMapper {
|
||||
return myDocument.getLineNumber(offset) - myFoldingModel.getFoldedLinesCountBefore(offset) + softWrapsBeforeOrAtOffset;
|
||||
}
|
||||
|
||||
private int visualLineToOffset(int visualLine) {
|
||||
int visualLineToOffset(int visualLine) {
|
||||
int start = 0;
|
||||
int end = myDocument.getTextLength();
|
||||
int current = 0;
|
||||
@@ -288,7 +240,7 @@ class EditorCoordinateMapper {
|
||||
if (visualLine < myView.getEditor().getVisibleLineCount()) {
|
||||
int visualLineStartOffset = visualLineToOffset(visualLine);
|
||||
int maxOffset = 0;
|
||||
for (VisualLineFragmentsIterator.Fragment fragment : VisualLineFragmentsIterator.create(myView, visualLineStartOffset, false)) {
|
||||
for (VisualLineFragmentsIterator.Fragment fragment : VisualLineFragmentsIterator.create(myView, visualLineStartOffset, false, false)) {
|
||||
if (p.x <= fragment.getStartX()) {
|
||||
int markerWidth = myView.getEditor().getSoftWrapModel().getMinDrawingWidthInPixels(SoftWrapDrawingType.AFTER_SOFT_WRAP);
|
||||
float indent = fragment.getStartX() - markerWidth;
|
||||
@@ -334,7 +286,7 @@ class EditorCoordinateMapper {
|
||||
if (visualLine < myView.getEditor().getVisibleLineCount()) {
|
||||
int visualLineStartOffset = visualLineToOffset(visualLine);
|
||||
int maxOffset = 0;
|
||||
for (VisualLineFragmentsIterator.Fragment fragment : VisualLineFragmentsIterator.create(myView, visualLineStartOffset, false)) {
|
||||
for (VisualLineFragmentsIterator.Fragment fragment : VisualLineFragmentsIterator.create(myView, visualLineStartOffset, false, false)) {
|
||||
if (column < fragment.getStartVisualColumn()) {
|
||||
break;
|
||||
}
|
||||
@@ -365,7 +317,7 @@ class EditorCoordinateMapper {
|
||||
float x = getStartX(logicalLine);
|
||||
if (myDocument.getTextLength() > 0) {
|
||||
boolean firstFragment = true;
|
||||
for (VisualLineFragmentsIterator.Fragment fragment : VisualLineFragmentsIterator.create(myView, offset, beforeSoftWrap)) {
|
||||
for (VisualLineFragmentsIterator.Fragment fragment : VisualLineFragmentsIterator.create(myView, offset, beforeSoftWrap, false)) {
|
||||
if (firstFragment && offset == visualLineStartOffset && !leanTowardsLargerOffsets) {
|
||||
x = fragment.getStartX();
|
||||
break;
|
||||
|
||||
@@ -465,7 +465,7 @@ class EditorPainter implements TextDrawingCallback {
|
||||
Collection<LineExtensionInfo> extensions = painter.getLineExtensions(project, virtualFile, line);
|
||||
if (extensions != null) {
|
||||
for (LineExtensionInfo info : extensions) {
|
||||
LineLayout layout = new LineLayout(myView, info.getText(), info.getFontType(), g.getFontRenderContext());
|
||||
LineLayout layout = new LineLayout(myView, info.getText(), info.getFontType());
|
||||
g.setColor(info.getColor());
|
||||
x = paintLineLayoutWithEffect(g, layout, x, y, info.getEffectColor(), info.getEffectType());
|
||||
int currentLineWidth = (int)x;
|
||||
@@ -584,7 +584,7 @@ class EditorPainter implements TextDrawingCallback {
|
||||
}
|
||||
}
|
||||
else {
|
||||
int maxWidth = myView.getMaxWidthInLineRange(startPosition.line, endPosition.line) - 1;
|
||||
int maxWidth = myView.getMaxWidthInLineRange(startPosition.line, endPosition.line, false) - 1;
|
||||
TFloatArrayList leadingRanges = adjustedLogicalRangeToVisualRanges(startOffset,
|
||||
myView.visualPositionToOffset(new VisualPosition(
|
||||
startPosition.line, Integer.MAX_VALUE, true)));
|
||||
@@ -691,7 +691,7 @@ class EditorPainter implements TextDrawingCallback {
|
||||
private TFloatArrayList logicalRangeToVisualRanges(int startOffset, int endOffset) {
|
||||
assert startOffset <= endOffset;
|
||||
TFloatArrayList result = new TFloatArrayList();
|
||||
for (VisualLineFragmentsIterator.Fragment fragment : VisualLineFragmentsIterator.create(myView, startOffset, false)) {
|
||||
for (VisualLineFragmentsIterator.Fragment fragment : VisualLineFragmentsIterator.create(myView, startOffset, false, false)) {
|
||||
int minOffset = fragment.getMinOffset();
|
||||
int maxOffset = fragment.getMaxOffset();
|
||||
if (startOffset == endOffset) {
|
||||
@@ -770,8 +770,9 @@ class EditorPainter implements TextDrawingCallback {
|
||||
g.fillRect(startX, y, width, lineHeight - 1);
|
||||
if (myDocument.getTextLength() > 0 && caret != null) {
|
||||
int targetVisualColumn = caret.getVisualPosition().column;
|
||||
for (VisualLineFragmentsIterator.Fragment fragment : VisualLineFragmentsIterator.create(myView,
|
||||
caret.getVisualLineStart(), false)) {
|
||||
for (VisualLineFragmentsIterator.Fragment fragment : VisualLineFragmentsIterator.create(myView,
|
||||
caret.getVisualLineStart(),
|
||||
false, false)) {
|
||||
int startVisualColumn = fragment.getStartVisualColumn();
|
||||
int endVisualColumn = fragment.getEndVisualColumn();
|
||||
if (startVisualColumn < targetVisualColumn && endVisualColumn > targetVisualColumn ||
|
||||
@@ -809,7 +810,7 @@ class EditorPainter implements TextDrawingCallback {
|
||||
int prevEndOffset = -1;
|
||||
boolean firstFragment = true;
|
||||
int maxColumn = 0;
|
||||
for (VisualLineFragmentsIterator.Fragment fragment : VisualLineFragmentsIterator.create(myView, offset, false)) {
|
||||
for (VisualLineFragmentsIterator.Fragment fragment : VisualLineFragmentsIterator.create(myView, offset, false, false)) {
|
||||
int fragmentStartOffset = fragment.getStartOffset();
|
||||
int start = fragmentStartOffset;
|
||||
int end = fragment.getEndOffset();
|
||||
|
||||
+33
-47
@@ -29,36 +29,35 @@ import com.intellij.openapi.editor.impl.EditorImpl;
|
||||
import com.intellij.openapi.editor.impl.softwrap.mapping.IncrementalCacheUpdateEvent;
|
||||
import com.intellij.openapi.editor.impl.softwrap.mapping.SoftWrapAwareDocumentParsingListenerAdapter;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import gnu.trove.TIntArrayList;
|
||||
import gnu.trove.TIntHashSet;
|
||||
import gnu.trove.TIntProcedure;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Calculates width (in pixels) of editor contents.
|
||||
*/
|
||||
class EditorSizeManager implements PrioritizedDocumentListener, Disposable, FoldingListener {
|
||||
private static final int UNKNOWN_WIDTH = Integer.MAX_VALUE;
|
||||
private static final int UNKNOWN_WIDTH = -1;
|
||||
|
||||
private final EditorView myView;
|
||||
private final EditorImpl myEditor;
|
||||
private final DocumentEx myDocument;
|
||||
|
||||
private final TIntArrayList myLineWidths = new TIntArrayList(); // cached widths of visual lines (in pixels)
|
||||
// negative value means an estimated (not precise) width
|
||||
// UNKNOWN_WIDTH(Integer.MAX_VALUE) means no value
|
||||
// UNKNOWN_WIDTH(-1) means no value
|
||||
private int myWidthInPixels;
|
||||
|
||||
private int myMaxLineWithExtensionWidth;
|
||||
private int myWidestLineWithExtension;
|
||||
|
||||
private boolean myApproximateWidthCalculated;
|
||||
private final TIntHashSet myLinesWithCreatedLayout = new TIntHashSet();
|
||||
|
||||
private final List<TextRange> myDeferredRanges = new ArrayList<TextRange>();
|
||||
|
||||
private final SoftWrapAwareDocumentParsingListenerAdapter mySoftWrapChangeListener = new SoftWrapAwareDocumentParsingListenerAdapter() {
|
||||
@Override
|
||||
public void onRecalculationEnd(@NotNull IncrementalCacheUpdateEvent event) {
|
||||
@@ -112,15 +111,11 @@ class EditorSizeManager implements PrioritizedDocumentListener, Disposable, Fold
|
||||
}
|
||||
foldingChangeStartOffset = Integer.MAX_VALUE;
|
||||
foldingChangeEndOffset = Integer.MIN_VALUE;
|
||||
|
||||
myLinesWithCreatedLayout.forEach(new TIntProcedure() {
|
||||
@Override
|
||||
public boolean execute(int logicalLine) {
|
||||
onLineLayoutCreated(logicalLine);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
myLinesWithCreatedLayout.clear();
|
||||
|
||||
for (TextRange range : myDeferredRanges) {
|
||||
onTextLayoutPerformed(range.getStartOffset(), range.getEndOffset());
|
||||
}
|
||||
myDeferredRanges.clear();
|
||||
}
|
||||
|
||||
private void onSoftWrapRecalculationEnd(IncrementalCacheUpdateEvent event) {
|
||||
@@ -128,7 +123,8 @@ class EditorSizeManager implements PrioritizedDocumentListener, Disposable, Fold
|
||||
}
|
||||
|
||||
Dimension getPreferredSize() {
|
||||
int width = getPreferredWidth();
|
||||
int widthWithoutCaret = getPreferredWidth();
|
||||
int width = widthWithoutCaret;
|
||||
if (!myDocument.isInBulkUpdate()) {
|
||||
for (Caret caret : myEditor.getCaretModel().getAllCarets()) {
|
||||
if (caret.isUpToDate()) {
|
||||
@@ -137,10 +133,18 @@ class EditorSizeManager implements PrioritizedDocumentListener, Disposable, Fold
|
||||
}
|
||||
}
|
||||
}
|
||||
width += myEditor.getSettings().getAdditionalColumnsCount() * myView.getPlainSpaceWidth();
|
||||
if (shouldRespectAdditionalColumns(widthWithoutCaret)) {
|
||||
width += myEditor.getSettings().getAdditionalColumnsCount() * myView.getPlainSpaceWidth();
|
||||
}
|
||||
return new Dimension(width, myEditor.getPreferredHeight());
|
||||
}
|
||||
|
||||
private boolean shouldRespectAdditionalColumns(int widthWithoutCaret) {
|
||||
return !myEditor.getSoftWrapModel().isSoftWrappingEnabled()
|
||||
|| myEditor.getSoftWrapModel().isRespectAdditionalColumns()
|
||||
|| widthWithoutCaret > myEditor.getScrollingModel().getVisibleArea().getWidth();
|
||||
}
|
||||
|
||||
private int getPreferredWidth() {
|
||||
if (myWidthInPixels < 0) {
|
||||
myWidthInPixels = calculatePreferredWidth();
|
||||
@@ -171,15 +175,7 @@ class EditorSizeManager implements PrioritizedDocumentListener, Disposable, Fold
|
||||
for (int i = 0; i < lineCount; i++) {
|
||||
int width = myLineWidths.get(i);
|
||||
if (width == UNKNOWN_WIDTH) {
|
||||
myApproximateWidthCalculated = false;
|
||||
myView.getTextLayoutCache().enableQuickLayouts(true);
|
||||
try {
|
||||
width = myView.getMaxWidthInLineRange(i, i);
|
||||
}
|
||||
finally {
|
||||
myView.getTextLayoutCache().enableQuickLayouts(false);
|
||||
}
|
||||
if (myApproximateWidthCalculated) width = - width;
|
||||
width = myView.getMaxWidthInLineRange(i, i, true);
|
||||
myLineWidths.set(i, width);
|
||||
}
|
||||
maxWidth = Math.max(maxWidth, Math.abs(width));
|
||||
@@ -217,38 +213,28 @@ class EditorSizeManager implements PrioritizedDocumentListener, Disposable, Fold
|
||||
myMaxLineWithExtensionWidth = width;
|
||||
}
|
||||
|
||||
void quickLineLayoutCreated() {
|
||||
myApproximateWidthCalculated = true;
|
||||
}
|
||||
|
||||
void lineLayoutCreated(int logicalLine) {
|
||||
void textLayoutPerformed(int startOffset, int endOffset) {
|
||||
if (myEditor.getFoldingModel().isInBatchFoldingOperation()) {
|
||||
myLinesWithCreatedLayout.add(logicalLine);
|
||||
myDeferredRanges.add(new TextRange(startOffset, endOffset));
|
||||
}
|
||||
else {
|
||||
onLineLayoutCreated(logicalLine);
|
||||
onTextLayoutPerformed(startOffset, endOffset);
|
||||
}
|
||||
}
|
||||
|
||||
private void onLineLayoutCreated(int logicalLine) {
|
||||
private void onTextLayoutPerformed(int startOffset, int endOffset) {
|
||||
boolean purePaintingMode = myEditor.isPurePaintingMode();
|
||||
boolean foldingEnabled = myEditor.getFoldingModel().isFoldingEnabled();
|
||||
myEditor.setPurePaintingMode(false);
|
||||
myEditor.getFoldingModel().setFoldingEnabled(true);
|
||||
try {
|
||||
int startVisualLine = myView.offsetToVisualLine(myDocument.getLineStartOffset(logicalLine), false);
|
||||
int endVisualLine = myView.offsetToVisualLine(myDocument.getLineEndOffset(logicalLine), false);
|
||||
boolean sizeInvalidated = false;
|
||||
int startVisualLine = myView.offsetToVisualLine(startOffset, false);
|
||||
int endVisualLine = myView.offsetToVisualLine(endOffset, true);
|
||||
for (int i = startVisualLine; i <= endVisualLine; i++) {
|
||||
if (myLineWidths.get(i) < 0) {
|
||||
myLineWidths.set(i, UNKNOWN_WIDTH);
|
||||
sizeInvalidated = true;
|
||||
}
|
||||
}
|
||||
if (sizeInvalidated) {
|
||||
myWidthInPixels = -1;
|
||||
myEditor.getContentComponent().revalidate();
|
||||
myLineWidths.set(i, UNKNOWN_WIDTH);
|
||||
}
|
||||
myWidthInPixels = -1;
|
||||
myEditor.getContentComponent().revalidate();
|
||||
}
|
||||
finally {
|
||||
myEditor.setPurePaintingMode(purePaintingMode);
|
||||
|
||||
@@ -55,6 +55,7 @@ public class EditorView implements TextDrawingCallback, Disposable {
|
||||
private final EditorCoordinateMapper myMapper;
|
||||
private final EditorSizeManager mySizeManager;
|
||||
private final TextLayoutCache myTextLayoutCache;
|
||||
private final LogicalPositionCache myLogicalPositionCache;
|
||||
private final TabFragment myTabFragment;
|
||||
|
||||
private String myPrefixText; // accessed only in EDT
|
||||
@@ -79,8 +80,10 @@ public class EditorView implements TextDrawingCallback, Disposable {
|
||||
myMapper = new EditorCoordinateMapper(this);
|
||||
mySizeManager = new EditorSizeManager(this);
|
||||
myTextLayoutCache = new TextLayoutCache(this);
|
||||
myLogicalPositionCache = new LogicalPositionCache(this);
|
||||
myTabFragment = new TabFragment(this);
|
||||
|
||||
Disposer.register(this, myLogicalPositionCache);
|
||||
Disposer.register(this, myTextLayoutCache);
|
||||
Disposer.register(this, mySizeManager);
|
||||
}
|
||||
@@ -108,6 +111,10 @@ public class EditorView implements TextDrawingCallback, Disposable {
|
||||
TabFragment getTabFragment() {
|
||||
return myTabFragment;
|
||||
}
|
||||
|
||||
LogicalPositionCache getLogicalPositionCache() {
|
||||
return myLogicalPositionCache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
@@ -191,7 +198,7 @@ public class EditorView implements TextDrawingCallback, Disposable {
|
||||
myPrefixText = prefixText;
|
||||
synchronized (myLock) {
|
||||
myPrefixLayout = prefixText == null || prefixText.isEmpty() ? null :
|
||||
new LineLayout(this, prefixText, attributes.getFontType(), myFontRenderContext);
|
||||
new LineLayout(this, prefixText, attributes.getFontType());
|
||||
}
|
||||
myPrefixAttributes = attributes;
|
||||
mySizeManager.invalidateRange(0, 0);
|
||||
@@ -231,18 +238,17 @@ public class EditorView implements TextDrawingCallback, Disposable {
|
||||
|
||||
public int getMaxWidthInRange(int startOffset, int endOffset) {
|
||||
assertIsDispatchThread();
|
||||
return getMaxWidthInLineRange(offsetToVisualLine(startOffset, false), offsetToVisualLine(endOffset, true));
|
||||
return getMaxWidthInLineRange(offsetToVisualLine(startOffset, false), offsetToVisualLine(endOffset, true), false);
|
||||
}
|
||||
|
||||
int getMaxWidthInLineRange(int startVisualLine, int endVisualLine) {
|
||||
int getMaxWidthInLineRange(int startVisualLine, int endVisualLine, boolean quick) {
|
||||
int maxWidth = 0;
|
||||
endVisualLine = Math.min(endVisualLine, myEditor.getVisibleLineCount() - 1);
|
||||
for (int i = startVisualLine; i <= endVisualLine; i++) {
|
||||
LogicalPosition startPosition = visualToLogicalPosition(new VisualPosition(i, 0));
|
||||
if (startPosition.line >= myDocument.getLineCount()) break;
|
||||
int startOffset = logicalPositionToOffset(startPosition);
|
||||
int startOffset = myMapper.visualLineToOffset(i);
|
||||
float x = 0;
|
||||
int maxOffset = 0;
|
||||
for (VisualLineFragmentsIterator.Fragment fragment : VisualLineFragmentsIterator.create(this, startOffset, false)) {
|
||||
for (VisualLineFragmentsIterator.Fragment fragment : VisualLineFragmentsIterator.create(this, startOffset, false, quick)) {
|
||||
x = fragment.getEndX();
|
||||
maxOffset = Math.max(maxOffset, fragment.getMaxOffset());
|
||||
}
|
||||
@@ -264,9 +270,11 @@ public class EditorView implements TextDrawingCallback, Disposable {
|
||||
myMaxCharWidth = -1;
|
||||
myTabSize = -1;
|
||||
}
|
||||
reset();
|
||||
setPrefix(myPrefixText, myPrefixAttributes); // recreate prefix layout
|
||||
myLogicalPositionCache.reset(false);
|
||||
myTextLayoutCache.resetToDocumentSize(false);
|
||||
invalidateFoldRegionLayouts();
|
||||
setPrefix(myPrefixText, myPrefixAttributes); // recreate prefix layout
|
||||
mySizeManager.reset();
|
||||
}
|
||||
|
||||
public void invalidateRange(int startOffset, int endOffset) {
|
||||
@@ -277,14 +285,18 @@ public class EditorView implements TextDrawingCallback, Disposable {
|
||||
}
|
||||
int startLine = myDocument.getLineNumber(Math.max(0, startOffset));
|
||||
int endLine = myDocument.getLineNumber(Math.min(textLength, endOffset));
|
||||
myTextLayoutCache.invalidateLines(startLine, endLine, endLine);
|
||||
myTextLayoutCache.invalidateLines(startLine, endLine);
|
||||
mySizeManager.invalidateRange(startOffset, endOffset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when document might have changed, but no notifications were sent (for a hacky document in EditorTextFieldCellRenderer)
|
||||
*/
|
||||
public void reset() {
|
||||
assertIsDispatchThread();
|
||||
myLogicalPositionCache.reset(true);
|
||||
myTextLayoutCache.resetToDocumentSize(true);
|
||||
mySizeManager.reset();
|
||||
myTextLayoutCache.resetToDocumentSize();
|
||||
}
|
||||
|
||||
public boolean isRtlLocation(@NotNull VisualPosition visualPosition) {
|
||||
@@ -422,8 +434,7 @@ public class EditorView implements TextDrawingCallback, Disposable {
|
||||
if (layout == null) {
|
||||
TextAttributes placeholderAttributes = myEditor.getFoldingModel().getPlaceholderAttributes();
|
||||
layout = new LineLayout(this, foldRegion.getPlaceholderText(),
|
||||
placeholderAttributes == null ? Font.PLAIN : placeholderAttributes.getFontType(),
|
||||
myFontRenderContext);
|
||||
placeholderAttributes == null ? Font.PLAIN : placeholderAttributes.getFontType());
|
||||
foldRegion.putUserData(FOLD_REGION_TEXT_LAYOUT, layout);
|
||||
}
|
||||
return layout;
|
||||
|
||||
@@ -32,10 +32,6 @@ interface LineFragment {
|
||||
int getLogicalColumnCount(int startColumn);
|
||||
|
||||
int getVisualColumnCount(float startX);
|
||||
|
||||
int offsetToLogicalColumn(int startColumn, int offset);
|
||||
|
||||
int logicalColumnToOffset(int startColumn, int column);
|
||||
|
||||
// columns are visual
|
||||
int logicalToVisualColumn(float startX, int startColumn, int column);
|
||||
|
||||
+166
-276
@@ -35,100 +35,84 @@ import java.util.List;
|
||||
|
||||
/**
|
||||
* Layout of a single line of document text. Consists of a series of BidiRuns, which, in turn, consist of TextFragments.
|
||||
* TextFragments within BidiRun are grouped into Chunks for performance reasons, glyph layout is performed per-Chunk, and only
|
||||
* for required Chunks.
|
||||
*/
|
||||
class LineLayout {
|
||||
private final BidiRun[] myBidiRunsInLogicalOrder;
|
||||
private final BidiRun[] myBidiRunsInVisualOrder;
|
||||
private final float myWidth;
|
||||
|
||||
/**
|
||||
* Creates a dummy layout used for editor size estimations
|
||||
*/
|
||||
LineLayout(EditorView view, int lineLength) {
|
||||
this(createDummyFragment(view, lineLength), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a layout for a fragment of text from editor.
|
||||
*/
|
||||
LineLayout(@NotNull EditorView view,
|
||||
int startOffset, int endOffset,
|
||||
@NotNull FontRenderContext fontRenderContext) {
|
||||
this(createFragments(view, startOffset, endOffset, fontRenderContext), false);
|
||||
LineLayout(@NotNull EditorView view, int startOffset, int endOffset, boolean skipBidiLayout) {
|
||||
this(createFragments(view, startOffset, endOffset, skipBidiLayout), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a layout for an arbitrary piece of text (using a common font style).
|
||||
*/
|
||||
LineLayout(@NotNull EditorView view,
|
||||
@NotNull CharSequence text, @JdkConstants.FontStyle int fontStyle,
|
||||
@NotNull FontRenderContext fontRenderContext) {
|
||||
this(createFragments(view, text, fontStyle, fontRenderContext), true);
|
||||
LineLayout(@NotNull EditorView view, @NotNull CharSequence text, @JdkConstants.FontStyle int fontStyle) {
|
||||
this(createFragments(view, text, fontStyle), true);
|
||||
}
|
||||
|
||||
private LineLayout(@NotNull List<BidiRun> runs, boolean calculateWidth) {
|
||||
myBidiRunsInLogicalOrder = runs.toArray(new BidiRun[runs.size()]);
|
||||
myBidiRunsInVisualOrder = myBidiRunsInLogicalOrder.clone();
|
||||
|
||||
reorderRunsVisually(myBidiRunsInVisualOrder);
|
||||
|
||||
int logicalColumn = 0;
|
||||
for (BidiRun run : runs) {
|
||||
logicalColumn = run.createIndices(logicalColumn);
|
||||
}
|
||||
reorderRunsVisually(myBidiRunsInVisualOrder);
|
||||
|
||||
myWidth = calculateWidth ? calculateWidth() : -1;
|
||||
}
|
||||
|
||||
private static void reorderRunsVisually(BidiRun[] bidiRunsInLogicalOrder) {
|
||||
if (bidiRunsInLogicalOrder.length > 1) {
|
||||
byte[] levels = new byte[bidiRunsInLogicalOrder.length];
|
||||
for (int i = 0; i < bidiRunsInLogicalOrder.length; i++) {
|
||||
levels[i] = bidiRunsInLogicalOrder[i].level;
|
||||
|
||||
// runs are supposed to be in logical order initially
|
||||
private static void reorderRunsVisually(BidiRun[] bidiRuns) {
|
||||
if (bidiRuns.length > 1) {
|
||||
byte[] levels = new byte[bidiRuns.length];
|
||||
for (int i = 0; i < bidiRuns.length; i++) {
|
||||
levels[i] = bidiRuns[i].level;
|
||||
}
|
||||
Bidi.reorderVisually(levels, 0, bidiRunsInLogicalOrder, 0, levels.length);
|
||||
Bidi.reorderVisually(levels, 0, bidiRuns, 0, levels.length);
|
||||
}
|
||||
}
|
||||
|
||||
static boolean isBidiLayoutRequired(@NotNull CharSequence text) {
|
||||
char[] chars = CharArrayUtil.fromSequence(text);
|
||||
return Bidi.requiresBidi(chars, 0, chars.length);
|
||||
}
|
||||
|
||||
private static List<BidiRun> createFragments(@NotNull EditorView view, int lineStartOffset, int lineEndOffset,
|
||||
@NotNull FontRenderContext fontRenderContext) {
|
||||
private static List<BidiRun> createFragments(@NotNull EditorView view, int lineStartOffset, int lineEndOffset, boolean skipBidiLayout) {
|
||||
if (lineEndOffset <= lineStartOffset) return Collections.emptyList();
|
||||
EditorImpl editor = view.getEditor();
|
||||
FontPreferences fontPreferences = editor.getColorsScheme().getFontPreferences();
|
||||
char[] chars = CharArrayUtil.fromSequence(editor.getDocument().getImmutableCharSequence(), lineStartOffset, lineEndOffset);
|
||||
List<BidiRun> runs = createRuns(editor, chars, lineStartOffset);
|
||||
for (BidiRun run : runs) {
|
||||
IterationState it = new IterationState(editor, lineStartOffset + run.startOffset, lineStartOffset + run.endOffset,
|
||||
false, false, false, false);
|
||||
while (!it.atEnd()) {
|
||||
addFragments(run, chars, it.getStartOffset() - lineStartOffset, it.getEndOffset() - lineStartOffset,
|
||||
it.getMergedAttributes().getFontType(), fontPreferences, fontRenderContext, view.getTabFragment());
|
||||
it.advance();
|
||||
}
|
||||
assert !run.fragments.isEmpty();
|
||||
List<BidiRun> runs;
|
||||
if (skipBidiLayout) {
|
||||
runs = Collections.singletonList(new BidiRun((byte)0, 0, lineEndOffset - lineStartOffset));
|
||||
}
|
||||
else {
|
||||
CharSequence text = editor.getDocument().getImmutableCharSequence().subSequence(lineStartOffset, lineEndOffset);
|
||||
char[] chars = CharArrayUtil.fromSequence(text);
|
||||
runs = createRuns(editor, chars, lineStartOffset);
|
||||
}
|
||||
return runs;
|
||||
}
|
||||
|
||||
private static List<BidiRun> createFragments(@NotNull EditorView view, @NotNull CharSequence text,
|
||||
@JdkConstants.FontStyle int fontStyle, @NotNull FontRenderContext fontRenderContext) {
|
||||
@JdkConstants.FontStyle int fontStyle) {
|
||||
if (text.length() == 0) return Collections.emptyList();
|
||||
EditorImpl editor = view.getEditor();
|
||||
FontRenderContext fontRenderContext = view.getFontRenderContext();
|
||||
FontPreferences fontPreferences = editor.getColorsScheme().getFontPreferences();
|
||||
char[] chars = CharArrayUtil.fromSequence(text);
|
||||
List<BidiRun> runs = createRuns(editor, chars, -1);
|
||||
for (BidiRun run : runs) {
|
||||
addFragments(run, chars, run.startOffset, run.endOffset, fontStyle, fontPreferences, fontRenderContext, null);
|
||||
assert !run.fragments.isEmpty();
|
||||
for (Chunk chunk : run.getChunks()) {
|
||||
addFragments(chunk, run.isRtl(), chars, chunk.startOffset, chunk.endOffset, fontStyle, fontPreferences, fontRenderContext, null);
|
||||
}
|
||||
}
|
||||
return runs;
|
||||
}
|
||||
|
||||
private static List<BidiRun> createDummyFragment(@NotNull EditorView view, int lineLength) {
|
||||
BidiRun run = new BidiRun((byte)0, 0, lineLength);
|
||||
run.fragments.add(new DummyLineFragment(lineLength, view.getMaxCharWidth()));
|
||||
return Collections.singletonList(run);
|
||||
}
|
||||
|
||||
private static List<BidiRun> createRuns(EditorImpl editor, char[] text, int startOffsetInEditor) {
|
||||
int textLength = text.length;
|
||||
if (editor.myDisableRtl) return Collections.singletonList(new BidiRun((byte)0, 0, textLength));
|
||||
@@ -186,37 +170,39 @@ class LineLayout {
|
||||
}
|
||||
}
|
||||
|
||||
private static void addFragments(BidiRun run, char[] text, int start, int end, int fontStyle,
|
||||
private static void addFragments(Chunk chunk, boolean isRtl, char[] text, int start, int end, int fontStyle,
|
||||
FontPreferences fontPreferences, FontRenderContext fontRenderContext,
|
||||
@Nullable TabFragment tabFragment) {
|
||||
assert start < end;
|
||||
Font currentFont = null;
|
||||
int currentIndex = start;
|
||||
for(int i = start; i < end; i++) {
|
||||
char c = text[i];
|
||||
if (c == '\t' && tabFragment != null) {
|
||||
assert run.level == 0;
|
||||
addTextFragmentIfNeeded(run, text, currentIndex, i, currentFont, fontRenderContext, run.isRtl());
|
||||
run.fragments.add(tabFragment);
|
||||
assert !isRtl;
|
||||
addTextFragmentIfNeeded(chunk, text, currentIndex, i, currentFont, fontRenderContext, false);
|
||||
chunk.fragments.add(tabFragment);
|
||||
currentFont = null;
|
||||
currentIndex = i + 1;
|
||||
}
|
||||
else {
|
||||
Font font = ComplementaryFontsRegistry.getFontAbleToDisplay(c, fontStyle, fontPreferences).getFont();
|
||||
if (!font.equals(currentFont)) {
|
||||
addTextFragmentIfNeeded(run, text, currentIndex, i, currentFont, fontRenderContext, run.isRtl());
|
||||
addTextFragmentIfNeeded(chunk, text, currentIndex, i, currentFont, fontRenderContext, isRtl);
|
||||
currentFont = font;
|
||||
currentIndex = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
addTextFragmentIfNeeded(run, text, currentIndex, end, currentFont, fontRenderContext, run.isRtl());
|
||||
addTextFragmentIfNeeded(chunk, text, currentIndex, end, currentFont, fontRenderContext, isRtl);
|
||||
assert !chunk.fragments.isEmpty();
|
||||
}
|
||||
|
||||
private static void addTextFragmentIfNeeded(BidiRun run, char[] chars, int from, int to, Font font,
|
||||
private static void addTextFragmentIfNeeded(Chunk chunk, char[] chars, int from, int to, Font font,
|
||||
FontRenderContext fontRenderContext, boolean isRtl) {
|
||||
if (to > from) {
|
||||
assert font != null;
|
||||
run.fragments.add(new TextFragment(chars, from, to, isRtl, font, fontRenderContext));
|
||||
chunk.fragments.add(new TextFragment(chars, from, to, isRtl, font, fontRenderContext));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,38 +219,24 @@ class LineLayout {
|
||||
return myWidth;
|
||||
}
|
||||
|
||||
int offsetToLogicalColumn(int offset) {
|
||||
if (offset <= 0) return 0;
|
||||
for (BidiRun run : myBidiRunsInLogicalOrder) {
|
||||
if (offset <= run.endOffset) return run.offsetToLogicalColumn(offset);
|
||||
}
|
||||
return myBidiRunsInLogicalOrder.length == 0 ? 0 : myBidiRunsInLogicalOrder[myBidiRunsInLogicalOrder.length - 1].endLogicalColumn;
|
||||
}
|
||||
|
||||
int logicalColumnToOffset(int logicalColumn) {
|
||||
if (logicalColumn <= 0) return 0;
|
||||
for (BidiRun run : myBidiRunsInLogicalOrder) {
|
||||
if (logicalColumn <= run.endLogicalColumn) return run.logicalColumnToOffset(logicalColumn);
|
||||
}
|
||||
return myBidiRunsInLogicalOrder.length == 0 ? 0 : myBidiRunsInLogicalOrder[myBidiRunsInLogicalOrder.length - 1].endOffset;
|
||||
}
|
||||
|
||||
Iterable<VisualFragment> getFragmentsInVisualOrder(final float startX) {
|
||||
return new Iterable<VisualFragment>() {
|
||||
@Override
|
||||
public Iterator<VisualFragment> iterator() {
|
||||
return new VisualOrderIterator(startX, 0, 0, 0, myBidiRunsInVisualOrder);
|
||||
return new VisualOrderIterator(null, 0, startX, 0, 0, 0, myBidiRunsInVisualOrder);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Iterable<VisualFragment> getFragmentsInVisualOrder(final float startX,
|
||||
final int startVisualColumn,
|
||||
final int startOffset,
|
||||
int endOffset) {
|
||||
Iterable<VisualFragment> getFragmentsInVisualOrder(@NotNull final EditorView view,
|
||||
final int lineStartOffset,
|
||||
final float startX,
|
||||
final int startVisualColumn,
|
||||
final int startOffset,
|
||||
int endOffset,
|
||||
boolean quick) {
|
||||
assert startOffset <= endOffset;
|
||||
final BidiRun[] runs;
|
||||
int startLogicalColumn = 0;
|
||||
if (startOffset == endOffset) {
|
||||
runs = new BidiRun[0];
|
||||
}
|
||||
@@ -273,19 +245,16 @@ class LineLayout {
|
||||
for (BidiRun run : myBidiRunsInLogicalOrder) {
|
||||
if (run.endOffset <= startOffset) continue;
|
||||
if (run.startOffset >= endOffset) break;
|
||||
runList.add(run.subRun(startOffset, endOffset));
|
||||
if (run.startOffset <= startOffset) {
|
||||
startLogicalColumn = run.offsetToLogicalColumn(startOffset);
|
||||
}
|
||||
runList.add(run.subRun(view, lineStartOffset, startOffset, endOffset, quick));
|
||||
}
|
||||
runs = runList.toArray(new BidiRun[runList.size()]);
|
||||
reorderRunsVisually(runs);
|
||||
}
|
||||
final int finalStartLogicalColumn = startLogicalColumn;
|
||||
final int startLogicalColumn = view.offsetToLogicalPosition(lineStartOffset + startOffset).column;
|
||||
return new Iterable<VisualFragment>() {
|
||||
@Override
|
||||
public Iterator<VisualFragment> iterator() {
|
||||
return new VisualOrderIterator(startX, startVisualColumn, finalStartLogicalColumn, startOffset, runs);
|
||||
return new VisualOrderIterator(view, lineStartOffset, startX, startVisualColumn, startLogicalColumn, startOffset, runs);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -332,17 +301,12 @@ class LineLayout {
|
||||
}
|
||||
|
||||
private static class BidiRun {
|
||||
private static final int CHUNK_CHARACTERS = 1024;
|
||||
|
||||
private final byte level;
|
||||
private final int startOffset;
|
||||
private final int endOffset;
|
||||
private final List<LineFragment> fragments = new ArrayList<LineFragment>(); // in logical order
|
||||
|
||||
// values cached for performance
|
||||
private static final int INDEX_FREQUENCY = 100;
|
||||
private int startLogicalColumn;
|
||||
private int endLogicalColumn;
|
||||
private int[] offsetIndex; // stores start offsets for each INDEX_FREQUENCY-th fragment, for faster lookup by offset
|
||||
private int[] logicalColumnIndex; // stores start logical column for each INDEX_FREQUENCY-th fragment, for faster offset <-> logical column conversions
|
||||
private Chunk[] chunks; // in logical order
|
||||
|
||||
private BidiRun(byte level, int startOffset, int endOffset) {
|
||||
this.level = level;
|
||||
@@ -353,124 +317,108 @@ class LineLayout {
|
||||
private boolean isRtl() {
|
||||
return (level & 1) != 0;
|
||||
}
|
||||
|
||||
private BidiRun subRun(int targetStartOffset, int targetEndOffset) {
|
||||
assert targetStartOffset < endOffset;
|
||||
assert targetEndOffset > startOffset;
|
||||
if (targetStartOffset <= startOffset && targetEndOffset >= this.endOffset) {
|
||||
return this;
|
||||
}
|
||||
int start = Math.max(startOffset, targetStartOffset);
|
||||
int end = Math.min(endOffset, targetEndOffset);
|
||||
BidiRun run = new BidiRun(level, start, end);
|
||||
int offset = startOffset;
|
||||
int startFragment = 0;
|
||||
if (offsetIndex != null) {
|
||||
int pos = Arrays.binarySearch(offsetIndex, start);
|
||||
if (pos < 0) pos = - pos - 2;
|
||||
if (pos >= 0) {
|
||||
startFragment = (pos + 1) * INDEX_FREQUENCY;
|
||||
offset = offsetIndex[pos];
|
||||
|
||||
private Chunk[] getChunks() {
|
||||
if (chunks == null) {
|
||||
int chunkCount = (endOffset - startOffset + CHUNK_CHARACTERS - 1) / CHUNK_CHARACTERS;
|
||||
chunks = new Chunk[chunkCount];
|
||||
for (int i = 0; i < chunkCount; i++) {
|
||||
int from = startOffset + i * CHUNK_CHARACTERS;
|
||||
int to = (i == chunkCount - 1) ? endOffset : from + CHUNK_CHARACTERS;
|
||||
Chunk chunk = new Chunk(from, to);
|
||||
chunks[i] = chunk;
|
||||
}
|
||||
}
|
||||
for (int i = startFragment; i < fragments.size(); i++) {
|
||||
LineFragment fragment = fragments.get(i);
|
||||
return chunks;
|
||||
}
|
||||
|
||||
private BidiRun subRun(@NotNull EditorView view, int lineStartOffset, int targetStartOffset, int targetEndOffset, boolean quick) {
|
||||
assert targetStartOffset < endOffset;
|
||||
assert targetEndOffset > startOffset;
|
||||
int start = Math.max(startOffset, targetStartOffset);
|
||||
int end = Math.min(endOffset, targetEndOffset);
|
||||
BidiRun subRun = new BidiRun(level, start, end);
|
||||
List<Chunk> subChunks = new ArrayList<Chunk>();
|
||||
for (Chunk chunk : getChunks()) {
|
||||
if (chunk.endOffset <= start) continue;
|
||||
if (chunk.startOffset >= end) break;
|
||||
subChunks.add(chunk.subChunk(view, lineStartOffset, isRtl(), start, end, quick));
|
||||
}
|
||||
subRun.chunks = subChunks.toArray(new Chunk[subChunks.size()]);
|
||||
return subRun;
|
||||
}
|
||||
}
|
||||
|
||||
private static class Chunk {
|
||||
private final List<LineFragment> fragments = new ArrayList<LineFragment>(); // in logical order
|
||||
private int startOffset;
|
||||
private int endOffset;
|
||||
|
||||
private Chunk(int startOffset, int endOffset) {
|
||||
this.startOffset = startOffset;
|
||||
this.endOffset = endOffset;
|
||||
}
|
||||
|
||||
private void ensureLayout(@NotNull EditorView view, int lineStartOffset, boolean isRtl) {
|
||||
if (!fragments.isEmpty()) return;
|
||||
int start = lineStartOffset + startOffset;
|
||||
int end = lineStartOffset + endOffset;
|
||||
IterationState it = new IterationState(view.getEditor(), start, end, false, false, false, false);
|
||||
FontPreferences fontPreferences = view.getEditor().getColorsScheme().getFontPreferences();
|
||||
char[] chars = CharArrayUtil.fromSequence(view.getEditor().getDocument().getImmutableCharSequence(), start, end);
|
||||
while (!it.atEnd()) {
|
||||
addFragments(this, isRtl, chars, it.getStartOffset() - start, it.getEndOffset() - start,
|
||||
it.getMergedAttributes().getFontType(), fontPreferences, view.getFontRenderContext(), view.getTabFragment());
|
||||
it.advance();
|
||||
}
|
||||
view.getSizeManager().textLayoutPerformed(start, end);
|
||||
assert !fragments.isEmpty();
|
||||
}
|
||||
|
||||
private Chunk subChunk(EditorView view, int lineStartOffset, boolean isRtl, int targetStartOffset, int targetEndOffset, boolean quick) {
|
||||
assert targetStartOffset < endOffset;
|
||||
assert targetEndOffset > startOffset;
|
||||
int start = Math.max(startOffset, targetStartOffset);
|
||||
int end = Math.min(endOffset, targetEndOffset);
|
||||
if (quick && fragments.isEmpty()) {
|
||||
Chunk chunk = new Chunk(start, end);
|
||||
int startColumn = view.offsetToLogicalPosition(lineStartOffset + start).column;
|
||||
int endColumn = view.offsetToLogicalPosition(lineStartOffset + end).column;
|
||||
chunk.fragments.add(new ApproximationFragment(end - start, endColumn - startColumn, view.getMaxCharWidth()));
|
||||
return chunk;
|
||||
}
|
||||
if (start == startOffset && end == this.endOffset) {
|
||||
return this;
|
||||
}
|
||||
ensureLayout(view, lineStartOffset, isRtl);
|
||||
Chunk chunk = new Chunk(start, end);
|
||||
int offset = startOffset;
|
||||
for (LineFragment fragment : fragments) {
|
||||
if (end <= offset) break;
|
||||
int endOffset = offset + fragment.getLength();
|
||||
if (start < endOffset) {
|
||||
run.fragments.add(fragment.subFragment(Math.max(start, offset) - offset, Math.min(end, endOffset) - offset));
|
||||
chunk.fragments.add(fragment.subFragment(Math.max(start, offset) - offset, Math.min(end, endOffset) - offset));
|
||||
}
|
||||
offset = endOffset;
|
||||
}
|
||||
return run;
|
||||
}
|
||||
|
||||
private int logicalColumnToOffset(int logicalColumn) {
|
||||
if (logicalColumn <= startLogicalColumn) return startOffset;
|
||||
if (logicalColumn >= endLogicalColumn) return endOffset;
|
||||
int currentOffset = startOffset;
|
||||
int currentLogicalColumn = startLogicalColumn;
|
||||
int startFragment = 0;
|
||||
if (offsetIndex != null) {
|
||||
int pos = Arrays.binarySearch(logicalColumnIndex == null ? offsetIndex : logicalColumnIndex, logicalColumn);
|
||||
if (pos >= 0) return offsetIndex[pos];
|
||||
int beforeInsertPosition = - pos - 2;
|
||||
currentOffset = beforeInsertPosition < 0 ? startOffset : offsetIndex[beforeInsertPosition];
|
||||
currentLogicalColumn = beforeInsertPosition < 0 ? startLogicalColumn : logicalColumnIndex == null ? currentOffset :
|
||||
logicalColumnIndex[beforeInsertPosition];
|
||||
startFragment = INDEX_FREQUENCY * (beforeInsertPosition + 1);
|
||||
}
|
||||
for (int i = startFragment; i < fragments.size(); i++) {
|
||||
LineFragment fragment = fragments.get(i);
|
||||
int endLogicalColumn = currentLogicalColumn + fragment.getLogicalColumnCount(currentLogicalColumn);
|
||||
if (logicalColumn <= endLogicalColumn) return currentOffset + fragment.logicalColumnToOffset(currentLogicalColumn, logicalColumn);
|
||||
currentOffset += fragment.getLength();
|
||||
currentLogicalColumn = endLogicalColumn;
|
||||
}
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
private int offsetToLogicalColumn(int offset) {
|
||||
if (offset <= startOffset) return startLogicalColumn;
|
||||
if (offset >= endOffset) return endLogicalColumn;
|
||||
int currentOffset = startOffset;
|
||||
int currentLogicalColumn = startLogicalColumn;
|
||||
int startFragment = 0;
|
||||
if (offsetIndex != null) {
|
||||
int pos = Arrays.binarySearch(offsetIndex, offset);
|
||||
if (pos >= 0) return logicalColumnIndex == null ? offset : logicalColumnIndex[pos];
|
||||
int beforeInsertPosition = - pos - 2;
|
||||
currentOffset = beforeInsertPosition < 0 ? startOffset : offsetIndex[beforeInsertPosition];
|
||||
currentLogicalColumn = beforeInsertPosition < 0 ? startLogicalColumn : logicalColumnIndex == null ? currentOffset :
|
||||
logicalColumnIndex[beforeInsertPosition];
|
||||
startFragment = INDEX_FREQUENCY * (beforeInsertPosition + 1);
|
||||
}
|
||||
for (int i = startFragment; i < fragments.size(); i++) {
|
||||
LineFragment fragment = fragments.get(i);
|
||||
int endOffset = currentOffset + fragment.getLength();
|
||||
if (offset <= endOffset) return fragment.offsetToLogicalColumn(currentLogicalColumn, offset - currentOffset);
|
||||
currentOffset = endOffset;
|
||||
currentLogicalColumn += fragment.getLogicalColumnCount(currentLogicalColumn);
|
||||
}
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
private int createIndices(int startLogicalColumn) {
|
||||
this.startLogicalColumn = endLogicalColumn = startLogicalColumn;
|
||||
int indexSize = (fragments.size() - 1) / INDEX_FREQUENCY;
|
||||
if (indexSize > 0) {
|
||||
offsetIndex = new int[indexSize];
|
||||
}
|
||||
int i = 0;
|
||||
int offset = startOffset;
|
||||
for (LineFragment fragment : fragments) {
|
||||
if (offsetIndex != null && i > 0 && (i % INDEX_FREQUENCY) == 0) {
|
||||
int pos = i / INDEX_FREQUENCY - 1;
|
||||
offsetIndex[pos] = offset;
|
||||
if (logicalColumnIndex == null && offset != endLogicalColumn) {
|
||||
logicalColumnIndex = new int[indexSize];
|
||||
System.arraycopy(offsetIndex, 0, logicalColumnIndex, 0, pos);
|
||||
}
|
||||
if (logicalColumnIndex != null) {
|
||||
logicalColumnIndex[pos] = endLogicalColumn;
|
||||
}
|
||||
}
|
||||
i++;
|
||||
offset += fragment.getLength();
|
||||
endLogicalColumn += fragment.getLogicalColumnCount(endLogicalColumn);
|
||||
}
|
||||
return endLogicalColumn;
|
||||
return chunk;
|
||||
}
|
||||
}
|
||||
|
||||
private static class VisualOrderIterator implements Iterator<VisualFragment> {
|
||||
private BidiRun[] myRuns;
|
||||
private final EditorView myView;
|
||||
private final int myLineStartOffset;
|
||||
private final BidiRun[] myRuns;
|
||||
private int myRunIndex = 0;
|
||||
private int myChunkIndex = 0;
|
||||
private int myFragmentIndex = 0;
|
||||
private int myOffsetInsideRun = 0;
|
||||
private VisualFragment myFragment = new VisualFragment();
|
||||
|
||||
private VisualOrderIterator(float startX, int startVisualColumn, int startLogicalColumn, int startOffset, BidiRun[] runsInVisualOrder) {
|
||||
private VisualOrderIterator(EditorView view, int lineStartOffset,
|
||||
float startX, int startVisualColumn, int startLogicalColumn, int startOffset, BidiRun[] runsInVisualOrder) {
|
||||
myView = view;
|
||||
myLineStartOffset = lineStartOffset;
|
||||
myRuns = runsInVisualOrder;
|
||||
myFragment.startX = startX;
|
||||
myFragment.startVisualColumn = startVisualColumn;
|
||||
@@ -480,7 +428,15 @@ class LineLayout {
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return myRunIndex < myRuns.length && myFragmentIndex < myRuns[myRunIndex].fragments.size();
|
||||
if (myRunIndex >= myRuns.length) return false;
|
||||
BidiRun run = myRuns[myRunIndex];
|
||||
Chunk[] chunks = run.getChunks();
|
||||
if (myChunkIndex >= chunks.length) return false;
|
||||
Chunk chunk = chunks[run.isRtl() ? chunks.length - 1 - myChunkIndex : myChunkIndex];
|
||||
if (myView != null) {
|
||||
chunk.ensureLayout(myView, myLineStartOffset, run.isRtl());
|
||||
}
|
||||
return myFragmentIndex < chunk.fragments.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -490,12 +446,12 @@ class LineLayout {
|
||||
}
|
||||
BidiRun run = myRuns[myRunIndex];
|
||||
|
||||
if (myRunIndex == 0 && myFragmentIndex == 0) {
|
||||
if (myRunIndex == 0 && myChunkIndex == 0 && myFragmentIndex == 0) {
|
||||
myFragment.startLogicalColumn += (run.isRtl() ? run.endOffset : run.startOffset) - myFragment.startOffset;
|
||||
}
|
||||
else {
|
||||
myFragment.startLogicalColumn = myFragment.getEndLogicalColumn();
|
||||
if (myFragmentIndex == 0) {
|
||||
if (myChunkIndex == 0 && myFragmentIndex == 0) {
|
||||
myFragment.startLogicalColumn += (run.isRtl() ? run.endOffset : run.startOffset) - myFragment.getEndOffset();
|
||||
}
|
||||
myFragment.startVisualColumn = myFragment.getEndVisualColumn();
|
||||
@@ -503,15 +459,22 @@ class LineLayout {
|
||||
}
|
||||
|
||||
myFragment.isRtl = run.isRtl();
|
||||
myFragment.delegate = run.fragments.get(run.isRtl() ? run.fragments.size() - 1 - myFragmentIndex : myFragmentIndex);
|
||||
Chunk[] chunks = run.getChunks();
|
||||
Chunk chunk = chunks[run.isRtl() ? chunks.length - 1 - myChunkIndex : myChunkIndex];
|
||||
assert !chunk.fragments.isEmpty();
|
||||
myFragment.delegate = chunk.fragments.get(run.isRtl() ? chunk.fragments.size() - 1 - myFragmentIndex : myFragmentIndex);
|
||||
myFragment.startOffset = run.isRtl() ? run.endOffset - myOffsetInsideRun : run.startOffset + myOffsetInsideRun;
|
||||
|
||||
myOffsetInsideRun += myFragment.getLength();
|
||||
myFragmentIndex++;
|
||||
if (myFragmentIndex >= run.fragments.size()) {
|
||||
if (myFragmentIndex >= chunk.fragments.size()) {
|
||||
myFragmentIndex = 0;
|
||||
myOffsetInsideRun = 0;
|
||||
myRunIndex++;
|
||||
myChunkIndex++;
|
||||
if (myChunkIndex >= chunks.length) {
|
||||
myChunkIndex = 0;
|
||||
myOffsetInsideRun = 0;
|
||||
myRunIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
return myFragment;
|
||||
@@ -649,77 +612,4 @@ class LineLayout {
|
||||
return isRtl ? startOffset - offset : offset - startOffset;
|
||||
}
|
||||
}
|
||||
|
||||
public static class DummyLineFragment implements LineFragment {
|
||||
private final int myLength;
|
||||
private final float myCharWidth;
|
||||
|
||||
public DummyLineFragment(int length, float charWidth) {
|
||||
myLength = length;
|
||||
myCharWidth = charWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLength() {
|
||||
return myLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLogicalColumnCount(int startColumn) {
|
||||
return myLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVisualColumnCount(float startX) {
|
||||
return myLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int offsetToLogicalColumn(int startColumn, int offset) {
|
||||
return startColumn + offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int logicalColumnToOffset(int startColumn, int column) {
|
||||
return column - startColumn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int logicalToVisualColumn(float startX, int startColumn, int column) {
|
||||
return column;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int visualToLogicalColumn(float startX, int startColumn, int column) {
|
||||
return column;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float visualColumnToX(float startX, int column) {
|
||||
return column * myCharWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] xToVisualColumn(float startX, float x) {
|
||||
float relX = x - startX;
|
||||
int column = Math.max(0, Math.min(myLength, Math.round(relX / myCharWidth)));
|
||||
return new int[] {column, relX <= column * myCharWidth ? 0 : 1};
|
||||
}
|
||||
|
||||
@Override
|
||||
public float offsetToX(float startX, int startOffset, int offset) {
|
||||
return offset * myCharWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Graphics2D g, float x, float y, int startColumn, int endColumn) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public LineFragment subFragment(int startOffset, int endOffset) {
|
||||
return new DummyLineFragment(endOffset - startOffset, myCharWidth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+205
@@ -0,0 +1,205 @@
|
||||
/*
|
||||
* Copyright 2000-2015 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.openapi.editor.impl.view;
|
||||
|
||||
import com.intellij.openapi.Disposable;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.editor.LogicalPosition;
|
||||
import com.intellij.openapi.editor.event.DocumentEvent;
|
||||
import com.intellij.openapi.editor.ex.PrioritizedDocumentListener;
|
||||
import com.intellij.openapi.editor.impl.EditorDocumentPriorities;
|
||||
import com.intellij.util.text.CharArrayUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* Caches information allowing faster offset<->logicalPosition conversions even for long lines.
|
||||
* Requests for conversion can be made from under read action, document changes and cache invalidation should be done in EDT.
|
||||
*/
|
||||
@SuppressWarnings("SynchronizeOnThis")
|
||||
class LogicalPositionCache implements PrioritizedDocumentListener, Disposable {
|
||||
private final Document myDocument;
|
||||
private final EditorView myView;
|
||||
private ArrayList<LineData> myLines = new ArrayList<LineData>();
|
||||
private int myTabSize = -1;
|
||||
private int myDocumentChangeOldEndLine;
|
||||
|
||||
LogicalPositionCache(EditorView view) {
|
||||
myView = view;
|
||||
myDocument = view.getEditor().getDocument();
|
||||
myDocument.addDocumentListener(this, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return EditorDocumentPriorities.LOGICAL_POSITION_CACHE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeDocumentChange(DocumentEvent event) {
|
||||
myDocumentChangeOldEndLine = getAdjustedLineNumber(event.getOffset() + event.getOldLength());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void documentChanged(DocumentEvent event) {
|
||||
int startLine = myDocument.getLineNumber(event.getOffset());
|
||||
int newEndLine = getAdjustedLineNumber(event.getOffset() + event.getNewLength());
|
||||
invalidateLines(startLine, myDocumentChangeOldEndLine, newEndLine, CharArrayUtil.indexOf(event.getNewFragment(), "\t", 0) == -1);
|
||||
}
|
||||
|
||||
synchronized void reset(boolean force) {
|
||||
checkDisposed();
|
||||
int oldTabSize = myTabSize;
|
||||
myTabSize = myView.getTabSize();
|
||||
if (force || oldTabSize != myTabSize) {
|
||||
invalidateLines(0, myLines.size() - 1, myDocument.getLineCount() - 1, true);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
synchronized LogicalPosition offsetToLogicalPosition(int offset) {
|
||||
int textLength = myDocument.getTextLength();
|
||||
if (offset <= 0 || textLength == 0) {
|
||||
return new LogicalPosition(0, 0);
|
||||
}
|
||||
offset = Math.min(offset, textLength);
|
||||
int line = myDocument.getLineNumber(offset);
|
||||
LineData lineData = getLineInfo(line);
|
||||
return new LogicalPosition(line, lineData.offsetToLogicalColumn(myDocument, line, myTabSize, offset));
|
||||
}
|
||||
|
||||
synchronized int logicalPositionToOffset(@NotNull LogicalPosition pos) {
|
||||
int line = pos.line;
|
||||
if (line >= myDocument.getLineCount()) return myDocument.getTextLength();
|
||||
LineData lineData = getLineInfo(line);
|
||||
return lineData.logicalColumnToOffset(myDocument, line, myTabSize, pos.column);
|
||||
}
|
||||
|
||||
private int getAdjustedLineNumber(int offset) {
|
||||
return myDocument.getTextLength() == 0 ? -1 : myDocument.getLineNumber(offset);
|
||||
}
|
||||
|
||||
private synchronized void invalidateLines(int startLine, int oldEndLine, int newEndLine, boolean preserveTrivialLines) {
|
||||
checkDisposed();
|
||||
int endLine = Math.min(oldEndLine, newEndLine);
|
||||
for (int line = startLine; line <= endLine; line++) {
|
||||
LineData data = myLines.get(line);
|
||||
if (data != null && (!preserveTrivialLines || data.columnCache != null)) myLines.set(line, null);
|
||||
}
|
||||
if (oldEndLine < newEndLine) {
|
||||
myLines.addAll(oldEndLine + 1, Collections.nCopies(newEndLine - oldEndLine, (LineData)null));
|
||||
} else if (oldEndLine > newEndLine) {
|
||||
myLines.subList(newEndLine + 1, oldEndLine + 1).clear();
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private LineData getLineInfo(int line) {
|
||||
checkDisposed();
|
||||
LineData result = myLines.get(line);
|
||||
if (result == null) {
|
||||
result = new LineData(myDocument, line, myTabSize);
|
||||
myLines.set(line, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void dispose() {
|
||||
myLines = null;
|
||||
}
|
||||
|
||||
private void checkDisposed() {
|
||||
if (myLines == null) throw new IllegalStateException("Editor is already disposed");
|
||||
}
|
||||
|
||||
private static class LineData {
|
||||
private static final int CACHE_FREQUENCY = 1024; // logical column will be cached for each CACHE_FREQUENCY-th character on the line
|
||||
|
||||
private final int[] columnCache;
|
||||
|
||||
private LineData(@NotNull Document document, int line, int tabSize) {
|
||||
int start = document.getLineStartOffset(line);
|
||||
int end = document.getLineEndOffset(line);
|
||||
int cacheSize = (end - start) / CACHE_FREQUENCY;
|
||||
int[] cache = new int[cacheSize];
|
||||
CharSequence text = document.getImmutableCharSequence();
|
||||
int column = 0;
|
||||
boolean hasTabs = false;
|
||||
for (int i = start; i < end; i++) {
|
||||
if (i > start && (i - start) % CACHE_FREQUENCY == 0) {
|
||||
cache[(i - start) / CACHE_FREQUENCY - 1] = column;
|
||||
}
|
||||
if (text.charAt(i) == '\t') {
|
||||
column = (column / tabSize + 1) * tabSize;
|
||||
hasTabs = true;
|
||||
}
|
||||
else {
|
||||
column++;
|
||||
}
|
||||
}
|
||||
columnCache = hasTabs ? cache : null;
|
||||
}
|
||||
|
||||
private int offsetToLogicalColumn(@NotNull Document document, int line, int tabSize, int offset) {
|
||||
offset = Math.min(offset, document.getLineEndOffset(line));
|
||||
int lineStartOffset = document.getLineStartOffset(line);
|
||||
int relOffset = offset - lineStartOffset;
|
||||
if (columnCache == null) return relOffset;
|
||||
int cacheIndex = relOffset / CACHE_FREQUENCY;
|
||||
int startOffset = lineStartOffset + cacheIndex * CACHE_FREQUENCY;
|
||||
int column = cacheIndex == 0 ? 0 : columnCache[cacheIndex - 1];
|
||||
CharSequence text = document.getImmutableCharSequence();
|
||||
for (int i = startOffset; i < offset; i++) {
|
||||
if (text.charAt(i) == '\t') {
|
||||
column = (column / tabSize + 1) * tabSize;
|
||||
}
|
||||
else {
|
||||
column++;
|
||||
}
|
||||
}
|
||||
return column;
|
||||
}
|
||||
|
||||
private int logicalColumnToOffset(@NotNull Document document, int line, int tabSize, int logicalColumn) {
|
||||
int lineStartOffset = document.getLineStartOffset(line);
|
||||
int lineEndOffset = document.getLineEndOffset(line);
|
||||
if (columnCache == null) {
|
||||
int result = lineStartOffset + logicalColumn;
|
||||
return result < 0 || // guarding over overflow
|
||||
result > lineEndOffset ? lineEndOffset : result;
|
||||
}
|
||||
int pos = Arrays.binarySearch(columnCache, logicalColumn);
|
||||
if (pos >= 0) return lineStartOffset + (pos + 1) * CACHE_FREQUENCY;
|
||||
int startOffset = lineStartOffset + (- pos - 1) * CACHE_FREQUENCY;
|
||||
int column = pos == -1 ? 0 : columnCache[- pos - 2];
|
||||
CharSequence text = document.getImmutableCharSequence();
|
||||
for (int i = startOffset; i < lineEndOffset; i++) {
|
||||
if (text.charAt(i) == '\t') {
|
||||
column = (column / tabSize + 1) * tabSize;
|
||||
}
|
||||
else {
|
||||
column++;
|
||||
}
|
||||
if (logicalColumn < column) return i;
|
||||
}
|
||||
return lineEndOffset;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -50,16 +50,6 @@ class TabFragment implements LineFragment {
|
||||
return EditorUtil.columnsNumber((int)(x - startX), myView.getPlainSpaceWidth());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int offsetToLogicalColumn(int startColumn, int offset) {
|
||||
return startColumn + (offset == 0 ? 0 : getLogicalColumnCount(startColumn));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int logicalColumnToOffset(int startColumn, int column) {
|
||||
return column < (startColumn + getLogicalColumnCount(startColumn)) ? 0 : 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Graphics2D g, float x, float y, int startColumn, int endColumn) {
|
||||
}
|
||||
|
||||
@@ -72,16 +72,6 @@ class TextFragment implements LineFragment {
|
||||
return myCharPositions.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int offsetToLogicalColumn(int startColumn, int offset) {
|
||||
return startColumn + offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int logicalColumnToOffset(int startColumn, int column) {
|
||||
return column - startColumn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Graphics2D g, float x, float y, int startColumn, int endColumn) {
|
||||
assert startColumn >= 0;
|
||||
@@ -201,16 +191,6 @@ class TextFragment implements LineFragment {
|
||||
return getLength();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int offsetToLogicalColumn(int startColumn, int offset) {
|
||||
return startColumn + offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int logicalColumnToOffset(int startColumn, int column) {
|
||||
return column - startColumn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int logicalToVisualColumn(float startX, int startColumn, int column) {
|
||||
return column;
|
||||
|
||||
+31
-26
@@ -22,24 +22,28 @@ import com.intellij.openapi.editor.ex.PrioritizedDocumentListener;
|
||||
import com.intellij.openapi.editor.impl.EditorDocumentPriorities;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* Editor text layout storage. It's cached on a per-line basis, created lazily (when requested), and invalidated on document changes or
|
||||
* when explicitly requested.
|
||||
* Editor text layout storage. Layout is stored on a per-logical-line basis,
|
||||
* it's created lazily (when requested) and invalidated on document changes or when explicitly requested.
|
||||
*
|
||||
* @see LineLayout
|
||||
*/
|
||||
class TextLayoutCache implements PrioritizedDocumentListener, Disposable {
|
||||
private final EditorView myView;
|
||||
private final Document myDocument;
|
||||
private final LineLayout myBidiNotRequiredMarker;
|
||||
private ArrayList<LineLayout> myLines = new ArrayList<LineLayout>();
|
||||
private int myDocumentChangeOldEndLine;
|
||||
private boolean myQuickLayoutsEnabled;
|
||||
|
||||
TextLayoutCache(EditorView view) {
|
||||
myView = view;
|
||||
myDocument = view.getEditor().getDocument();
|
||||
myDocument.addDocumentListener(this, this);
|
||||
myBidiNotRequiredMarker = new LineLayout(view, "", Font.PLAIN);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -56,7 +60,7 @@ class TextLayoutCache implements PrioritizedDocumentListener, Disposable {
|
||||
public void documentChanged(DocumentEvent event) {
|
||||
int startLine = myDocument.getLineNumber(event.getOffset());
|
||||
int newEndLine = getAdjustedLineNumber(event.getOffset() + event.getNewLength());
|
||||
invalidateLines(startLine, myDocumentChangeOldEndLine, newEndLine);
|
||||
invalidateLines(startLine, myDocumentChangeOldEndLine, newEndLine, !LineLayout.isBidiLayoutRequired(event.getNewFragment()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -67,17 +71,24 @@ class TextLayoutCache implements PrioritizedDocumentListener, Disposable {
|
||||
private int getAdjustedLineNumber(int offset) {
|
||||
return myDocument.getTextLength() == 0 ? -1 : myDocument.getLineNumber(offset);
|
||||
}
|
||||
|
||||
void resetToDocumentSize() {
|
||||
|
||||
void resetToDocumentSize(boolean documentChangedWithoutNotification) {
|
||||
checkDisposed();
|
||||
invalidateLines(0, myLines.size() - 1, myDocument.getLineCount() - 1);
|
||||
invalidateLines(0, myLines.size() - 1, myDocument.getLineCount() - 1, !documentChangedWithoutNotification);
|
||||
}
|
||||
|
||||
void invalidateLines(int startLine, int endLine) {
|
||||
invalidateLines(startLine, endLine, endLine, true);
|
||||
}
|
||||
|
||||
void invalidateLines(int startLine, int oldEndLine, int newEndLine) {
|
||||
private void invalidateLines(int startLine, int oldEndLine, int newEndLine, boolean keepBidiNotRequiredState) {
|
||||
checkDisposed();
|
||||
int endLine = Math.min(oldEndLine, newEndLine);
|
||||
for (int line = startLine; line <= endLine; line++) {
|
||||
myLines.set(line, null);
|
||||
LineLayout lineLayout = myLines.get(line);
|
||||
if (lineLayout != null) {
|
||||
myLines.set(line, keepBidiNotRequiredState && lineLayout.isLtr() ? myBidiNotRequiredMarker : null);
|
||||
}
|
||||
}
|
||||
if (oldEndLine < newEndLine) {
|
||||
myLines.addAll(oldEndLine + 1, Collections.nCopies(newEndLine - oldEndLine, (LineLayout)null));
|
||||
@@ -85,31 +96,25 @@ class TextLayoutCache implements PrioritizedDocumentListener, Disposable {
|
||||
myLines.subList(newEndLine + 1, oldEndLine + 1).clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@NotNull
|
||||
LineLayout getLineLayout(int line) {
|
||||
checkDisposed();
|
||||
LineLayout result = myLines.get(line);
|
||||
if (result == null) {
|
||||
int lineStart = myDocument.getLineStartOffset(line);
|
||||
int lineEnd = myDocument.getLineEndOffset(line);
|
||||
if (myQuickLayoutsEnabled) {
|
||||
result = new LineLayout(myView, lineEnd - lineStart);
|
||||
myView.getSizeManager().quickLineLayoutCreated();
|
||||
}
|
||||
else {
|
||||
result = new LineLayout(myView, lineStart, lineEnd, myView.getFontRenderContext());
|
||||
myLines.set(line, result);
|
||||
myView.getSizeManager().lineLayoutCreated(line);
|
||||
}
|
||||
if (result == null || result == myBidiNotRequiredMarker) {
|
||||
result = createLineLayout(line, result == myBidiNotRequiredMarker);
|
||||
myLines.set(line, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void enableQuickLayouts(boolean enabled) {
|
||||
myQuickLayoutsEnabled = enabled;
|
||||
|
||||
@NotNull
|
||||
private LineLayout createLineLayout(int line, boolean skipBidiLayout) {
|
||||
int lineStart = myDocument.getLineStartOffset(line);
|
||||
int lineEnd = myDocument.getLineEndOffset(line);
|
||||
return new LineLayout(myView, lineStart, lineEnd, skipBidiLayout);
|
||||
}
|
||||
|
||||
|
||||
private void checkDisposed() {
|
||||
if (myLines == null) throw new IllegalStateException("Editor is already disposed");
|
||||
}
|
||||
|
||||
+8
-6
@@ -34,11 +34,11 @@ import java.util.NoSuchElementException;
|
||||
*/
|
||||
class VisualLineFragmentsIterator implements Iterator<VisualLineFragmentsIterator.Fragment> {
|
||||
|
||||
static Iterable<Fragment> create(final EditorView view, final int offset, final boolean beforeSoftWrap) {
|
||||
static Iterable<Fragment> create(final EditorView view, final int offset, final boolean beforeSoftWrap, final boolean quick) {
|
||||
return new Iterable<Fragment>() {
|
||||
@Override
|
||||
public Iterator<Fragment> iterator() {
|
||||
return new VisualLineFragmentsIterator(view, offset, beforeSoftWrap);
|
||||
return new VisualLineFragmentsIterator(view, offset, beforeSoftWrap, quick);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -48,6 +48,7 @@ class VisualLineFragmentsIterator implements Iterator<VisualLineFragmentsIterato
|
||||
private final FoldRegion[] myRegions;
|
||||
private final Fragment myFragment = new Fragment();
|
||||
private final int myVisualLineStartOffset;
|
||||
private final boolean myQuick;
|
||||
|
||||
private int mySegmentStartOffset;
|
||||
private int mySegmentEndOffset;
|
||||
@@ -61,7 +62,8 @@ class VisualLineFragmentsIterator implements Iterator<VisualLineFragmentsIterato
|
||||
private int myCurrentEndLogicalLine;
|
||||
private int myNextWrapOffset;
|
||||
|
||||
private VisualLineFragmentsIterator(EditorView view, int offset, boolean beforeSoftWrap) {
|
||||
private VisualLineFragmentsIterator(EditorView view, int offset, boolean beforeSoftWrap, boolean quick) {
|
||||
myQuick = quick;
|
||||
myView = view;
|
||||
EditorImpl editor = view.getEditor();
|
||||
myDocument = editor.getDocument();
|
||||
@@ -108,9 +110,9 @@ class VisualLineFragmentsIterator implements Iterator<VisualLineFragmentsIterato
|
||||
int line = myDocument.getLineNumber(mySegmentStartOffset);
|
||||
mySegmentEndOffset = Math.min(myNextWrapOffset, Math.min(mySegmentEndOffset, myDocument.getLineEndOffset(line)));
|
||||
int lineStartOffset = myDocument.getLineStartOffset(line);
|
||||
myFragmentIterator = myView.getTextLayoutCache().getLineLayout(line).getFragmentsInVisualOrder(myCurrentX, myCurrentVisualColumn,
|
||||
mySegmentStartOffset - lineStartOffset,
|
||||
mySegmentEndOffset - lineStartOffset).iterator();
|
||||
myFragmentIterator = myView.getTextLayoutCache().getLineLayout(line).
|
||||
getFragmentsInVisualOrder(myView, lineStartOffset, myCurrentX, myCurrentVisualColumn,
|
||||
mySegmentStartOffset - lineStartOffset, mySegmentEndOffset - lineStartOffset, myQuick).iterator();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user