From ccde60def28880ec8b333f8cfe7e124f2fb18ac7 Mon Sep 17 00:00:00 2001 From: Vojtech Balik Date: Sun, 2 Aug 2026 18:35:58 +0200 Subject: [PATCH] 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 --- .../openapi/editor/impl/SoftWrapEngine.java | 8 ++- .../mapping/SoftWrapApplianceManager.java | 9 ++- .../impl/EditorCharacterGridSizeTest.java | 60 +++++++++++++++++-- .../view/impl/TerminalEditorFactory.kt | 1 + 4 files changed, 71 insertions(+), 7 deletions(-) diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/SoftWrapEngine.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/SoftWrapEngine.java index 6805f8df4360..baef60eabb8a 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/SoftWrapEngine.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/SoftWrapEngine.java @@ -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; diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/SoftWrapApplianceManager.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/SoftWrapApplianceManager.java index 3685a5aba21e..01cc4af1ace9 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/SoftWrapApplianceManager.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/SoftWrapApplianceManager.java @@ -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"); diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/EditorCharacterGridSizeTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/EditorCharacterGridSizeTest.java index 4e548fcacf0d..f0fbfd4a95ee 100644 --- a/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/EditorCharacterGridSizeTest.java +++ b/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/EditorCharacterGridSizeTest.java @@ -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; + } + } } diff --git a/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalEditorFactory.kt b/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalEditorFactory.kt index 294922d32ce0..f584d8924007 100644 --- a/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalEditorFactory.kt +++ b/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalEditorFactory.kt @@ -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) }