IJPL-251582 [editor, markdown, terminal] soft-wraps: make optimized grid mode soft-wrapping algo opt-in

IJ-CR-217736

Because the optimization also alters behavior by not respecting LineWrapPositionStrategy

(cherry picked from commit a402c423489e5b679babc7e7986b8e6635342aa9)

GitOrigin-RevId: 9c3665181035655699f28d5deb3d5bb55d3c736f
This commit is contained in:
Vojtech Balik
2026-08-10 17:09:24 +00:00
committed by intellij-monorepo-bot
parent 08a68de340
commit ccde60def2
4 changed files with 71 additions and 7 deletions
@@ -42,6 +42,7 @@ public final class SoftWrapEngine {
private final int mySoftWrapWidth;
private final IncrementalCacheUpdateEvent myEvent;
private final int myRelativeIndent;
private final boolean myAllowGridModeOptimizations;
private LineWrapPositionStrategy myLineWrapPositionStrategy;
@@ -52,7 +53,8 @@ public final class SoftWrapEngine {
@NotNull IncrementalCacheUpdateEvent event,
@Nullable LineWrapPositionStrategy lineWrapStrategy,
int visibleWidth,
int relativeIndent) {
int relativeIndent,
boolean allowGridModeOptimizations) {
myEditor = editor;
myDocument = editor.getElfDocument();
myText = myDocument.getImmutableCharSequence();
@@ -65,6 +67,7 @@ public final class SoftWrapEngine {
myEvent = event;
myRelativeIndent = relativeIndent;
myLineWrapPositionStrategy = lineWrapStrategy;
myAllowGridModeOptimizations = allowGridModeOptimizations;
}
public void generate() {
@@ -79,7 +82,8 @@ public final class SoftWrapEngine {
var customWraps = myEditor.getCustomWrapModel().getWrapsInRange(startOffset, maxEndOffset);
var grid = myEditor.getCharacterGrid();
if (grid != null && inlineInlays.isEmpty() && afterLineEndInlays.isEmpty() && customWraps.isEmpty()
if (grid != null && myAllowGridModeOptimizations
&& inlineInlays.isEmpty() && afterLineEndInlays.isEmpty() && customWraps.isEmpty()
&& !SoftWrapHelper.hasCollapsedOffsetsIn(myEditor.getFoldingModel(), startOffset, maxEndOffset)) {
generateGridSoftWraps(grid, startOffset, minEndOffset, maxEndOffset);
return;
@@ -98,6 +98,7 @@ public final class SoftWrapApplianceManager implements Dumpable {
private int myDocumentChangeEndOffset = -1;
private int myAvailableWidth = QUICK_DUMMY_WRAPPING;
private @Nullable LineWrapPositionStrategy myLineWrapPositionStrategy;
private boolean myAllowGridModeOptimizations;
@ApiStatus.Internal
@@ -125,6 +126,12 @@ public final class SoftWrapApplianceManager implements Dumpable {
mySoftWrapsUnderScrollBar = softWrapsUnderScrollBar;
}
@ApiStatus.Internal
public void setAllowGridModeOptimizations(boolean allowGridModeOptimizations) {
myAllowGridModeOptimizations = allowGridModeOptimizations;
reset();
}
@ApiStatus.Internal
public void reset() {
myIsDirty = true;
@@ -215,7 +222,7 @@ public final class SoftWrapApplianceManager implements Dumpable {
}
else {
new SoftWrapEngine(myEditor, myPainter, myStorage, myDataMapper, event, myLineWrapPositionStrategy, myVisibleAreaWidth,
myCustomIndentUsedLastTime ? myCustomIndentValueUsedLastTime : -1).generate();
myCustomIndentUsedLastTime ? myCustomIndentValueUsedLastTime : -1, myAllowGridModeOptimizations).generate();
}
if (LOG.isDebugEnabled()) {
LOG.debug("Soft wrap recalculation done: " + event + ". " + (event.getActualEndOffset() - event.getStartOffset()) + " characters processed");
@@ -1,16 +1,30 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.openapi.editor.impl;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.LineWrapPositionStrategy;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.impl.softwrap.mapping.SoftWrapApplianceManager;
import com.intellij.openapi.project.Project;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class EditorCharacterGridSizeTest extends AbstractEditorTest {
private static final String LONG_LINE = "x".repeat(200);
private static final int CONTENT_WIDTH = LONG_LINE.length() * TEST_CHAR_WIDTH;
private static final char WRAP_MARKER = '|';
private void enableGridMode() {
private @NotNull SoftWrapApplianceManager softWrapApplianceManager() {
return ((SoftWrapModelImpl)getEditor().getSoftWrapModel()).getApplianceManager();
}
private void enableGridMode(boolean allowSoftWrapOptimizations) {
getEditor().getSettings().setCharacterGridWidthMultiplier(1.0f);
((EditorEx)getEditor()).reinitSettings();
assertNotNull(((EditorImpl)getEditor()).getCharacterGrid());
var softWrapApplianceManager = softWrapApplianceManager();
softWrapApplianceManager.setAllowGridModeOptimizations(allowSoftWrapOptimizations);
softWrapApplianceManager.recalculateIfNecessary("allow grid mode optimizations maybe toggled");
}
private int preferredWidth() {
@@ -22,7 +36,7 @@ public class EditorCharacterGridSizeTest extends AbstractEditorTest {
initText(LONG_LINE);
getEditor().getSettings().setAdditionalColumnsCount(0);
setEditorVisibleSize(20, 10);
enableGridMode();
enableGridMode(false);
assertTrue(getEditor().getScrollingModel().getVisibleArea().width < CONTENT_WIDTH);
assertTrue("preferred=" + preferredWidth(), preferredWidth() >= CONTENT_WIDTH);
@@ -33,7 +47,7 @@ public class EditorCharacterGridSizeTest extends AbstractEditorTest {
initText(LONG_LINE);
getEditor().getSettings().setAdditionalColumnsCount(0);
configureSoftWraps(20);
enableGridMode();
enableGridMode(true);
assertTrue("preferred=" + preferredWidth(), preferredWidth() < CONTENT_WIDTH);
}
@@ -43,8 +57,46 @@ public class EditorCharacterGridSizeTest extends AbstractEditorTest {
initText(LONG_LINE.substring(0, 20));
configureSoftWraps(10, false);
addCollapsedFoldRegion(8, 14, "..."); // covers the grid column boundary at offset 11
enableGridMode();
enableGridMode(true);
verifySoftWrapPositions(8);
}
// The grid mode soft-wrapping optimization is not a pure optimization:
// it places wraps at grid column boundaries without consulting LineWrapPositionStrategy.
// Grid editors that didn't opt in should keep the strategy-based wrap positions.
public void testGridEditorWithoutSoftWrapOptimizationsRespectsLineWrapPositionStrategy() {
initText(("xxx" + WRAP_MARKER).repeat(10));
configureSoftWraps(10, false);
softWrapApplianceManager().setLineWrapPositionStrategy(new MarkerLineWrapPositionStrategy());
enableGridMode(/* allowSoftWrapOptimizations */ false);
verifySoftWrapPositions(7, 15, 23, 31);
}
// Wraps right before {@link #WRAP_MARKER} only
private static final class MarkerLineWrapPositionStrategy implements LineWrapPositionStrategy {
@Override
public int calculateWrapPosition(@NotNull Document document,
@Nullable Project project,
int startOffset,
int endOffset,
int maxPreferredOffset,
boolean allowToBeyondMaxPreferredOffset,
boolean isSoftWrap) {
assertTrue(isSoftWrap);
CharSequence text = document.getImmutableCharSequence();
for (int offset = endOffset - 1; offset > startOffset; offset--) {
if (text.charAt(offset) == WRAP_MARKER) {
return offset;
}
}
return -1;
}
@Override
public boolean canWrapLineAtOffset(CharSequence text, int offset) {
return false;
}
}
}
@@ -154,6 +154,7 @@ object TerminalEditorFactory {
editor.settings.isUseCustomSoftWrapIndent = false
val softWrapModel = editor.softWrapModel
softWrapModel.applianceManager.setLineWrapPositionStrategy(TerminalLineWrapPositionStrategy())
softWrapModel.applianceManager.setAllowGridModeOptimizations(true)
softWrapModel.applianceManager.setSoftWrapsUnderScrollBar(true)
softWrapModel.setSoftWrapPainter(EmptySoftWrapPainter)
}