IDEA-204702 Support after-line-end inlays in editor

temporary revert changes - they are causing tests failures
This commit is contained in:
Dmitry Batrak
2019-02-08 11:00:38 +03:00
parent 3b886b590c
commit 32cbfb6cfb
19 changed files with 144 additions and 569 deletions
@@ -32,7 +32,7 @@ public interface Inlay<T extends EditorCustomElementRenderer> extends Disposable
* Defines relative position of inlay element with respect to the containing text.
*/
@NotNull
Placement getPlacement();
VerticalAlignment getVerticalAlignment();
/**
* Tells whether this element is valid. Inlay becomes invalid on explicit disposal,
@@ -103,7 +103,7 @@ public interface Inlay<T extends EditorCustomElementRenderer> extends Disposable
void repaint();
/**
* @see #getPlacement()
* @see #getVerticalAlignment()
*/
enum Placement { INLINE, ABOVE_LINE, BELOW_LINE, AFTER_LINE_END }
enum VerticalAlignment { INLINE, ABOVE_LINE, BELOW_LINE }
}
@@ -13,8 +13,7 @@ import java.util.List;
/**
* Provides an ability to introduce custom visual elements into editor's representation.
* Such elements are not reflected in document contents. Elements are 'anchored' to a certain document offset at creation,
* this offset behaves similar to a zero-range {@link RangeMarker} with respect to document changes.
* Such elements are not reflected in document contents.
* <p>
* WARNING! This is an experimental API, it can change at any time.
* @see Editor#getInlayModel()
@@ -30,24 +29,21 @@ public interface InlayModel {
}
/**
* Introduces an inline visual element at a given offset, its width and appearance is defined by the provided renderer.
*
* Introduces an inline visual element at a given offset, its width and appearance is defined by the provided renderer. With respect to
* document changes, created element behaves in a similar way to a zero-range {@link RangeMarker}. This method returns {@code null}
* if requested element cannot be created, e.g. if corresponding functionality is not supported by current editor instance.
*
* @param relatesToPrecedingText whether element is associated with preceding or following text
* (see {@link Inlay#isRelatedToPrecedingText()})
* @return {@code null} if requested element cannot be created, e.g. if corresponding functionality
* is not supported by current editor instance.
*/
@Nullable
<T extends EditorCustomElementRenderer> Inlay<T> addInlineElement(int offset, boolean relatesToPrecedingText, @NotNull T renderer);
/**
* Introduces a 'block' visual element at a given offset, its size and appearance is defined by the provided renderer. This element
* will be displayed between lines of text.
*
* @param relatesToPrecedingText whether element is associated with preceding or following text
* (see {@link Inlay#isRelatedToPrecedingText()})
* @return {@code null} if requested element cannot be created, e.g. if corresponding functionality
* is not supported by current editor instance.
* will be displayed between lines of text. With respect to document changes, created element behaves in a similar way to a zero-range
* {@link RangeMarker}. This method returns {@code null} if requested element cannot be created, e.g. if corresponding functionality
* is not supported by current editor instance.
*/
@Nullable
<T extends EditorCustomElementRenderer> Inlay<T> addBlockElement(int offset,
@@ -56,18 +52,6 @@ public interface InlayModel {
int priority,
@NotNull T renderer);
/**
* Introduces a visual element, which will be displayed after the end of corresponding logical line.
*
* @param relatesToPrecedingText whether element is associated with preceding or following text
* (see {@link Inlay#isRelatedToPrecedingText()})
* @return {@code null} if requested element cannot be created, e.g. if corresponding functionality
* is not supported by current editor instance.
*/
@Nullable
<T extends EditorCustomElementRenderer> Inlay<T> addAfterLineEndElement(int offset, boolean relatesToPrecedingText, @NotNull T renderer);
/**
* Returns a list of inline elements for a given offset range (both limits are inclusive). Returned list is sorted by offset.
* Both visible and invisible (due to folding) elements are returned.
@@ -152,34 +136,6 @@ public interface InlayModel {
@Nullable
Inlay getElementAt(@NotNull Point point);
/**
* Returns a list of after-line-end elements for a given offset range (both limits are inclusive).
* Returned list is sorted by offset. Both visible and invisible (due to folding) elements are returned.
*
* @see #addAfterLineEndElement(int, boolean, EditorCustomElementRenderer)
*/
@NotNull
List<Inlay> getAfterLineEndElementsInRange(int startOffset, int endOffset);
/**
* Same as {@link #getAfterLineEndElementsInRange(int, int)}, but returned list contains only inlays with renderer of given type.
*/
@NotNull
default <T> List<Inlay<? extends T>> getAfterLineEndElementsInRange(int startOffset, int endOffset, Class<T> type) {
//noinspection unchecked
return (List)ContainerUtil.filter(getAfterLineEndElementsInRange(startOffset, endOffset),
inlay -> type.isInstance(inlay.getRenderer()));
}
/**
* Returns after-line-end elements for a given logical line, in creation order (this is the order they are displayed in).
* Elements are returned regardless of whether they are currently visible.
*
* @see #addAfterLineEndElement(int, boolean, EditorCustomElementRenderer)
*/
@NotNull
List<Inlay> getAfterLineEndElementsForLogicalLine(int logicalLine);
/**
* When text is inserted at inline element's offset, resulting element's position is determined by its
* {@link Inlay#isRelatedToPrecedingText()} property. But to enable natural editing experience around inline elements (so that typed text
@@ -37,15 +37,6 @@ class InlayModelWindow implements InlayModel {
return null;
}
@Nullable
@Override
public <T extends EditorCustomElementRenderer> Inlay<T> addAfterLineEndElement(int offset,
boolean relatesToPrecedingText,
@NotNull T renderer) {
logUnsupported();
return null;
}
@NotNull
@Override
public List<Inlay> getInlineElementsInRange(int startOffset, int endOffset) {
@@ -87,20 +78,6 @@ class InlayModelWindow implements InlayModel {
return null;
}
@NotNull
@Override
public List<Inlay> getAfterLineEndElementsInRange(int startOffset, int endOffset) {
logUnsupported();
return Collections.emptyList();
}
@NotNull
@Override
public List<Inlay> getAfterLineEndElementsForLogicalLine(int logicalLine) {
logUnsupported();
return Collections.emptyList();
}
@Override
public void setConsiderCaretPositionOnDocumentUpdates(boolean enabled) {
logUnsupported();
@@ -1,75 +0,0 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.openapi.editor.impl;
import com.intellij.openapi.editor.EditorCustomElementRenderer;
import com.intellij.openapi.editor.Inlay;
import com.intellij.openapi.editor.VisualPosition;
import com.intellij.util.DocumentUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.awt.*;
import java.util.List;
class AfterLineEndInlayImpl<R extends EditorCustomElementRenderer> extends InlayImpl<R, AfterLineEndInlayImpl> {
private static int ourGlobalCounter = 0;
final int myOrder;
AfterLineEndInlayImpl(@NotNull EditorImpl editor, int offset, boolean relatesToPrecedingText, @NotNull R renderer) {
super(editor, offset, relatesToPrecedingText, renderer);
//noinspection AssignmentToStaticFieldFromInstanceMethod
myOrder = ourGlobalCounter++;
}
@Override
RangeMarkerTree<AfterLineEndInlayImpl> getTree() {
return myEditor.getInlayModel().myAfterLineEndElementsTree;
}
@Override
void doUpdateSize() {
myWidthInPixels = myRenderer.calcWidthInPixels(this);
if (myWidthInPixels <= 0) {
throw new IllegalArgumentException("Positive width should be defined for an after-line-end element");
}
}
@Override
Point getPosition() {
VisualPosition pos = getVisualPosition();
return myEditor.visualPositionToXY(pos);
}
@Nullable
@Override
public Rectangle getBounds() {
int targetOffset = DocumentUtil.getLineEndOffset(getOffset(), myEditor.getDocument());
if (myEditor.getFoldingModel().isOffsetCollapsed(targetOffset)) return null;
Point pos = getPosition();
return new Rectangle(pos.x, pos.y, getWidthInPixels(), getHeightInPixels());
}
@NotNull
@Override
public Placement getPlacement() {
return Placement.AFTER_LINE_END;
}
@NotNull
@Override
public VisualPosition getVisualPosition() {
int offset = getOffset();
int logicalLine = myEditor.getDocument().getLineNumber(offset);
int lineEndOffset = myEditor.getDocument().getLineEndOffset(logicalLine);
VisualPosition position = myEditor.offsetToVisualPosition(lineEndOffset, true, true);
if (myEditor.getFoldingModel().isOffsetCollapsed(lineEndOffset)) return position;
List<Inlay> inlays = myEditor.getInlayModel().getAfterLineEndElementsForLogicalLine(logicalLine);
int order = inlays.indexOf(this);
return new VisualPosition(position.line, position.column + 1 + order);
}
@Override
public int getHeightInPixels() {
return myEditor.getLineHeight();
}
}
@@ -75,8 +75,8 @@ class BlockInlayImpl<R extends EditorCustomElementRenderer> extends InlayImpl<R,
@NotNull
@Override
public Placement getPlacement() {
return myShowAbove ? Placement.ABOVE_LINE : Placement.BELOW_LINE;
public VerticalAlignment getVerticalAlignment() {
return myShowAbove ? VerticalAlignment.ABOVE_LINE : VerticalAlignment.BELOW_LINE;
}
@NotNull
@@ -47,7 +47,6 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener,
private volatile CaretImpl myPrimaryCaret;
private CaretImpl myCurrentCaret; // active caret in the context of 'runForEachCaret' call
private boolean myPerformCaretMergingAfterCurrentOperation;
private boolean myVisualPositionUpdateScheduled;
int myDocumentUpdateCounter;
@@ -80,7 +79,6 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener,
myDocumentUpdateCounter++;
if (!myEditor.getDocument().isInBulkUpdate()) {
doWithCaretMerging(() -> {}); // do caret merging if it's not scheduled for later
if (myVisualPositionUpdateScheduled) updateVisualPosition();
}
}
@@ -92,7 +90,6 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener,
}
}
myIsInUpdate = true;
myVisualPositionUpdateScheduled = false;
}
@Override
@@ -594,34 +591,28 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener,
@Override
public void onAdded(@NotNull Inlay inlay) {
if (myEditor.getDocument().isInBulkUpdate()) return;
Inlay.Placement placement = inlay.getPlacement();
if (placement == Inlay.Placement.INLINE) {
if (inlay.getVerticalAlignment() == Inlay.VerticalAlignment.INLINE) {
int offset = inlay.getOffset();
for (CaretImpl caret : myCarets) {
caret.onInlayAdded(offset);
}
}
else if (placement != Inlay.Placement.AFTER_LINE_END || hasCaretInVirtualSpace()) {
else {
updateVisualPosition();
}
}
@Override
public void onRemoved(@NotNull Inlay inlay) {
if (myEditor.getDocument().isInBulkUpdate()) return;
Inlay.Placement placement = inlay.getPlacement();
if (myEditor.getDocument().isInEventsHandling()) {
if (placement == Inlay.Placement.AFTER_LINE_END) myVisualPositionUpdateScheduled = true;
return;
}
if (placement == Inlay.Placement.INLINE) {
if (myEditor.getDocument().isInEventsHandling() || myEditor.getDocument().isInBulkUpdate()) return;
if (inlay.getVerticalAlignment() == Inlay.VerticalAlignment.INLINE) {
doWithCaretMerging(() -> {
for (CaretImpl caret : myCarets) {
caret.onInlayRemoved(inlay.getOffset(), ((InlineInlayImpl)inlay).getOrder());
caret.onInlayRemoved(inlay.getOffset(), ((InlayImpl)inlay).getOrder());
}
});
}
else if (placement != Inlay.Placement.AFTER_LINE_END || hasCaretInVirtualSpace()) {
else {
updateVisualPosition();
}
}
@@ -629,13 +620,7 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener,
@Override
public void onUpdated(@NotNull Inlay inlay) {
if (myEditor.getDocument().isInBulkUpdate()) return;
if (inlay.getPlacement() != Inlay.Placement.AFTER_LINE_END || hasCaretInVirtualSpace()) {
updateVisualPosition();
}
}
private boolean hasCaretInVirtualSpace() {
return myEditor.getSettings().isVirtualSpace() && ContainerUtil.exists(myCarets, CaretImpl::isInVirtualSpace);
updateVisualPosition();
}
@TestOnly
@@ -8,6 +8,7 @@ import com.intellij.diagnostic.Dumpable;
import com.intellij.ide.*;
import com.intellij.ide.dnd.DnDManager;
import com.intellij.ide.ui.UISettings;
import com.intellij.internal.performance.LatenciometerKt;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
@@ -627,14 +628,9 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
if (myDocument.isInEventsHandling() || myDocument.isInBulkUpdate()) return;
validateSize();
int offset = inlay.getOffset();
Inlay.Placement placement = inlay.getPlacement();
if (placement == Inlay.Placement.INLINE) {
if (inlay.getVerticalAlignment() == Inlay.VerticalAlignment.INLINE) {
repaint(offset, offset, false);
}
else if (placement == Inlay.Placement.AFTER_LINE_END) {
int lineEndOffset = DocumentUtil.getLineEndOffset(offset, myDocument);
repaint(lineEndOffset, lineEndOffset, false);
}
else {
int visualLine = offsetToVisualLine(offset);
int y = visualLineToY(visualLine) - EditorUtil.getTotalInlaysHeight(myInlayModel.getBlockElementsForVisualLine(visualLine, true));
@@ -2073,7 +2069,6 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
+ ", caret model: " + getCaretModel().dumpState()
+ ", soft wraps data: " + getSoftWrapModel().dumpState()
+ "\n\nfolding data: " + getFoldingModel().dumpState()
+ "\ninlay model: " + getInlayModel().dumpState()
+ (myDocument instanceof DocumentImpl ? "\n\ndocument info: " + ((DocumentImpl)myDocument).dumpState() : "")
+ "\nfont preferences: " + myScheme.getFontPreferences()
+ "\npure painting mode: " + myPurePaintingMode
@@ -101,6 +101,24 @@ public class FoldingModelImpl extends InlayModel.SimpleAdapter
return region instanceof FoldRegionImpl && ((FoldRegionImpl)region).hasDocumentRegionChanged();
}
@NotNull
FoldRegion getFirstRegion(@NotNull FoldingGroup group, @NotNull FoldRegion child) {
final List<FoldRegion> regions = getGroupedRegions(group);
if (regions.isEmpty()) {
final boolean inAll = Arrays.asList(getAllFoldRegions()).contains(child);
throw new AssertionError("Folding group without children; the known child is in all: " + inAll);
}
FoldRegion main = regions.get(0);
for (int i = 1; i < regions.size(); i++) {
FoldRegion region = regions.get(i);
if (main.getStartOffset() > region.getStartOffset()) {
main = region;
}
}
return main;
}
public int getEndOffset(@NotNull FoldingGroup group) {
final List<FoldRegion> regions = getGroupedRegions(group);
int endOffset = 0;
@@ -548,8 +566,7 @@ public class FoldingModelImpl extends InlayModel.SimpleAdapter
@Override
public void onUpdated(@NotNull Inlay inlay) {
Inlay.Placement placement = inlay.getPlacement();
if (placement == Inlay.Placement.ABOVE_LINE || placement == Inlay.Placement.BELOW_LINE) myFoldTree.clearCachedInlayValues();
if (inlay.getVerticalAlignment() != Inlay.VerticalAlignment.INLINE) myFoldTree.clearCachedInlayValues();
}
@Nullable
@@ -10,9 +10,11 @@ import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.util.List;
abstract class InlayImpl<R extends EditorCustomElementRenderer, T extends InlayImpl> extends RangeMarkerWithGetterImpl implements Inlay<R> {
static final Key<Integer> OFFSET_BEFORE_DISPOSAL = Key.create("inlay.offset.before.disposal");
private static final Key<Integer> ORDER_BEFORE_DISPOSAL = Key.create("inlay.order.before.disposal");
@NotNull
final EditorImpl myEditor;
@@ -76,10 +78,13 @@ abstract class InlayImpl<R extends EditorCustomElementRenderer, T extends InlayI
public void dispose() {
if (isValid()) {
int offset = getOffset(); // We want listeners notified after disposal, but want inlay offset to be available at that time
InlayModelImpl inlayModel = myEditor.getInlayModel();
List<Inlay> inlays = inlayModel.getInlineElementsInRange(offset, offset);
putUserData(ORDER_BEFORE_DISPOSAL, inlays.indexOf(this));
putUserData(OFFSET_BEFORE_DISPOSAL, offset);
//noinspection unchecked
getTree().removeInterval((T)this);
myEditor.getInlayModel().notifyRemoved(this);
inlayModel.notifyRemoved(this);
}
}
@@ -114,4 +119,9 @@ abstract class InlayImpl<R extends EditorCustomElementRenderer, T extends InlayI
public int getWidthInPixels() {
return myWidthInPixels;
}
int getOrder() {
Integer value = getUserData(ORDER_BEFORE_DISPOSAL);
return value == null ? -1 : value;
}
}
@@ -1,13 +1,11 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.openapi.editor.impl;
import com.intellij.diagnostic.Dumpable;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.*;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.editor.ex.DocumentEx;
import com.intellij.openapi.editor.ex.PrioritizedInternalDocumentListener;
import com.intellij.openapi.util.Getter;
import com.intellij.util.DocumentUtil;
@@ -20,20 +18,21 @@ import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import java.awt.*;
import java.util.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.function.Predicate;
public class InlayModelImpl implements InlayModel, Disposable, Dumpable {
public class InlayModelImpl implements InlayModel, Disposable {
private static final Logger LOG = Logger.getInstance(InlayModelImpl.class);
private static final Comparator<Inlay> INLINE_ELEMENTS_COMPARATOR = Comparator.comparingInt((Inlay i) -> i.getOffset())
private static final Comparator<Inlay> INLINE_ELEMENTS_COMPARATOR = Comparator.comparingInt((Inlay inlay) -> inlay.getOffset())
.thenComparing(i -> i.isRelatedToPrecedingText());
private static final Comparator<BlockInlayImpl> BLOCK_ELEMENTS_PRIORITY_COMPARATOR = Comparator.comparingInt(i -> -i.myPriority);
private static final Comparator<BlockInlayImpl> BLOCK_ELEMENTS_COMPARATOR = Comparator.comparing((BlockInlayImpl i) -> i.getPlacement())
.thenComparing(i -> i.getPlacement() == Inlay.Placement.ABOVE_LINE ? i.myPriority : -i.myPriority);
private static final Comparator<AfterLineEndInlayImpl> AFTER_LINE_END_ELEMENTS_OFFSET_COMPARATOR =
Comparator.comparingInt((AfterLineEndInlayImpl i) -> i.getOffset()).thenComparingInt(i -> i.myOrder);
private static final Comparator<AfterLineEndInlayImpl> AFTER_LINE_END_ELEMENTS_COMPARATOR = Comparator.comparingInt(i -> i.myOrder);
private static final Comparator<BlockInlayImpl> BLOCK_ELEMENTS_PRIORITY_COMPARATOR =
Comparator.comparingInt((BlockInlayImpl i) -> -i.myPriority);
private static final Comparator<BlockInlayImpl> BLOCK_ELEMENTS_COMPARATOR =
Comparator.comparing((BlockInlayImpl inlay) -> inlay.getVerticalAlignment())
.thenComparing(i -> i.getVerticalAlignment() == Inlay.VerticalAlignment.ABOVE_LINE ? i.myPriority : -i.myPriority);
private static final Processor<InlayImpl> UPDATE_SIZE_PROCESSOR = inlay -> {
inlay.updateSize();
return true;
@@ -45,7 +44,6 @@ public class InlayModelImpl implements InlayModel, Disposable, Dumpable {
final List<InlayImpl> myInlaysInvalidatedOnMove = new ArrayList<>();
final RangeMarkerTree<InlineInlayImpl> myInlineElementsTree;
final MarkerTreeWithPartialSums<BlockInlayImpl> myBlockElementsTree;
final RangeMarkerTree<AfterLineEndInlayImpl> myAfterLineEndElementsTree;
boolean myMoveInProgress;
boolean myPutMergedIntervalsAtBeginning;
@@ -56,7 +54,6 @@ public class InlayModelImpl implements InlayModel, Disposable, Dumpable {
myEditor = editor;
myInlineElementsTree = new InlineElementsTree(editor.getDocument());
myBlockElementsTree = new BlockElementsTree(editor.getDocument());
myAfterLineEndElementsTree = new AfterLineEndElementTree(editor.getDocument());
myEditor.getDocument().addDocumentListener(new PrioritizedInternalDocumentListener() {
@Override
public int getPriority() {
@@ -143,19 +140,6 @@ public class InlayModelImpl implements InlayModel, Disposable, Dumpable {
return inlay;
}
@Nullable
@Override
public <T extends EditorCustomElementRenderer> Inlay<T> addAfterLineEndElement(int offset,
boolean relatesToPrecedingText,
@NotNull T renderer) {
ApplicationManager.getApplication().assertIsDispatchThread();
Document document = myEditor.getDocument();
offset = Math.max(0, Math.min(document.getTextLength(), offset));
AfterLineEndInlayImpl<T> inlay = new AfterLineEndInlayImpl<>(myEditor, offset, relatesToPrecedingText, renderer);
notifyAdded(inlay);
return inlay;
}
@NotNull
@Override
public List<Inlay> getInlineElementsInRange(int startOffset, int endOffset) {
@@ -298,10 +282,8 @@ public class InlayModelImpl implements InlayModel, Disposable, Dumpable {
public Inlay getElementAt(@NotNull Point point) {
boolean hasInlineElements = hasInlineElements();
boolean hasBlockElements = hasBlockElements();
boolean hasAfterLineEndElements = hasAfterLineEndElements();
if (!hasInlineElements && !hasBlockElements && !hasAfterLineEndElements) return null;
if (!hasInlineElements && !hasBlockElements) return null;
int offset = -1;
VisualPosition visualPosition = myEditor.xyToVisualPosition(point);
if (hasBlockElements) {
int visualLine = visualPosition.line;
@@ -332,84 +314,21 @@ public class InlayModelImpl implements InlayModel, Disposable, Dumpable {
}
}
if (hasInlineElements) {
offset = myEditor.logicalPositionToOffset(myEditor.visualToLogicalPosition(visualPosition));
int offset = myEditor.logicalPositionToOffset(myEditor.visualToLogicalPosition(visualPosition));
List<Inlay> inlays = getInlineElementsInRange(offset, offset);
if (!inlays.isEmpty()) {
VisualPosition startVisualPosition = myEditor.offsetToVisualPosition(offset);
int x = myEditor.visualPositionToXY(startVisualPosition).x;
Inlay inlay = findInlay(inlays, point, x);
if (inlay != null) return inlay;
}
}
if (hasAfterLineEndElements) {
if (offset < 0) offset = myEditor.logicalPositionToOffset(myEditor.visualToLogicalPosition(visualPosition));
int logicalLine = myEditor.getDocument().getLineNumber(offset);
if (offset == myEditor.getDocument().getLineEndOffset(logicalLine) && !myEditor.getFoldingModel().isOffsetCollapsed(offset)) {
List<Inlay> inlays = myEditor.getInlayModel().getAfterLineEndElementsForLogicalLine(logicalLine);
if (!inlays.isEmpty()) {
Rectangle bounds = inlays.get(0).getBounds();
assert bounds != null;
Inlay inlay = findInlay(inlays, point, bounds.x);
if (inlay != null) return inlay;
}
if (inlays.isEmpty()) return null;
VisualPosition startVisualPosition = myEditor.offsetToVisualPosition(offset);
int x = myEditor.visualPositionToXY(startVisualPosition).x;
for (Inlay inlay : inlays) {
int endX = x + inlay.getWidthInPixels();
if (point.x >= x && point.x < endX) return inlay;
x = endX;
}
}
return null;
}
private static Inlay findInlay(List<Inlay> inlays, @NotNull Point point, int startX) {
for (Inlay inlay : inlays) {
int endX = startX + inlay.getWidthInPixels();
if (point.x >= startX && point.x < endX) return inlay;
startX = endX;
}
return null;
}
@NotNull
@Override
public List<Inlay> getAfterLineEndElementsInRange(int startOffset, int endOffset) {
if (!hasAfterLineEndElements()) return Collections.emptyList();
List<AfterLineEndInlayImpl> range =
getElementsInRange(myAfterLineEndElementsTree, startOffset, endOffset, inlay -> true, AFTER_LINE_END_ELEMENTS_OFFSET_COMPARATOR);
//noinspection unchecked
return (List)range;
}
@NotNull
@Override
public <T> List<Inlay<? extends T>> getAfterLineEndElementsInRange(int startOffset, int endOffset, Class<T> type) {
if (!hasAfterLineEndElements()) return Collections.emptyList();
List<AfterLineEndInlayImpl> range =
getElementsInRange(myAfterLineEndElementsTree, startOffset, endOffset, inlay -> type.isInstance(inlay.myRenderer),
AFTER_LINE_END_ELEMENTS_OFFSET_COMPARATOR);
//noinspection unchecked
return (List)range;
}
@NotNull
@Override
public List<Inlay> getAfterLineEndElementsForLogicalLine(int logicalLine) {
DocumentEx document = myEditor.getDocument();
if (!hasAfterLineEndElements() || logicalLine < 0 || logicalLine > 0 && logicalLine >= document.getLineCount()) {
return Collections.emptyList();
}
List<AfterLineEndInlayImpl> result = new ArrayList<>();
int startOffset = document.getLineStartOffset(logicalLine);
int endOffset = document.getLineEndOffset(logicalLine);
myAfterLineEndElementsTree.processOverlappingWith(startOffset, endOffset, inlay -> {
result.add(inlay);
return true;
});
result.sort(AFTER_LINE_END_ELEMENTS_COMPARATOR);
//noinspection unchecked
return (List)result;
}
public boolean hasAfterLineEndElements() {
return myAfterLineEndElementsTree.size() > 0;
}
@Override
public void setConsiderCaretPositionOnDocumentUpdates(boolean enabled) {
myConsiderCaretPositionOnDocumentUpdates = enabled;
@@ -439,24 +358,6 @@ public class InlayModelImpl implements InlayModel, Disposable, Dumpable {
}
}
@NotNull
@Override
public String dumpState() {
return "Inline elements: " + dumpInlays(myInlineElementsTree)
+ ", after-line-end elements: " + dumpInlays(myAfterLineEndElementsTree)
+ ", block elements: " + dumpInlays(myBlockElementsTree);
}
private static String dumpInlays(RangeMarkerTree<? extends InlayImpl> tree) {
StringJoiner joiner = new StringJoiner(",", "[", "]");
tree.processAll(o -> {
joiner.add(Integer.toString(o.getOffset()));
return true;
});
return joiner.toString();
}
private class InlineElementsTree extends HardReferencingRangeMarkerTree<InlineInlayImpl> {
InlineElementsTree(@NotNull Document document) {
super(document);
@@ -507,17 +408,4 @@ public class InlayModelImpl implements InlayModel, Disposable, Dumpable {
}
}
}
private class AfterLineEndElementTree extends HardReferencingRangeMarkerTree<AfterLineEndInlayImpl> {
AfterLineEndElementTree(@NotNull Document document) {
super(document);
}
@Override
void fireBeforeRemoved(@NotNull AfterLineEndInlayImpl inlay, @NotNull @NonNls Object reason) {
if (inlay.getUserData(InlayImpl.OFFSET_BEFORE_DISPOSAL) == null) {
notifyRemoved(inlay);
}
}
}
}
@@ -5,7 +5,6 @@ import com.intellij.openapi.editor.EditorCustomElementRenderer;
import com.intellij.openapi.editor.Inlay;
import com.intellij.openapi.editor.VisualPosition;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.util.Key;
import com.intellij.util.DocumentUtil;
import org.jetbrains.annotations.NotNull;
@@ -13,8 +12,6 @@ import java.awt.*;
import java.util.List;
class InlineInlayImpl<R extends EditorCustomElementRenderer> extends InlayImpl<R, InlineInlayImpl> {
private static final Key<Integer> ORDER_BEFORE_DISPOSAL = Key.create("inlay.order.before.disposal");
InlineInlayImpl(@NotNull EditorImpl editor,
int offset,
boolean relatesToPrecedingText,
@@ -51,16 +48,6 @@ class InlineInlayImpl<R extends EditorCustomElementRenderer> extends InlayImpl<R
}
}
@Override
public void dispose() {
if (isValid()) {
int offset = getOffset();
List<Inlay> inlays = myEditor.getInlayModel().getInlineElementsInRange(offset, offset);
putUserData(ORDER_BEFORE_DISPOSAL, inlays.indexOf(this));
}
super.dispose();
}
@Override
void doUpdateSize() {
myWidthInPixels = myRenderer.calcWidthInPixels(this);
@@ -71,8 +58,8 @@ class InlineInlayImpl<R extends EditorCustomElementRenderer> extends InlayImpl<R
@NotNull
@Override
public Placement getPlacement() {
return Placement.INLINE;
public VerticalAlignment getVerticalAlignment() {
return VerticalAlignment.INLINE;
}
@NotNull
@@ -95,9 +82,4 @@ class InlineInlayImpl<R extends EditorCustomElementRenderer> extends InlayImpl<R
public int getHeightInPixels() {
return myEditor.getLineHeight();
}
int getOrder() {
Integer value = getUserData(ORDER_BEFORE_DISPOSAL);
return value == null ? -1 : value;
}
}
@@ -98,7 +98,6 @@ public class SoftWrapModelImpl extends InlayModel.SimpleAdapter
private boolean myDirty;
private boolean myForceAdditionalColumns;
private boolean myAfterLineEndInlayUpdated;
SoftWrapModelImpl(@NotNull EditorImpl editor) {
myEditor = editor;
@@ -398,7 +397,6 @@ public class SoftWrapModelImpl extends InlayModel.SimpleAdapter
@Override
public void beforeDocumentChange(@NotNull DocumentEvent event) {
myAfterLineEndInlayUpdated = false;
if (myBulkUpdateInProgress) {
return;
}
@@ -419,7 +417,7 @@ public class SoftWrapModelImpl extends InlayModel.SimpleAdapter
if (!isSoftWrappingEnabled()) {
return;
}
myApplianceManager.documentChanged(event, myAfterLineEndInlayUpdated);
myApplianceManager.documentChanged(event);
}
@Override
@@ -474,23 +472,14 @@ public class SoftWrapModelImpl extends InlayModel.SimpleAdapter
@Override
public void onUpdated(@NotNull Inlay inlay) {
if (myEditor.getDocument().isInBulkUpdate() ||
inlay.getPlacement() != Inlay.Placement.INLINE && inlay.getPlacement() != Inlay.Placement.AFTER_LINE_END) return;
if (myEditor.getDocument().isInEventsHandling() || myEditor.getDocument().isInBulkUpdate() ||
inlay.getVerticalAlignment() != Inlay.VerticalAlignment.INLINE) return;
if (!isSoftWrappingEnabled()) {
myDirty = true;
return;
}
if (!myDirty) {
if (myEditor.getDocument().isInEventsHandling()) {
if (inlay.getPlacement() == Inlay.Placement.AFTER_LINE_END) {
myAfterLineEndInlayUpdated = true;
}
return;
}
int offset = inlay.getOffset();
if (inlay.getPlacement() == Inlay.Placement.AFTER_LINE_END) {
offset = DocumentUtil.getLineEndOffset(offset, myEditor.getDocument());
}
myApplianceManager.recalculate(Collections.singletonList(new TextRange(offset, offset)));
}
}
@@ -19,18 +19,21 @@ public class IncrementalCacheUpdateEvent {
private final int myStartOffset;
private final int myMandatoryEndOffset;
private int myActualEndOffset = -1;
private final int myLengthDiff;
@NotNull
private final LogicalPosition myStartLogicalPosition;
private final int myOldEndLogicalLine;
private int myNewEndLogicalLine = -1;
/**
* Creates new {@code IncrementalCacheUpdateEvent} object on the basis on the given event object that describes
* document change that caused cache update.
* <p/>
* This constructor is assumed to be used <b>before</b> the document change.
*
* This constructor is assumed to be used <b>before</b> the document change, {@link #updateAfterDocumentChange(Document)}
* should be called <b>'after'</b> document change to complete object creation.
*
* @param event object that describes document change that caused cache update
*/
IncrementalCacheUpdateEvent(@NotNull DocumentEvent event, @NotNull EditorImpl editor) {
@@ -43,12 +46,13 @@ public class IncrementalCacheUpdateEvent {
*/
IncrementalCacheUpdateEvent(int startOffset, int endOffset, @NotNull EditorImpl editor) {
this(startOffset, endOffset, endOffset, editor);
myNewEndLogicalLine = myOldEndLogicalLine;
}
/**
* Creates new {@code IncrementalCacheUpdateEvent} object that is configured to perform whole reparse of the given
* document.
*
*
* @param document target document to reparse
*/
IncrementalCacheUpdateEvent(@NotNull Document document) {
@@ -56,8 +60,9 @@ public class IncrementalCacheUpdateEvent {
myMandatoryEndOffset = document.getTextLength();
myLengthDiff = 0;
myStartLogicalPosition = new LogicalPosition(0, 0);
myOldEndLogicalLine = myNewEndLogicalLine = Math.max(0, document.getLineCount() - 1);
}
private IncrementalCacheUpdateEvent(int startOffset, int oldEndOffset, int newEndOffset, @NotNull EditorImpl editor) {
VisualLineInfo info = getVisualLineInfo(editor, startOffset, false);
if (info.startsWithSoftWrap) {
@@ -67,6 +72,7 @@ public class IncrementalCacheUpdateEvent {
myStartLogicalPosition = editor.offsetToLogicalPosition(myStartOffset);
myMandatoryEndOffset = newEndOffset;
myLengthDiff = newEndOffset - oldEndOffset;
myOldEndLogicalLine = editor.getDocument().getLineNumber(oldEndOffset);
}
@@ -82,11 +88,11 @@ public class IncrementalCacheUpdateEvent {
int wrapIndex = softWrapModel.getSoftWrapIndex(offset);
int prevSoftWrapIndex = wrapIndex < 0 ? - wrapIndex - 2 : wrapIndex - (beforeSoftWrap ? 1 : 0);
SoftWrap prevSoftWrap = prevSoftWrapIndex < 0 ? null : softWrapModel.getRegisteredSoftWraps().get(prevSoftWrapIndex);
int visualLineStartOffset = prevSoftWrap == null ? startOffset : Math.max(startOffset, prevSoftWrap.getStart());
return new VisualLineInfo(visualLineStartOffset, prevSoftWrap != null && prevSoftWrap.getStart() == visualLineStartOffset);
}
private static class VisualLineInfo {
private final int startOffset;
private final boolean startsWithSoftWrap;
@@ -97,6 +103,10 @@ public class IncrementalCacheUpdateEvent {
}
}
void updateAfterDocumentChange(@NotNull Document document) {
myNewEndLogicalLine = document.getLineNumber(myMandatoryEndOffset);
}
/**
* Returns offset, from which soft wrap recalculation should start
*/
@@ -144,6 +154,8 @@ public class IncrementalCacheUpdateEvent {
", mandatoryEndOffset=" + myMandatoryEndOffset +
", actualEndOffset=" + myActualEndOffset +
", lengthDiff=" + myLengthDiff +
", startLogicalPosition=" + myStartLogicalPosition;
", startLogicalPosition=" + myStartLogicalPosition +
", oldEndLogicalLine=" + myOldEndLogicalLine +
", newEndLogicalLine=" + myNewEndLogicalLine;
}
}
@@ -295,9 +295,7 @@ public class SoftWrapApplianceManager implements Dumpable {
myContext.softWrapStartOffset++;
}
myContext.inlineInlays = myEditor.getInlayModel().getInlineElementsInRange(start, endOffsetUpperEstimate);
myContext.afterLineEndInlays = myEditor.getInlayModel().getAfterLineEndElementsInRange(DocumentUtil.getLineStartOffset(start, document),
endOffsetUpperEstimate);
myContext.inlays = myEditor.getInlayModel().getInlineElementsInRange(start, endOffsetUpperEstimate);
// Perform soft wraps calculation.
while (!iterationState.atEnd()) {
@@ -949,15 +947,10 @@ public class SoftWrapApplianceManager implements Dumpable {
myDocumentChangedEvent = new IncrementalCacheUpdateEvent(event, myEditor);
}
public void documentChanged(DocumentEvent event, boolean processAlsoLineEnd) {
public void documentChanged(DocumentEvent event) {
LOG.assertTrue(myDocumentChangedEvent != null);
myDocumentChangedEvent.updateAfterDocumentChange(event.getDocument());
recalculate(myDocumentChangedEvent);
if (processAlsoLineEnd) {
int lineEndOffset = DocumentUtil.getLineEndOffset(myDocumentChangedEvent.getMandatoryEndOffset(), event.getDocument());
if (lineEndOffset > myDocumentChangedEvent.getActualEndOffset()) {
recalculate(new IncrementalCacheUpdateEvent(lineEndOffset, lineEndOffset, myEditor));
}
}
myDocumentChangedEvent = null;
}
@@ -1212,10 +1205,8 @@ public class SoftWrapApplianceManager implements Dumpable {
int fontType;
boolean skipToLineEnd;
List<Inlay> inlineInlays;
int inlineInlayIndex;
List<Inlay> afterLineEndInlays;
int afterLineEndInlayIndex;
List<Inlay> inlays;
int inlayIndex;
@Override
public String toString() {
@@ -1241,10 +1232,8 @@ public class SoftWrapApplianceManager implements Dumpable {
skipToLineEnd = false;
fontType2spaceWidth.reset();
logicalLineData.reset();
inlineInlays = null;
inlineInlayIndex = 0;
afterLineEndInlays = null;
afterLineEndInlayIndex = 0;
inlays = null;
inlayIndex = 0;
}
int getSpaceWidth() {
@@ -1329,11 +1318,11 @@ public class SoftWrapApplianceManager implements Dumpable {
}
private int getInlaysWidthForOffset(int offset) {
while (inlineInlayIndex < inlineInlays.size() && inlineInlays.get(inlineInlayIndex).getOffset() < offset) inlineInlayIndex++;
while (inlineInlayIndex > 0 && inlineInlays.get(inlineInlayIndex - 1).getOffset() >= offset) inlineInlayIndex--;
while (inlayIndex < inlays.size() && inlays.get(inlayIndex).getOffset() < offset) inlayIndex++;
while (inlayIndex > 0 && inlays.get(inlayIndex - 1).getOffset() >= offset) inlayIndex--;
int width = 0;
while (inlineInlayIndex < inlineInlays.size() && inlineInlays.get(inlineInlayIndex).getOffset() == offset) {
width += inlineInlays.get(inlineInlayIndex++).getWidthInPixels();
while (inlayIndex < inlays.size() && inlays.get(inlayIndex).getOffset() == offset) {
width += inlays.get(inlayIndex++).getWidthInPixels();
}
return width;
}
@@ -1343,28 +1332,7 @@ public class SoftWrapApplianceManager implements Dumpable {
return nextOffset < text.length() && text.charAt(nextOffset) != '\n' ||
nextOffset > tokenEndOffset ||
nextOffset == tokenEndOffset && nextIsFoldRegion
? 0 : getInlaysWidthForOffset(nextOffset) + getAfterLineEndInlaysWidth(currentPosition.logicalLine);
}
private int getAfterLineEndInlaysWidth(int logicalLine) {
int startOffset = myEditor.getDocument().getLineStartOffset(logicalLine);
int endOffset = myEditor.getDocument().getLineEndOffset(logicalLine);
while (afterLineEndInlayIndex < afterLineEndInlays.size()
&& afterLineEndInlays.get(afterLineEndInlayIndex).getOffset() < startOffset) {
afterLineEndInlayIndex++;
}
while (afterLineEndInlayIndex > 0 && afterLineEndInlays.get(afterLineEndInlayIndex - 1).getOffset() >= startOffset) {
afterLineEndInlayIndex--;
}
int width = 0;
while (afterLineEndInlayIndex < afterLineEndInlays.size()) {
Inlay inlay = afterLineEndInlays.get(afterLineEndInlayIndex);
int offset = inlay.getOffset();
if (offset < startOffset || offset > endOffset) break;
width += inlay.getWidthInPixels();
afterLineEndInlayIndex++;
}
return width;
? 0 : getInlaysWidthForOffset(nextOffset);
}
/**
@@ -3,6 +3,8 @@
*/
package com.intellij.openapi.editor.impl.view;
import com.intellij.openapi.diagnostic.Attachment;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.*;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.ex.util.EditorUtil;
@@ -26,6 +28,8 @@ import java.util.List;
* @see VisualPosition
*/
class EditorCoordinateMapper {
private static final Logger LOG = Logger.getInstance(EditorCoordinateMapper.class);
private final EditorView myView;
private final Document myDocument;
private final FoldingModelImpl myFoldingModel;
@@ -93,28 +97,19 @@ class EditorCoordinateMapper {
int column = pos.column;
int logicalLineCount = myDocument.getLineCount();
if (line >= logicalLineCount) {
if (line == 0) {
int resultColumn = logToVisWithInlays(0, column, pos.leansForward);
if (resultColumn < 0) resultColumn = Integer.MAX_VALUE; // guarding against overflow
return new VisualPosition(0, resultColumn, pos.leansForward);
}
else {
return new VisualPosition(line - logicalLineCount + myView.getEditor().getVisibleLineCount(), column, pos.leansForward);
}
return new VisualPosition(line - logicalLineCount + myView.getEditor().getVisibleLineCount(), column, pos.leansForward);
}
int offset = logicalPositionToOffset(pos);
int visualLine = offsetToVisualLine(offset, beforeSoftWrap);
int maxVisualColumn = 0;
int maxLogicalColumn = 0;
int endLogicalLine = line;
for (VisualLineFragmentsIterator.Fragment fragment : VisualLineFragmentsIterator.create(myView, offset, beforeSoftWrap)) {
if (!pos.leansForward && offset == fragment.getVisualLineStartOffset()) {
return new VisualPosition(visualLine, fragment.getStartVisualColumn());
}
endLogicalLine = fragment.getEndLogicalLine();
maxVisualColumn = fragment.getEndVisualColumn();
if (fragment.isCollapsedFoldRegion()) {
int startLogicalLine = fragment.getStartLogicalLine();
int endLogicalLine = fragment.getEndLogicalLine();
int startLogicalColumn = fragment.getStartLogicalColumn();
int endLogicalColumn = fragment.getEndLogicalColumn();
if ((line > startLogicalLine || line == startLogicalLine && (column > startLogicalColumn ||
@@ -123,7 +118,7 @@ class EditorCoordinateMapper {
return new VisualPosition(visualLine, fragment.getStartVisualColumn(), true);
}
if (line == endLogicalLine && column == endLogicalColumn && !pos.leansForward) {
return new VisualPosition(visualLine, maxVisualColumn);
return new VisualPosition(visualLine, fragment.getEndVisualColumn());
}
maxLogicalColumn = startLogicalLine == endLogicalLine ? Math.max(maxLogicalColumn, endLogicalColumn) : endLogicalColumn;
}
@@ -138,47 +133,31 @@ class EditorCoordinateMapper {
}
maxLogicalColumn = Math.max(maxLogicalColumn, maxColumn);
}
maxVisualColumn = fragment.getEndVisualColumn();
}
int resultColumn = column - maxLogicalColumn + maxVisualColumn;
if (resultColumn < 0) {
if (maxVisualColumn > maxLogicalColumn) {
resultColumn = Integer.MAX_VALUE; // guarding against overflow
}
else {
LOG.error("Error converting " + pos + " to visual position",
new Attachment("details.txt", String.format("offset: %d, visual line: %d, max logical column: %d, max visual column: %d",
offset, visualLine, maxLogicalColumn, maxVisualColumn)),
new Attachment("dump.txt", myView.getEditor().dumpState()));
resultColumn = 0;
}
}
int resultColumn = maxVisualColumn + logToVisWithInlays(endLogicalLine, column - maxLogicalColumn, pos.leansForward);
if (resultColumn < 0) resultColumn = Integer.MAX_VALUE; // guarding against overflow
return new VisualPosition(visualLine, resultColumn, pos.leansForward);
}
private int logToVisWithInlays(int logLine, int remainingLogColumn, boolean leansForward) {
if (remainingLogColumn > 1 || remainingLogColumn == 1 && leansForward) {
remainingLogColumn += myView.getEditor().getInlayModel().getAfterLineEndElementsForLogicalLine(logLine).size();
}
return remainingLogColumn;
}
private int visToLogWithInlays(int logLine, int remainingVisColumns, boolean[] leansForward) {
if (remainingVisColumns == 0) return 0;
int inlayCount = myView.getEditor().getInlayModel().getAfterLineEndElementsForLogicalLine(logLine).size();
if (inlayCount == 0) return remainingVisColumns;
if (remainingVisColumns < inlayCount + 1) {
leansForward[0] = false;
return 1;
}
if (remainingVisColumns == inlayCount + 1) {
leansForward[0] = true;
}
return remainingVisColumns - inlayCount;
}
@NotNull
LogicalPosition visualToLogicalPosition(@NotNull VisualPosition pos) {
int line = pos.line;
int column = pos.column;
int visualLineCount = myView.getEditor().getVisibleLineCount();
if (line >= visualLineCount) {
if (line == 0) {
boolean[] leansForward = new boolean[] {pos.leansRight};
int resultColumn = visToLogWithInlays(0, column, leansForward);
return new LogicalPosition(0, resultColumn, leansForward[0]);
}
else {
return new LogicalPosition(line - visualLineCount + myDocument.getLineCount(), column, pos.leansRight);
}
return new LogicalPosition(line - visualLineCount + myDocument.getLineCount(), column, pos.leansRight);
}
int offset = visualLineToOffset(line);
int logicalLine = myDocument.getLineNumber(offset);
@@ -214,10 +193,11 @@ class EditorCoordinateMapper {
maxOffset = Math.max(maxOffset, fragment.getMaxOffset());
}
if (myView.getEditor().getSoftWrapModel().getSoftWrap(maxOffset) == null) {
boolean[] leansForward = new boolean[] {pos.leansRight};
int resultColumn = maxLogicalColumn + visToLogWithInlays(logicalLine, column - maxVisualColumn, leansForward);
if (resultColumn < 0) resultColumn = Integer.MAX_VALUE; // guarding against overflow
return new LogicalPosition(logicalLine, resultColumn, leansForward[0]);
int resultColumn = column - maxVisualColumn + maxLogicalColumn;
if (resultColumn < 0 && maxLogicalColumn > maxVisualColumn) {
resultColumn = Integer.MAX_VALUE; // guarding against overflow
}
return new LogicalPosition(logicalLine, resultColumn, true);
}
else {
return offsetToLogicalPosition(maxOffset).leanForward(true);
@@ -321,7 +301,6 @@ class EditorCoordinateMapper {
int lastColumn = 0;
float x = getStartX(visualLine);
float px = (float)p.getX();
int logicalLine = -1;
if (visualLine < myView.getEditor().getVisibleLineCount()) {
int visualLineStartOffset = visualLineToOffset(visualLine);
int maxOffset = 0;
@@ -346,7 +325,6 @@ class EditorCoordinateMapper {
x = nextX;
lastColumn = fragment.getEndVisualColumn();
maxOffset = Math.max(maxOffset, fragment.getMaxOffset());
logicalLine = fragment.getEndLogicalLine();
}
if (myView.getEditor().getSoftWrapModel().getSoftWrap(maxOffset) != null) {
int markerWidth = myView.getEditor().getSoftWrapModel().getMinDrawingWidthInPixels(SoftWrapDrawingType.BEFORE_SOFT_WRAP_LINE_FEED);
@@ -356,34 +334,10 @@ class EditorCoordinateMapper {
}
px -= markerWidth;
lastColumn++;
logicalLine = -1;
}
else if (logicalLine == -1) {
logicalLine = myDocument.getLineNumber(visualLineStartOffset);
}
}
else if (visualLine == 0) {
logicalLine = 0;
}
float plainSpaceWidth = myView.getPlainSpaceWidth();
float remainingShift = px - x;
if (remainingShift > plainSpaceWidth && logicalLine >= 0) {
List<Inlay> inlays = myView.getEditor().getInlayModel().getAfterLineEndElementsForLogicalLine(logicalLine);
int inlaysWidth = 0;
int inlayCount = 0;
for (Inlay inlay : inlays) {
int width = inlay.getWidthInPixels();
int newWidth = inlaysWidth + width;
if (remainingShift <= plainSpaceWidth + newWidth) {
boolean leftPart = remainingShift <= plainSpaceWidth + (inlaysWidth + newWidth) / 2;
return new VisualPosition(visualLine, lastColumn + 1 + inlayCount + (leftPart ? 0 : 1), leftPart);
}
inlaysWidth = newWidth;
inlayCount++;
}
remainingShift -= inlaysWidth;
lastColumn += inlayCount;
}
int additionalColumns = remainingShift <= 0 ? 0 : Math.round(remainingShift / plainSpaceWidth);
return new VisualPosition(visualLine, lastColumn + additionalColumns, remainingShift > additionalColumns * plainSpaceWidth);
}
@@ -395,7 +349,6 @@ class EditorCoordinateMapper {
int y = visualLineToY(visualLine);
float x = getStartX(visualLine);
int lastColumn = 0;
int logicalLine = -1;
if (visualLine < myView.getEditor().getVisibleLineCount()) {
int visualLineStartOffset = visualLineToOffset(visualLine);
int maxOffset = 0;
@@ -411,32 +364,11 @@ class EditorCoordinateMapper {
x = fragment.getEndX();
lastColumn = endColumn;
maxOffset = Math.max(maxOffset, fragment.getMaxOffset());
logicalLine = fragment.getEndLogicalLine();
}
if (column > lastColumn && myView.getEditor().getSoftWrapModel().getSoftWrap(maxOffset) != null) {
column--;
x += myView.getEditor().getSoftWrapModel().getMinDrawingWidthInPixels(SoftWrapDrawingType.BEFORE_SOFT_WRAP_LINE_FEED);
}
else if (logicalLine == -1) {
logicalLine = myDocument.getLineNumber(visualLineStartOffset);
}
}
else if (visualLine == 0) {
logicalLine = 0;
}
if (column > lastColumn + 1 && logicalLine >= 0) {
List<Inlay> inlays = myView.getEditor().getInlayModel().getAfterLineEndElementsForLogicalLine(logicalLine);
int inlaysWidth = 0;
int inlayCount = 0;
for (Inlay inlay : inlays) {
inlayCount++;
inlaysWidth += inlay.getWidthInPixels();
if (column == lastColumn + 1 + inlayCount) {
break;
}
}
x += inlaysWidth;
column -= inlayCount;
}
float additionalShift = column <= lastColumn ? 0 : (column - lastColumn) * myView.getPlainSpaceWidth();
return new Point2D.Double(x + additionalShift, y);
@@ -501,20 +501,7 @@ public class EditorPainter implements TextDrawingCallback {
int offset = iterationState.getEndOffset();
SoftWrapModelImpl softWrapModel = myEditor.getSoftWrapModel();
if (softWrapModel.getSoftWrap(offset) == null) {
int logicalLine = myDocument.getLineNumber(offset);
List<Inlay> inlays = myEditor.getInlayModel().getAfterLineEndElementsForLogicalLine(logicalLine);
if (!inlays.isEmpty()) {
x += myView.getPlainSpaceWidth();
int lineHeight = myView.getLineHeight();
TextAttributes backgroundAttributes = iterationState.getPastLineEndBackgroundAttributes();
for (Inlay inlay : inlays) {
int width = inlay.getWidthInPixels();
inlay.getRenderer().paint(inlay, g, new Rectangle((int) x, y - myView.getAscent(), width, lineHeight),
backgroundAttributes);
x += width;
}
}
paintLineExtensions(g, visualLine, logicalLine, x, y, extensionData);
paintLineExtensions(g, visualLine, offset, x, y, extensionData);
}
else if (paintSoftWraps) {
softWrapModel.doPaint(g, SoftWrapDrawingType.BEFORE_SOFT_WRAP_LINE_FEED,
@@ -684,7 +671,7 @@ public class EditorPainter implements TextDrawingCallback {
}
}
private void paintLineExtensions(Graphics2D g, int visualLine, int logicalLine, float x, int y,
private void paintLineExtensions(Graphics2D g, int visualLine, int offset, float x, int y,
TIntObjectHashMap<List<LineExtensionData>> extensionData) {
List<LineExtensionData> data = extensionData.get(visualLine);
if (data == null) return;
@@ -695,7 +682,7 @@ public class EditorPainter implements TextDrawingCallback {
int currentLineWidth = myCorrector.lineWidth(visualLine, x);
EditorSizeManager sizeManager = myView.getSizeManager();
if (currentLineWidth > sizeManager.getMaxLineWithExtensionWidth()) {
sizeManager.setMaxLineWithExtensionWidth(logicalLine, currentLineWidth);
sizeManager.setMaxLineWithExtensionWidth(myDocument.getLineNumber(offset), currentLineWidth);
myEditor.getContentComponent().revalidate();
}
}
@@ -17,7 +17,6 @@ import com.intellij.openapi.editor.impl.softwrap.mapping.SoftWrapAwareDocumentPa
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.TextRange;
import com.intellij.util.DocumentUtil;
import gnu.trove.TIntArrayList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -64,7 +63,6 @@ class EditorSizeManager extends InlayModel.SimpleAdapter implements PrioritizedD
private boolean myDirty; // true if we cannot calculate preferred size now because soft wrap model was invalidated after editor
// became hidden. myLineWidths contents is irrelevant in such a state. Previously calculated preferred size
// is kept until soft wraps will be recalculated and size calculations will become possible
private boolean myAfterLineEndInlayUpdated;
private final List<TextRange> myDeferredRanges = new ArrayList<>();
@@ -97,7 +95,6 @@ class EditorSizeManager extends InlayModel.SimpleAdapter implements PrioritizedD
@Override
public void beforeDocumentChange(@NotNull DocumentEvent event) {
myAfterLineEndInlayUpdated = false;
myDuringDocumentUpdate = true;
if (myDocument.isInBulkUpdate()) return;
myDocumentChangeStartOffset = event.getOffset();
@@ -109,10 +106,6 @@ class EditorSizeManager extends InlayModel.SimpleAdapter implements PrioritizedD
myDuringDocumentUpdate = false;
if (myDocument.isInBulkUpdate()) return;
doInvalidateRange(myDocumentChangeStartOffset, myDocumentChangeEndOffset);
if (myAfterLineEndInlayUpdated) {
int lineEndOffset = DocumentUtil.getLineEndOffset(myDocumentChangeEndOffset, myDocument);
doInvalidateRange(lineEndOffset, lineEndOffset);
}
assertValidState();
}
@@ -143,19 +136,8 @@ class EditorSizeManager extends InlayModel.SimpleAdapter implements PrioritizedD
@Override
public void onUpdated(@NotNull Inlay inlay) {
if (myDocument.isInBulkUpdate()
|| inlay.getPlacement() != Inlay.Placement.INLINE && inlay.getPlacement() != Inlay.Placement.AFTER_LINE_END) return;
if (myDuringDocumentUpdate) {
if (inlay.getPlacement() == Inlay.Placement.AFTER_LINE_END) {
myAfterLineEndInlayUpdated = true;
}
return;
}
int offset = inlay.getOffset();
if (inlay.getPlacement() == Inlay.Placement.AFTER_LINE_END) {
offset = DocumentUtil.getLineEndOffset(offset, myDocument);
}
doInvalidateRange(offset, offset);
if (myDuringDocumentUpdate || myDocument.isInBulkUpdate() || inlay.getVerticalAlignment() != Inlay.VerticalAlignment.INLINE) return;
doInvalidateRange(inlay.getOffset(), inlay.getOffset());
}
private void onSoftWrapRecalculationEnd(IncrementalCacheUpdateEvent event) {
@@ -342,8 +324,7 @@ class EditorSizeManager extends InlayModel.SimpleAdapter implements PrioritizedD
FoldRegion[] topLevelRegions = myEditor.getFoldingModel().fetchTopLevel();
if (quickEvaluationListener != null &&
(topLevelRegions == null || topLevelRegions.length == 0) && myEditor.getSoftWrapModel().getRegisteredSoftWraps().isEmpty() &&
!myView.getTextLayoutCache().hasCachedLayoutFor(visualLine)
&& !myEditor.getInlayModel().hasInlineElements() && !myEditor.getInlayModel().hasAfterLineEndElements()) {
!myView.getTextLayoutCache().hasCachedLayoutFor(visualLine) && !myEditor.getInlayModel().hasInlineElements()) {
// fast path - speeds up editor opening
quickEvaluationListener.run();
return (int)(myView.getLogicalPositionCache().offsetToLogicalColumn(visualLine,
@@ -362,15 +343,6 @@ class EditorSizeManager extends InlayModel.SimpleAdapter implements PrioritizedD
if (myEditor.getSoftWrapModel().getSoftWrap(maxOffset) != null) {
x += myEditor.getSoftWrapModel().getMinDrawingWidthInPixels(SoftWrapDrawingType.BEFORE_SOFT_WRAP_LINE_FEED);
}
else {
List<Inlay> inlays = myEditor.getInlayModel().getAfterLineEndElementsForLogicalLine(iterator.getEndLogicalLine());
if (!inlays.isEmpty()) {
x += myView.getPlainSpaceWidth();
for (Inlay inlay : inlays) {
x += inlay.getWidthInPixels();
}
}
}
return (int)x;
}
@@ -164,7 +164,7 @@ public class VisualLinesIterator {
int foldIndex = myLocation.foldRegion;
while (foldIndex < myFoldRegions.length && myFoldRegions[foldIndex].getEndOffset() <= inlayOffset) foldIndex++;
if (foldIndex < myFoldRegions.length && myFoldRegions[foldIndex].getStartOffset() <= inlayOffset) continue;
(inlay.getPlacement() == Inlay.Placement.ABOVE_LINE ? myInlaysAbove : myInlaysBelow).add(inlay);
(inlay.getVerticalAlignment() == Inlay.VerticalAlignment.ABOVE_LINE ? myInlaysAbove : myInlaysBelow).add(inlay);
}
}
@@ -32,14 +32,6 @@ public class TextComponentInlayModel implements InlayModel {
return null;
}
@Nullable
@Override
public <T extends EditorCustomElementRenderer> Inlay<T> addAfterLineEndElement(int offset,
boolean relatesToPrecedingText,
@NotNull T renderer) {
return null;
}
@NotNull
@Override
public List<Inlay> getInlineElementsInRange(int startOffset, int endOffset) {
@@ -75,18 +67,6 @@ public class TextComponentInlayModel implements InlayModel {
return null;
}
@NotNull
@Override
public List<Inlay> getAfterLineEndElementsInRange(int startOffset, int endOffset) {
return Collections.emptyList();
}
@NotNull
@Override
public List<Inlay> getAfterLineEndElementsForLogicalLine(int logicalLine) {
return Collections.emptyList();
}
@Override
public void setConsiderCaretPositionOnDocumentUpdates(boolean enabled) {}