diff --git a/java/java-tests/testData/codeInsight/softwrap/LongLineOfIdSymbolsIsNotSoftWrapped.txt b/java/java-tests/testData/codeInsight/softwrap/LongLineOfIdSymbolsIsNotSoftWrapped.txt new file mode 100644 index 000000000000..e60c0683feaa --- /dev/null +++ b/java/java-tests/testData/codeInsight/softwrap/LongLineOfIdSymbolsIsNotSoftWrapped.txt @@ -0,0 +1,3 @@ +abcdefghijklmnopqrstuvwxyz +123 +ABCDEFGHIJKLMNOPQRSTUVWXYZ \ No newline at end of file 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 6b2403b10f40..36e5cdeddb49 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 @@ -31,6 +31,7 @@ import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.text.StringUtil; import gnu.trove.TIntIntHashMap; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; @@ -161,6 +162,7 @@ public class SoftWrapApplianceManager implements FoldingListener, DocumentListen int softWrapStartOffset = startLineContext.offset; int reservedWidth = myPainter.getMinDrawingWidth(SoftWrapDrawingType.BEFORE_SOFT_WRAP_LINE_FEED); + SoftWrap delayedSoftWrap = null; // Perform soft wraps calculation. outer: @@ -175,10 +177,8 @@ public class SoftWrapApplianceManager implements FoldingListener, DocumentListen } if (newX + reservedWidth >= myVisibleAreaWidth) { logicalLineData.update(currentFold.getStartOffset(), spaceWidth); - SoftWrap softWrap = registerSoftWrap( - softWrapStartOffset, start, start, logicalLineData.indentInColumns, - logicalLineData.indentInPixels, spaceWidth - ); + SoftWrap softWrap = registerSoftWrap(softWrapStartOffset, start, start, spaceWidth, logicalLineData); + assert softWrap != null; // We expect that it's always possible to wrap collapsed fold region placeholder text softWrapStartOffset = softWrap.getStart(); if (softWrap.getStart() < start) { revertListeners(softWrap.getStart(), context.visualLine); @@ -242,6 +242,14 @@ public class SoftWrapApplianceManager implements FoldingListener, DocumentListen fontType = offset2fontType.get(i); } context.symbol = c; + + if (delayedSoftWrap != null && delayedSoftWrap.getStart() == i) { + processSoftWrap(delayedSoftWrap, context); + softWrapStartOffset = delayedSoftWrap.getStart(); + startLineContext.from(context); + delayedSoftWrap = null; + } + if (c == '\n') { processSymbol(context, startLineContext, logicalLineData, fontType, 0, fontType2spaceWidth, offset2widthInPixels, offset2fontType); softWrapStartOffset = startLineContext.offset; @@ -258,15 +266,29 @@ public class SoftWrapApplianceManager implements FoldingListener, DocumentListen if (newX + reservedWidth >= myVisibleAreaWidth) { logicalLineData.update(i, spaceWidth); SoftWrap softWrap = registerSoftWrap( - softWrapStartOffset, Math.max(softWrapStartOffset, i - 1), calculateSoftWrapEndOffset(softWrapStartOffset, end), - logicalLineData.indentInColumns, logicalLineData.indentInPixels, spaceWidth + softWrapStartOffset, Math.max(softWrapStartOffset, i - 1), + calculateSoftWrapEndOffset(softWrapStartOffset, logicalLineData.endLineOffset), spaceWidth, logicalLineData ); + if (softWrap == null) { + processSymbol(context, startLineContext, logicalLineData, fontType, newX, fontType2spaceWidth, offset2widthInPixels, + offset2fontType); + continue; + } int newI = softWrap.getStart(); - // There are two possible options: soft wrap offset is located before/after the current offset (it may be - // located after offset in situation when it's not possible to wrap in [softWrapStartOffset; currentOffset) - // interval). We should process that accordingly. - if (newI < i) { + // There are three possible options: + // 1. Soft wrap offset is located before the current offset; + // 2. Soft wrap offset is located after the current offset but doesn't exceed current token end offset + // (it may occur if there are no convenient wrap positions before the current offset); + // 3. Soft wrap offset is located after the current offset and exceeds current token end offset; + // We should process that accordingly. + if (newI > end) { + delayedSoftWrap = softWrap; + processSymbol(context, startLineContext, logicalLineData, fontType, newX, fontType2spaceWidth, offset2widthInPixels, + offset2fontType); + continue; + } + else if (newI < i) { revertListeners(newI, context.visualLine); for (int j = i - 1; j >= newI; j--) { int pixelsDiff = offset2widthInPixels.get(j); @@ -287,18 +309,8 @@ public class SoftWrapApplianceManager implements FoldingListener, DocumentListen } } - notifyListenersOnBeforeSoftWrap(context); + processSoftWrap(softWrap, context); softWrapStartOffset = newI; - - context.visualColumn = 0; - context.softWrapColumnDiff = context.visualColumn - context.foldingColumnDiff - context.logicalColumn; - context.softWrapLinesCurrent++; - context.visualLine++; - notifyListenersOnAfterSoftWrapLineFeed(context); - - context.x = softWrap.getIndentInPixels(); - context.visualColumn = softWrap.getIndentInColumns(); - context.softWrapColumnDiff += softWrap.getIndentInColumns(); i = newI - 1/* because of loop increment */; startLineContext.from(context); } @@ -383,9 +395,23 @@ public class SoftWrapApplianceManager implements FoldingListener, DocumentListen return result; } - private SoftWrap registerSoftWrap(int minOffset, int preferredOffset, int maxOffset, int indentInColumns, int indentInPixels, - int spaceSize) - { + /** + * This method is assumed to be called in situation when visible area width is exceeded. It tries to create and register + * new soft wrap which data is defined in accordance with the given parameters. + *

+ * There is a possible case that no soft wrap is created and registered. That is true, for example, for situation when + * we have a long line of text that doesn't contain white spaces, operators or any other symbols that may be used + * as a 'wrap points'. We just left such lines as-is. + * + * @param minOffset min line 'wrap point' offset + * @param preferredOffset preferred 'wrap point' offset, i.e. max offset which symbol doesn't exceed right margin + * @param maxOffset max line 'wrap point' offset + * @param spaceSize current space width in pixels + * @param lineData object that encapsulates information about currently processed logical line + * @return newly created and registered soft wrap if any; null otherwise + */ + @Nullable + private SoftWrap registerSoftWrap(int minOffset, int preferredOffset, int maxOffset, int spaceSize, LogicalLineData lineData) { Document document = myEditor.getDocument(); // Performance optimization implied by profiling results analysis. @@ -395,19 +421,37 @@ public class SoftWrapApplianceManager implements FoldingListener, DocumentListen int softWrapOffset = myLineWrapPositionStrategy.calculateWrapPosition( document.getCharsSequence(), minOffset, maxOffset, preferredOffset, minOffset != preferredOffset ); + if (softWrapOffset >= lineData.endLineOffset) { + return null; + } + int indent = 0; if (myCustomIndentUsedLastTime) { indent = myCustomIndentValueUsedLastTime; } SoftWrapImpl softWrap = new SoftWrapImpl( - new TextChangeImpl("\n" + StringUtil.repeatSymbol(' ', indentInColumns + indent), softWrapOffset, softWrapOffset), - indentInColumns + indent + 1/* for 'after soft wrap' drawing */, - indentInPixels + (indent * spaceSize) + myPainter.getMinDrawingWidth(SoftWrapDrawingType.AFTER_SOFT_WRAP) + new TextChangeImpl("\n" + StringUtil.repeatSymbol(' ', lineData.indentInColumns + indent), softWrapOffset, softWrapOffset), + lineData.indentInColumns + indent + 1/* for 'after soft wrap' drawing */, + lineData.indentInPixels + (indent * spaceSize) + myPainter.getMinDrawingWidth(SoftWrapDrawingType.AFTER_SOFT_WRAP) ); myStorage.storeOrReplace(softWrap, true); return softWrap; } + private void processSoftWrap(SoftWrap softWrap, ProcessingContext context) { + notifyListenersOnBeforeSoftWrap(context); + + context.visualColumn = 0; + context.softWrapColumnDiff = context.visualColumn - context.foldingColumnDiff - context.logicalColumn; + context.softWrapLinesCurrent++; + context.visualLine++; + notifyListenersOnAfterSoftWrapLineFeed(context); + + context.x = softWrap.getIndentInPixels(); + context.visualColumn = softWrap.getIndentInColumns(); + context.softWrapColumnDiff += softWrap.getIndentInColumns(); + } + /** * There is a possible case that we need to reparse the whole document (e.g. visible area width is changed or user-defined * soft wrap indent is changed etc). This method encapsulates that logic, i.e. it checks if necessary conditions are satisfied diff --git a/platform/platform-impl/testSrc/com/intellij/openapi/editor/impl/softwrap/mapping/SoftWrapApplianceManagerTest.java b/platform/platform-impl/testSrc/com/intellij/openapi/editor/impl/softwrap/mapping/SoftWrapApplianceManagerTest.java index 1f26f8f70dc7..4c02743824dc 100644 --- a/platform/platform-impl/testSrc/com/intellij/openapi/editor/impl/softwrap/mapping/SoftWrapApplianceManagerTest.java +++ b/platform/platform-impl/testSrc/com/intellij/openapi/editor/impl/softwrap/mapping/SoftWrapApplianceManagerTest.java @@ -15,6 +15,7 @@ */ package com.intellij.openapi.editor.impl.softwrap.mapping; +import com.intellij.openapi.editor.ex.SoftWrapModelEx; import com.intellij.openapi.editor.impl.SoftWrapModelImpl; import com.intellij.testFramework.LightPlatformCodeInsightTestCase; @@ -38,11 +39,23 @@ public class SoftWrapApplianceManagerTest extends LightPlatformCodeInsightTestCa init(800); int offset = myEditor.getDocument().getTextLength() + 1; - assertNull(myEditor.getSoftWrapModel().getSoftWrap(offset)); + assertTrue(getSoftWrapModel().getRegisteredSoftWraps().isEmpty()); type(" thisisalongtokenthatisnotexpectedtobebrokenintopartsduringsoftwrapping"); assertNotNull(myEditor.getSoftWrapModel().getSoftWrap(offset)); } + public void testLongLineOfIdSymbolsIsNotSoftWrapped() throws Exception { + init(100); + assertTrue(getSoftWrapModel().getRegisteredSoftWraps().isEmpty()); + type('1'); + assertTrue(getSoftWrapModel().getRegisteredSoftWraps().isEmpty()); + + int offset = myEditor.getDocument().getText().indexOf("\n"); + type(" test"); + assertEquals(1, getSoftWrapModel().getRegisteredSoftWraps().size()); + assertNotNull(getSoftWrapModel().getSoftWrap(offset)); + } + private void init(final int visibleWidth) throws Exception { configureByFile(PATH + getTestName(false) + ".txt"); myEditor.getSettings().setUseSoftWraps(true); @@ -58,4 +71,8 @@ public class SoftWrapApplianceManagerTest extends LightPlatformCodeInsightTestCa }); applianceManager.registerSoftWrapIfNecessary(new Rectangle(visibleWidth, visibleWidth * 2), 0); } + + private static SoftWrapModelEx getSoftWrapModel() { + return (SoftWrapModelEx)myEditor.getSoftWrapModel(); + } }