diff --git a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/enter/BaseIndentEnterHandler.java b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/enter/BaseIndentEnterHandler.java index 1793195ca3e3..285eaa828930 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/enter/BaseIndentEnterHandler.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/enter/BaseIndentEnterHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2010 JetBrains s.r.o. + * Copyright 2000-2012 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. @@ -94,7 +94,7 @@ public class BaseIndentEnterHandler extends EnterHandlerDelegateAdapter { PsiDocumentManager.getInstance(project).commitDocument(editor.getDocument()); int caret = editor.getCaretModel().getOffset(); - if (caret == 0) { + if (caret <= 0) { return Result.Continue; } diff --git a/platform/lang-impl/src/com/intellij/injected/editor/EditorWindow.java b/platform/lang-impl/src/com/intellij/injected/editor/EditorWindow.java index c0accddd92fd..46341c274783 100644 --- a/platform/lang-impl/src/com/intellij/injected/editor/EditorWindow.java +++ b/platform/lang-impl/src/com/intellij/injected/editor/EditorWindow.java @@ -774,6 +774,16 @@ public class EditorWindow extends UserDataHolderBase implements EditorEx { myDelegate.setStickySelection(enable); } + @Override + public boolean isPurePaintingMode() { + return myDelegate.isPurePaintingMode(); + } + + @Override + public void setPurePaintingMode(boolean enabled) { + myDelegate.setPurePaintingMode(enabled); + } + @Override public void setPrefixTextAndAttributes(@Nullable String prefixText, @Nullable TextAttributes attributes) { myDelegate.setPrefixTextAndAttributes(prefixText, attributes); diff --git a/platform/platform-api/src/com/intellij/openapi/editor/LineWrapPositionStrategy.java b/platform/platform-api/src/com/intellij/openapi/editor/LineWrapPositionStrategy.java index 2e13a3fb8f09..d4ec5493437c 100644 --- a/platform/platform-api/src/com/intellij/openapi/editor/LineWrapPositionStrategy.java +++ b/platform/platform-api/src/com/intellij/openapi/editor/LineWrapPositionStrategy.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2010 JetBrains s.r.o. + * Copyright 2000-2012 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. @@ -36,17 +36,17 @@ public interface LineWrapPositionStrategy { * * @param document target document which text is being processed * @param project target project - * @param startOffset start offset to use with the given text holder (inclusive) + * @param startOffset start offset to use with the given text holder (exclusive) * @param endOffset end offset to use with the given text holder (exclusive) * @param maxPreferredOffset this method is expected to do its best to return offset that belongs to * (startOffset; maxPreferredOffset] interval. However, it's allowed - * to return value from (maxPreferredOffset; endOffset] interval + * to return value from (maxPreferredOffset; endOffset) interval * unless 'allowToBeyondMaxPreferredOffset' if 'false' * @param allowToBeyondMaxPreferredOffset indicates if it's allowed to return value from * (maxPreferredOffset; endOffset] interval in case of inability to * find appropriate offset from (startOffset; maxPreferredOffset] interval * @param virtual identifies if current request is for virtual wrap (soft wrap) position - * @return offset from (startOffset; endOffset] interval where + * @return offset from (startOffset; endOffset) interval where * target line should be wrapped OR -1 if no wrapping should be performed */ int calculateWrapPosition( diff --git a/platform/platform-impl/src/com/intellij/codeInsight/hint/EditorFragmentComponent.java b/platform/platform-impl/src/com/intellij/codeInsight/hint/EditorFragmentComponent.java index bf92d9d976b0..afaf3b773465 100644 --- a/platform/platform-impl/src/com/intellij/codeInsight/hint/EditorFragmentComponent.java +++ b/platform/platform-impl/src/com/intellij/codeInsight/hint/EditorFragmentComponent.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2012 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. @@ -89,7 +89,13 @@ public class EditorFragmentComponent extends JPanel { textGraphics.translate(0, -y1); textGraphics.setClip(0, y1, textWidth, height); final boolean wasVisible = editor.setCaretVisible(false); - editor.getContentComponent().paint(textGraphics); + editor.setPurePaintingMode(true); + try { + editor.getContentComponent().paint(textGraphics); + } + finally { + editor.setPurePaintingMode(false); + } if (wasVisible) { editor.setCaretVisible(true); } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/ex/EditorEx.java b/platform/platform-impl/src/com/intellij/openapi/editor/ex/EditorEx.java index 33c3e34cc175..0ecb86e779bf 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/ex/EditorEx.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/ex/EditorEx.java @@ -203,6 +203,12 @@ public interface EditorEx extends Editor { */ void setStickySelection(boolean enable); + /** + * @return width in pixels of the {@link #setPrefixTextAndAttributes(String, TextAttributes) prefix} used with the current editor if any; + * zero otherwise + */ + int getPrefixTextWidthInPixels(); + /** * Allows to define prefix to be displayed on every editor line and text attributes to use for its coloring. * @@ -212,8 +218,21 @@ public interface EditorEx extends Editor { void setPrefixTextAndAttributes(@Nullable String prefixText, @Nullable TextAttributes attributes); /** - * @return width in pixels of the {@link #setPrefixTextAndAttributes(String, TextAttributes) prefix} used with the current editor if any; - * zero otherwise + * @return current 'pure painting mode' status + * @see #setPurePaintingMode(boolean) */ - int getPrefixTextWidthInPixels(); + boolean isPurePaintingMode(); + + /** + * We often re-use the logic encapsulated at the editor. For example, every time we show editor fragment (folding, preview etc) we + * create a dedicated graphics object and ask the editor to paint into it. + *

+ * The thing is that the editor itself may change its state if any postponed operation is triggered by the painting request + * (e.g. soft wraps recalculation is triggered by the paint request and newly calculated soft wraps cause caret to change its position). + *

+ * This method allows to inform the editor that all subsequent painting request should not change the editor state. + * + * @param enabled 'pure painting mode' status to use + */ + void setPurePaintingMode(boolean enabled); } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/CaretModelImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/CaretModelImpl.java index 0263f6332cd8..2cdabfa5e067 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/CaretModelImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/CaretModelImpl.java @@ -116,7 +116,9 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener, @Override public void updateFinished(@NotNull Document doc) { if (doc != myEditor.getDocument() || myIsInUpdate) return; - if (savedBeforeBulkCaretMarker != null && savedBeforeBulkCaretMarker.isValid()) { + if (savedBeforeBulkCaretMarker != null && savedBeforeBulkCaretMarker.isValid() + && savedBeforeBulkCaretMarker.getStartOffset() != myOffset) + { moveToOffset(savedBeforeBulkCaretMarker.getStartOffset()); } releaseBulkCaretMarker(); diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java index 28e18587875e..35762cecc63b 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java @@ -285,6 +285,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi private int myStickySelectionStart; private boolean myScrollToCaret = true; + private boolean myPurePaintingMode; private boolean myPaintSelection; private final EditorSizeAdjustmentStrategy mySizeAdjustmentStrategy = new EditorSizeAdjustmentStrategy(); @@ -499,6 +500,11 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi } } + @Override + public int getPrefixTextWidthInPixels() { + return myPrefixWidthInPixels; + } + @Override public void setPrefixTextAndAttributes(@Nullable String prefixText, @Nullable TextAttributes attributes) { myPrefixText = prefixText == null ? null: prefixText.toCharArray(); @@ -513,8 +519,13 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi } @Override - public int getPrefixTextWidthInPixels() { - return myPrefixWidthInPixels; + public boolean isPurePaintingMode() { + return myPurePaintingMode; + } + + @Override + public void setPurePaintingMode(boolean enabled) { + myPurePaintingMode = enabled; } @Override @@ -2001,7 +2012,9 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi boolean locateBeforeSoftWrap = !SoftWrapHelper.isCaretAfterSoftWrap(this); int start = clipStartOffset; int end = clipEndOffset; - getSoftWrapModel().registerSoftWrapsIfNecessary(); + if (!myPurePaintingMode) { + getSoftWrapModel().registerSoftWrapsIfNecessary(); + } LineIterator lIterator = createLineIterator(); lIterator.start(start); diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/SoftWrapModelImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/SoftWrapModelImpl.java index 4c09db41fac7..c520e149730b 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/SoftWrapModelImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/SoftWrapModelImpl.java @@ -222,7 +222,7 @@ public class SoftWrapModelImpl implements SoftWrapModelEx, PrioritizedDocumentLi @Override public boolean isSoftWrappingEnabled() { - if (!myUseSoftWraps || myEditor.isOneLineMode()) { + if (!myUseSoftWraps || myEditor.isOneLineMode() || myEditor.isPurePaintingMode()) { return false; } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/CacheEntry.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/CacheEntry.java index 80f20e15a270..63574c2bcba2 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/CacheEntry.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/CacheEntry.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2011 JetBrains s.r.o. + * Copyright 2000-2012 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. @@ -25,10 +25,7 @@ import gnu.trove.TObjectProcedure; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; +import java.util.*; /** * Encapsulates information to cache for the single visual line. @@ -131,6 +128,23 @@ class CacheEntry implements Comparable, Cloneable { return result; } + /** + * Removes fold data for all fold regions that start at or after the given offset. + * + * @param offset target offset + */ + public void removeAllFoldDataAtOrAfter(final int offset) { + if (myFoldingData == DUMMY || myFoldingData.isEmpty()) { + return; + } + myFoldingData.retainEntries(new TIntObjectProcedure() { + @Override + public boolean execute(int a, FoldingData b) { + return a < offset; + } + }); + } + @Nullable public FoldingData getFoldingData(@NotNull final FoldRegion region) { FoldingData candidate = myFoldingData.get(region.getStartOffset()); diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/CachingSoftWrapDataMapper.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/CachingSoftWrapDataMapper.java index a6c245998eea..de2a4ea5f19e 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/CachingSoftWrapDataMapper.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/CachingSoftWrapDataMapper.java @@ -285,7 +285,11 @@ public class CachingSoftWrapDataMapper implements SoftWrapDataMapper, SoftWrapAw @Override public void revertToOffset(final int offset, int visualLine) { - // Do nothing in assumption that we store only information about start and end visual line positions and + final CacheEntry entry = getCacheEntryForVisualLine(visualLine, false); + if (entry != null) { + entry.removeAllFoldDataAtOrAfter(offset); + } + // Do nothing more in assumption that we store only information about start and end visual line positions and // that start information remains the same and end of line is not reached yet. } 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 52745723e8e7..49b19dee6117 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 @@ -24,6 +24,7 @@ import com.intellij.openapi.editor.event.DocumentEvent; import com.intellij.openapi.editor.event.DocumentListener; import com.intellij.openapi.editor.event.VisibleAreaEvent; import com.intellij.openapi.editor.event.VisibleAreaListener; +import com.intellij.openapi.editor.ex.DocumentEx; import com.intellij.openapi.editor.ex.EditorEx; import com.intellij.openapi.editor.ex.ScrollingModelEx; import com.intellij.openapi.editor.ex.util.EditorUtil; @@ -80,7 +81,6 @@ public class SoftWrapApplianceManager implements SoftWrapFoldingListener, Docume private final SoftWrapsStorage myStorage; private final EditorEx myEditor; private final SoftWrapPainter myPainter; - private final EditorTextRepresentationHelper myRepresentationHelper; private final SoftWrapDataMapper myDataMapper; /** @@ -93,15 +93,16 @@ public class SoftWrapApplianceManager implements SoftWrapFoldingListener, Docume */ private int myLastTopLeftCornerOffset = -1; - private VisibleAreaWidthProvider myWidthProvider; - private LineWrapPositionStrategy myLineWrapPositionStrategy; - private IncrementalCacheUpdateEvent myEventBeingProcessed; - private boolean myVisualAreaListenerAttached; - private boolean myCustomIndentUsedLastTime; - private int myCustomIndentValueUsedLastTime; - private int myVisibleAreaWidth; - private boolean myInProgress; - private boolean myHasLinesWithFailedWrap; + private VisibleAreaWidthProvider myWidthProvider; + private EditorTextRepresentationHelper myRepresentationHelper; + private LineWrapPositionStrategy myLineWrapPositionStrategy; + private IncrementalCacheUpdateEvent myEventBeingProcessed; + private boolean myVisualAreaListenerAttached; + private boolean myCustomIndentUsedLastTime; + private int myCustomIndentValueUsedLastTime; + private int myVisibleAreaWidth; + private boolean myInProgress; + private boolean myHasLinesWithFailedWrap; public SoftWrapApplianceManager(@NotNull SoftWrapsStorage storage, @NotNull EditorEx editor, @@ -303,6 +304,7 @@ public class SoftWrapApplianceManager implements SoftWrapFoldingListener, Docume * @param foldRegion target collapsed fold region to process * @return true if processing should be continued; false otherwise */ + @SuppressWarnings("MagicConstant") private boolean processCollapsedFoldRegion(FoldRegion foldRegion) { if (processOutOfDateFoldRegion(foldRegion)) { return false; @@ -312,10 +314,9 @@ public class SoftWrapApplianceManager implements SoftWrapFoldingListener, Docume if (placeholder.isEmpty()) { return true; } - FontInfo fontInfo = EditorUtil.fontForChar(placeholder.charAt(0), myContext.fontType, myEditor); int placeholderWidthInPixels = 0; for (int i = 0; i < placeholder.length(); i++) { - placeholderWidthInPixels += fontInfo.charWidth(placeholder.charAt(i)); + placeholderWidthInPixels += myRepresentationHelper.charWidth(placeholder.charAt(i), myContext.fontType); } int newX = myContext.currentPosition.x + placeholderWidthInPixels; @@ -491,19 +492,50 @@ public class SoftWrapApplianceManager implements SoftWrapFoldingListener, Docume } } + @SuppressWarnings("MagicConstant") private void createSoftWrapIfPossible() { final int offset = myContext.currentPosition.offset; myContext.logicalLineData.update(offset); int softWrapStartOffset = myContext.softWrapStartOffset; + int preferredOffset = Math.max(softWrapStartOffset, offset - 1 /* reserve a column for the soft wrap sign */); SoftWrap softWrap = registerSoftWrap( - softWrapStartOffset, Math.max(softWrapStartOffset, offset), - calculateSoftWrapEndOffset(softWrapStartOffset, myContext.logicalLineData.endLineOffset), myContext.getSpaceWidth(), + softWrapStartOffset, + preferredOffset, + calculateSoftWrapEndOffset(softWrapStartOffset, myContext.logicalLineData.endLineOffset), + myContext.getSpaceWidth(), myContext.logicalLineData ); + boolean revertedToFoldRegion = false; if (softWrap == null) { - myContext.tryToShiftToNextLine(); - myHasLinesWithFailedWrap = true; - return; + EditorPosition wrapPosition = null; + + // Try to insert soft wrap after the last collapsed fold region that is located on the current visual line. + if (myContext.lastFoldEndPosition != null && myStorage.getSoftWrap(myContext.lastFoldEndPosition.offset) == null + && myContext.lastFoldEndPosition.offset + 1/* reserve one visual column for the soft wrap sign */ + < myContext.currentPosition.offset) + { + wrapPosition = myContext.lastFoldEndPosition; + } + + if (wrapPosition == null && myContext.lastFoldStartPosition != null + && myStorage.getSoftWrap(myContext.lastFoldStartPosition.offset) == null + && myContext.lastFoldStartPosition.offset < myContext.currentPosition.offset) + { + wrapPosition = myContext.lastFoldStartPosition; + } + + if (wrapPosition != null){ + revertListeners(wrapPosition.offset, wrapPosition.visualLine); + myContext.currentPosition = wrapPosition; + softWrap = registerSoftWrap(wrapPosition.offset, myContext.getSpaceWidth(), myContext.logicalLineData); + myContext.tokenStartOffset = wrapPosition.offset; + revertedToFoldRegion = true; + } + else { + myContext.tryToShiftToNextLine(); + myHasLinesWithFailedWrap = true; + return; + } } myContext.skipToLineEnd = false; @@ -528,14 +560,16 @@ public class SoftWrapApplianceManager implements SoftWrapFoldingListener, Docume return; } else if (actualSoftWrapOffset < offset) { - revertListeners(actualSoftWrapOffset, myContext.currentPosition.visualLine); - for (int j = offset - 1; j >= actualSoftWrapOffset; j--) { - int pixelsDiff = myOffset2widthInPixels.data[j - myOffset2widthInPixels.anchor]; - int tmpFontType = myOffset2fontType.get(j); - int columnsDiff = calculateWidthInColumns(myContext.text.charAt(j), pixelsDiff, myContext.getSpaceWidth(tmpFontType)); - myContext.currentPosition.offset--; - myContext.currentPosition.logicalColumn -= columnsDiff; - myContext.currentPosition.visualColumn -= columnsDiff; + if (!revertedToFoldRegion) { + revertListeners(actualSoftWrapOffset, myContext.currentPosition.visualLine); + for (int j = offset - 1; j >= actualSoftWrapOffset; j--) { + int pixelsDiff = myOffset2widthInPixels.data[j - myOffset2widthInPixels.anchor]; + int tmpFontType = myOffset2fontType.get(j); + int columnsDiff = calculateWidthInColumns(myContext.text.charAt(j), pixelsDiff, myContext.getSpaceWidth(tmpFontType)); + myContext.currentPosition.offset--; + myContext.currentPosition.logicalColumn -= columnsDiff; + myContext.currentPosition.visualColumn -= columnsDiff; + } } } else if (actualSoftWrapOffset > offset) { @@ -549,6 +583,10 @@ public class SoftWrapApplianceManager implements SoftWrapFoldingListener, Docume myContext.currentPosition.offset = actualSoftWrapOffset; myOffset2fontType.clear(); myOffset2widthInPixels.clear(); + + if (revertedToFoldRegion && myContext.currentPosition.offset == myContext.lastFold.getStartOffset()) { + processCollapsedFoldRegion(myContext.lastFold); + } } private int calculateNewX(char c) { @@ -570,7 +608,7 @@ public class SoftWrapApplianceManager implements SoftWrapFoldingListener, Docume return i; } } - return end; + return Math.max(start, end); } private static int calculateWidthInColumns(char c, int widthInPixels, int spaceWithInPixels) { @@ -619,7 +657,9 @@ public class SoftWrapApplianceManager implements SoftWrapFoldingListener, Docume } if (softWrapOffset >= lineData.endLineOffset || softWrapOffset < 0 - || (myCustomIndentUsedLastTime && softWrapOffset == lineData.nonWhiteSpaceSymbolOffset)) + || (myCustomIndentUsedLastTime && softWrapOffset == lineData.nonWhiteSpaceSymbolOffset) + || (softWrapOffset > preferredOffset && myContext.lastFoldStartPosition != null // Prefer to wrap on fold region backwards + && myContext.lastFoldStartPosition.offset <= preferredOffset)) // to wrapping forwards. { return null; } @@ -923,8 +963,14 @@ public class SoftWrapApplianceManager implements SoftWrapFoldingListener, Docume recalculateIfNecessary(); } - public void setWidthProvider(VisibleAreaWidthProvider widthProvider) { + public void setWidthProvider(@NotNull VisibleAreaWidthProvider widthProvider) { myWidthProvider = widthProvider; + reset(); + } + + public void setRepresentationHelper(@NotNull EditorTextRepresentationHelper representationHelper) { + myRepresentationHelper = representationHelper; + reset(); } @NotNull @@ -1132,6 +1178,14 @@ public class SoftWrapApplianceManager implements SoftWrapFoldingListener, Docume public CharSequence text; public EditorPosition lineStartPosition; public EditorPosition currentPosition; + /** + * Start position of the last collapsed fold region that is located at the current visual line and can be used as a fall back + * position for soft wrapping. + */ + public EditorPosition lastFoldStartPosition; + public EditorPosition lastFoldEndPosition; + /** A fold region referenced by the {@link #lastFoldStartPosition}. */ + public FoldRegion lastFold; public SoftWrap delayedSoftWrap; public JComponent contentComponent; public int reservedWidthInPixels; @@ -1156,6 +1210,9 @@ public class SoftWrapApplianceManager implements SoftWrapFoldingListener, Docume text = null; lineStartPosition = null; currentPosition = null; + lastFoldStartPosition = null; + lastFoldEndPosition = null; + lastFold = null; delayedSoftWrap = null; contentComponent = null; reservedWidthInPixels = 0; @@ -1187,10 +1244,14 @@ public class SoftWrapApplianceManager implements SoftWrapFoldingListener, Docume /** * Asks current context to update its state assuming that it begins to point to the line next to its current position. */ + @SuppressWarnings("MagicConstant") public void onNewLine() { notifyListenersOnVisualLineEnd(); currentPosition.onNewLine(); softWrapStartOffset = currentPosition.offset; + lastFoldStartPosition = null; + lastFoldEndPosition = null; + lastFold = null; lineStartPosition.from(currentPosition); logicalLineData.update(currentPosition.logicalLine, getSpaceWidth(), myEditor); fontType = myOffset2fontType.get(currentPosition.offset); @@ -1213,6 +1274,7 @@ public class SoftWrapApplianceManager implements SoftWrapFoldingListener, Docume onNonLineFeedSymbol(c, newX); } + @SuppressWarnings("MagicConstant") public void onNonLineFeedSymbol(char c, int newX) { int widthInPixels = newX - myContext.currentPosition.x; @@ -1247,18 +1309,27 @@ public class SoftWrapApplianceManager implements SoftWrapFoldingListener, Docume * @param foldRegion collapsed fold region to process */ private void advance(FoldRegion foldRegion, int placeHolderWidthInPixels) { + lastFoldStartPosition = currentPosition.clone(); + lastFold = foldRegion; int visualLineBefore = currentPosition.visualLine; + int logicalLineBefore = currentPosition.logicalLine; int logicalColumnBefore = currentPosition.logicalColumn; currentPosition.advance(foldRegion); currentPosition.x += placeHolderWidthInPixels; int collapsedFoldingWidthInColumns = currentPosition.logicalColumn; - if (currentPosition.visualLine <= visualLineBefore) { + if (currentPosition.logicalLine <= logicalLineBefore) { // Single-line fold region. collapsedFoldingWidthInColumns = currentPosition.logicalColumn - logicalColumnBefore; } + else { + final DocumentEx document = myEditor.getDocument(); + int endFoldLine = document.getLineNumber(foldRegion.getEndOffset()); + logicalLineData.endLineOffset = document.getLineEndOffset(endFoldLine); + } notifyListenersOnFoldRegion(foldRegion, collapsedFoldingWidthInColumns, visualLineBefore); tokenStartOffset = myContext.currentPosition.offset; softWrapStartOffset = foldRegion.getEndOffset(); + lastFoldEndPosition = currentPosition.clone(); } /** @@ -1288,7 +1359,7 @@ public class SoftWrapApplianceManager implements SoftWrapFoldingListener, Docume * @return true if given 'x' coordinate exceeds visual area's right edge; false otherwise */ public boolean exceedsVisualEdge(int x) { - return x + reservedWidthInPixels >= myVisibleAreaWidth; + return x >= myVisibleAreaWidth; } } diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/softwrap/mapping/SoftWrapApplianceOnDocumentModificationTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/softwrap/mapping/SoftWrapApplianceOnDocumentModificationTest.java index 28ffb0cbe25e..ed10dc32596d 100644 --- a/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/softwrap/mapping/SoftWrapApplianceOnDocumentModificationTest.java +++ b/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/softwrap/mapping/SoftWrapApplianceOnDocumentModificationTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2011 JetBrains s.r.o. + * Copyright 2000-2012 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. @@ -17,9 +17,7 @@ package com.intellij.openapi.editor.impl.softwrap.mapping; import com.intellij.codeInsight.folding.CodeFoldingManager; import com.intellij.openapi.editor.*; -import com.intellij.openapi.editor.impl.AbstractEditorProcessingOnDocumentModificationTest; -import com.intellij.openapi.editor.impl.EditorImpl; -import com.intellij.openapi.editor.impl.SoftWrapModelImpl; +import com.intellij.openapi.editor.impl.*; import com.intellij.openapi.editor.markup.TextAttributes; import com.intellij.psi.codeStyle.CommonCodeStyleSettings; import com.intellij.testFramework.TestFileType; @@ -64,7 +62,7 @@ public class SoftWrapApplianceOnDocumentModificationTest extends AbstractEditorP public void testSoftWrapAdditionOnTyping() throws Exception { String text = "this is a test string that is expected to end just before right margin"; - init(800, text); + init(100, text); int offset = myEditor.getDocument().getTextLength() + 1; assertTrue(getSoftWrapModel().getRegisteredSoftWraps().isEmpty()); @@ -86,7 +84,7 @@ public class SoftWrapApplianceOnDocumentModificationTest extends AbstractEditorP "123\n" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - init(100, text); + init(15, text); assertTrue(getSoftWrapModel().getRegisteredSoftWraps().isEmpty()); type('1'); assertTrue(getSoftWrapModel().getRegisteredSoftWraps().isEmpty()); @@ -105,7 +103,7 @@ public class SoftWrapApplianceOnDocumentModificationTest extends AbstractEditorP " }\n" + "}"; - init(300, text); + init(40, text); final FoldingModel foldingModel = myEditor.getFoldingModel(); assertEmpty(foldingModel.getAllFoldRegions()); @@ -133,7 +131,7 @@ public class SoftWrapApplianceOnDocumentModificationTest extends AbstractEditorP " }\n" + "}"; - init(300, text); + init(40, text); type('\n'); VisualPosition position = myEditor.getCaretModel().getVisualPosition(); assertEquals(new VisualPosition(5, 0), position); @@ -148,7 +146,7 @@ public class SoftWrapApplianceOnDocumentModificationTest extends AbstractEditorP "}\n" + "abcde"; - init(300, text); + init(40, text); int offset = text.indexOf("abcde"); myEditor.getSelectionModel().setSelection(offset, text.length()); delete(); @@ -161,7 +159,7 @@ public class SoftWrapApplianceOnDocumentModificationTest extends AbstractEditorP "}\n" + ""; - init(300, text); + init(40, text); type('\t'); assertEquals(new VisualPosition(2, 4), myEditor.getCaretModel().getVisualPosition()); } @@ -171,7 +169,7 @@ public class SoftWrapApplianceOnDocumentModificationTest extends AbstractEditorP "line1\n" + "long line"; - init(48, text); + init(48, text, 7); int softWrapsBefore = getSoftWrapModel().getRegisteredSoftWraps().size(); assertTrue(softWrapsBefore > 0); @@ -191,7 +189,7 @@ public class SoftWrapApplianceOnDocumentModificationTest extends AbstractEditorP " }\n" + "}"; - init(700, text); + init(100, text); int startFoldOffset = text.indexOf('@'); int endFoldOffset = text.indexOf(')'); @@ -215,7 +213,7 @@ public class SoftWrapApplianceOnDocumentModificationTest extends AbstractEditorP "line5\n" + "line6"; - init(700, text); + init(100, text); type("\nq"); assertEquals(new VisualPosition(7, 1), myEditor.offsetToVisualPosition(myEditor.getDocument().getTextLength())); } @@ -225,7 +223,7 @@ public class SoftWrapApplianceOnDocumentModificationTest extends AbstractEditorP String text = "line1\n" + "second line that is long enough to be soft wrapped"; - init(100, text); + init(15, text); TIntHashSet offsetsBefore = collectSoftWrapStartOffsets(1); assertTrue(!offsetsBefore.isEmpty()); @@ -257,7 +255,7 @@ public class SoftWrapApplianceOnDocumentModificationTest extends AbstractEditorP "ordinary line 7\n" + "ordinary line 8\n"; - init(200, text); + init(30, text); LogicalPosition position = myEditor.visualToLogicalPosition(new VisualPosition(8, 0)); assertSame(7, position.line); // Position from soft-wrapped part of the line @@ -274,7 +272,7 @@ public class SoftWrapApplianceOnDocumentModificationTest extends AbstractEditorP " */\n" + "public class Test {\n" + "}"; - init(500, text); + init(100, text); addCollapsedFoldRegion(0, text.indexOf("public") - 1, "/**...*/"); @@ -293,7 +291,7 @@ public class SoftWrapApplianceOnDocumentModificationTest extends AbstractEditorP // There was a problem that removing text from the last document line that was soft-wrapped removed soft wraps as well. String text = "This a long string that is expected to be wrapped in more than one visual line"; - init(150, text); + init(20, text); List softWrapsBeforeModification = new ArrayList(getSoftWrapModel().getRegisteredSoftWraps()); assertTrue(softWrapsBeforeModification.size() > 0); @@ -307,7 +305,7 @@ public class SoftWrapApplianceOnDocumentModificationTest extends AbstractEditorP String text = "Line1\n" + "Long line2 that is expected to be soft-wrapped"; - init(150, text); + init(20, text); List softWrapsBeforeModification = new ArrayList(getSoftWrapModel().getRegisteredSoftWraps()); assertTrue(softWrapsBeforeModification.size() > 0); @@ -331,7 +329,7 @@ public class SoftWrapApplianceOnDocumentModificationTest extends AbstractEditorP "fold line3\n" + "fold line4\n" + "normal line1"; - init(150, text); + init(20, text); int afterFoldOffset = text.indexOf("normal line1"); addCollapsedFoldRegion(text.indexOf("fold line1") + 2, afterFoldOffset - 1, "..."); @@ -352,7 +350,7 @@ public class SoftWrapApplianceOnDocumentModificationTest extends AbstractEditorP "line5\n" + "this is long line that is expected to be soft-wrapped\n" + "line6"; - init(100, text); + init(15, text); assertTrue(!getSoftWrapModel().getRegisteredSoftWraps().isEmpty()); @@ -392,7 +390,7 @@ public class SoftWrapApplianceOnDocumentModificationTest extends AbstractEditorP " */\n" + "class Test {\n" + "}"; - init(700, text); + init(100, text); String placeholder = "/**...*/"; addCollapsedFoldRegion(0, text.indexOf("class") - 1, placeholder); @@ -417,7 +415,7 @@ public class SoftWrapApplianceOnDocumentModificationTest extends AbstractEditorP " */\n" + "class Test {\n" + "}"; - init(700, text); + init(100, text); String placeholder = "/**...*/"; addCollapsedFoldRegion(2, text.indexOf("class") - 1, placeholder); @@ -436,7 +434,7 @@ public class SoftWrapApplianceOnDocumentModificationTest extends AbstractEditorP "class Test {\n" + "}"; - init(700, text); + init(100, text); int startOffset = text.indexOf("@"); int endOffset = text.indexOf("class") - 1; addCollapsedFoldRegion(startOffset, endOffset, "xxx"); @@ -456,7 +454,7 @@ public class SoftWrapApplianceOnDocumentModificationTest extends AbstractEditorP "line4\n" + "line5"; - init(30, text); + init(30, text, 7); int start = text.indexOf("line3") - 1; addCollapsedFoldRegion(start, text.length(), "..."); assertEquals(1, getSoftWrapModel().getRegisteredSoftWraps().size()); @@ -469,7 +467,7 @@ public class SoftWrapApplianceOnDocumentModificationTest extends AbstractEditorP " public String s = \"this is a long string literal that is expected to be soft-wrapped into multiple visual lines\";\n" + "}"; - init(250, text); + init(30, text); myEditor.getCaretModel().moveToOffset(text.indexOf("}") - 1); List softWraps = new ArrayList(getSoftWrapModel().getRegisteredSoftWraps()); @@ -510,7 +508,7 @@ public class SoftWrapApplianceOnDocumentModificationTest extends AbstractEditorP " public String s = \"this is a long string literal that is expected to be soft-wrapped into multiple visual lines\"; \n" + "}"; - init(250, text); + init(30, text); myEditor.getCaretModel().moveToOffset(text.indexOf("\n") + 1); List softWraps = new ArrayList(getSoftWrapModel().getRegisteredSoftWraps()); @@ -574,7 +572,7 @@ public class SoftWrapApplianceOnDocumentModificationTest extends AbstractEditorP "this is line 4\n" + "this is line 5"; - init(50, text); + init(50, text, 7); VisualPosition changePosition = new VisualPosition(1, 0); myEditor.getCaretModel().moveToVisualPosition(changePosition); @@ -635,7 +633,7 @@ public class SoftWrapApplianceOnDocumentModificationTest extends AbstractEditorP "3. just a line that is long enough to be soft wrapped\n" + "4. just a line that is long enough to be soft wrapped"; - init(100, text); + init(15, text); Document document = myEditor.getDocument(); int start = document.getLineStartOffset(1); int end = document.getLineEndOffset(1) + 1; @@ -663,7 +661,7 @@ public class SoftWrapApplianceOnDocumentModificationTest extends AbstractEditorP "2. just a line that is long enough to be soft wrapped\n" + "3. just a line that is long enough to be soft wrapped\n" + "4. just a line that is long enough to be soft wrapped"; - init(100, text); + init(15, text); CaretModel caretModel = myEditor.getCaretModel(); caretModel.moveToOffset(text.indexOf("2.") + 2); @@ -685,7 +683,7 @@ public class SoftWrapApplianceOnDocumentModificationTest extends AbstractEditorP " this is a string that starts with white space and is long enough to be soft-wrapped\n" + " this is a 'prefix' text before collapsed multi-line folding that is long enough to be soft-wrapped first fold line\n" + "second fold line"; - init(200, text); + init(30, text); addCollapsedFoldRegion(text.indexOf("first fold line"), text.length(), "..."); @@ -715,7 +713,7 @@ public class SoftWrapApplianceOnDocumentModificationTest extends AbstractEditorP "\n" + "class Test {\n" + "}"; - init(300, text); + init(40, text); final int foldStartOffset = "import".length() + 1; int foldEndOffset = text.indexOf("class") - 2; @@ -749,7 +747,7 @@ public class SoftWrapApplianceOnDocumentModificationTest extends AbstractEditorP "\n" + "class Test {\n" + "}"; - init(300, text); + init(40, text); final int foldStartOffset = text.indexOf("java.util.Collections"); final int foldEndOffset = text.indexOf("class") - 2; @@ -768,7 +766,7 @@ public class SoftWrapApplianceOnDocumentModificationTest extends AbstractEditorP "222\n" + "33333333 33333333333333333333333333333333\n" + "444"; - init(100, text); + init(15, text); assertEquals(new LogicalPosition(3, 0), myEditor.visualToLogicalPosition(new VisualPosition(4, 0))); } @@ -781,7 +779,7 @@ public class SoftWrapApplianceOnDocumentModificationTest extends AbstractEditorP "444 55\n" + "666 77"; - init(100, text); + init(15, text); int foldStart = text.indexOf("5"); int foldEnd = text.indexOf("7"); @@ -798,7 +796,7 @@ public class SoftWrapApplianceOnDocumentModificationTest extends AbstractEditorP public void testNoWrapAtFirstNonWsSymbolWithCustomIndent() throws IOException { String text = " 1111111111111111111111111111111"; - init(70, text); + init(10, text); getEditor().getSettings().setCustomSoftWrapIndent(0); getEditor().getSettings().setUseCustomSoftWrapIndent(true); int textLength = getEditor().getDocument().getTextLength(); @@ -814,7 +812,7 @@ public class SoftWrapApplianceOnDocumentModificationTest extends AbstractEditorP // Inspired by IDEA-76353. The point is that we need to consider cached information about tab symbols width during logical // position to offset mapping String text = "\t test"; - init(100, text); + init(15, text); ((EditorImpl)myEditor).setPrefixTextAndAttributes(" ", new TextAttributes()); myEditor.getCaretModel().moveToOffset(text.length()); } @@ -827,7 +825,7 @@ public class SoftWrapApplianceOnDocumentModificationTest extends AbstractEditorP "\t third line"; // Make soft wraps to build a document info cache. - init(300, text); + init(40, text); // Modify document while soft wraps processing is off. final EditorSettings settings = getEditor().getSettings(); @@ -856,7 +854,7 @@ public class SoftWrapApplianceOnDocumentModificationTest extends AbstractEditorP "\t my text"; // Build soft wraps cache. - init(300, text); + init(40, text); VisualPosition caretPositionBefore = getEditor().getCaretModel().getVisualPosition(); @@ -871,12 +869,86 @@ public class SoftWrapApplianceOnDocumentModificationTest extends AbstractEditorP getEditor().getCaretModel().getVisualPosition() ); } - - private void init(final int visibleWidth, @NotNull String fileText) throws IOException { - init(visibleWidth, fileText, TestFileType.TEXT); + + public void testNoPreliminarySoftWrapAtLineEnd() throws IOException { + // We used to make soft wrap when the string was couple of visual columns before the right screen edge even if it could + // be completely shown. + init(37, "a b c", 7); + assertEmpty(getSoftWrapModel().getRegisteredSoftWraps()); + } + + public void testNoPreliminarySoftWrapBeforeFoldingAtLineEnd() throws IOException { + final String text = "a b c test"; + init(50, text, 7); + addCollapsedFoldRegion(text.indexOf("t"), text.length(), "."); + assertEmpty(getSoftWrapModel().getRegisteredSoftWraps()); + } + + public void testMultiLineFoldRegionBeforeWrapPosition() throws IOException { + final String text = + "package org.denis;\n" + + "\n" + + "\n" + + "public class BrokenAlignment {\n" + + "\n" + + " void method1(int a) {\n" + + " }\n" + + "\n" + + " Object method2(Object ... data) {\n" + + " return new Runnable() {\n" + + " public void run() {\n" + + " System.out.println();\n" + + " }\n" + + " };\n" + + " }\n" + + "\n" + + "}"; + init(511, text, TestFileType.JAVA, 10); + + addCollapsedFoldRegion(text.indexOf("new Runnable"), text.indexOf("System"), "Runnable() { "); + + int start = text.indexOf("System"); + start = text.indexOf("\n", start); + int end = text.indexOf(';', start); + addCollapsedFoldRegion(start, end, " }"); + + final List wraps = getSoftWrapModel().getRegisteredSoftWraps(); + assertEquals(1, wraps.size()); + + assertEquals(start, wraps.get(0).getStart()); + assertEquals(myEditor.offsetToVisualPosition(start), myEditor.offsetToVisualPosition(start + 1)); + assertEquals(myEditor.offsetToVisualPosition(start).line, myEditor.offsetToVisualPosition(end).line); } - private void init(final int visibleWidth, @NotNull String fileText, @NotNull TestFileType fileType) throws IOException { + public void testEnsureBeforeSoftWrapSignIsVisible() throws IOException { + final String text = "a.b.c.d"; + init(43, text, 7); + + checkSoftWraps(text.indexOf('c') + 1); + } + + public void testWrapAfterCollapsedFoldRegion() throws IOException { + final String text = + "this is a long text to fold more"; + init(12, text); + + int start = text.indexOf("long"); + int end = text.indexOf("more"); + addCollapsedFoldRegion(start, end, "placeholder which is long enough to be wrapped"); + + checkSoftWraps(start, end); + } + + private void init(final int visibleWidthInColumns, @NotNull String fileText) throws IOException { + int symbolWidthInPixels = 7; + init(visibleWidthInColumns * symbolWidthInPixels, fileText, symbolWidthInPixels); + } + + private void init(final int visibleWidth, @NotNull String fileText, int symbolWidth) throws IOException { + init(visibleWidth, fileText, TestFileType.TEXT, symbolWidth); + } + + private void init(final int visibleWidth, @NotNull String fileText, @NotNull TestFileType fileType, final int symbolWidth) throws IOException { init(fileText, fileType); myEditor.getSettings().setUseSoftWraps(true); SoftWrapModelImpl model = (SoftWrapModelImpl)myEditor.getSoftWrapModel(); @@ -889,9 +961,27 @@ public class SoftWrapApplianceOnDocumentModificationTest extends AbstractEditorP return visibleWidth; } }); + + if (symbolWidth > 0) { + applianceManager.setRepresentationHelper(new DefaultEditorTextRepresentationHelper(myEditor) { + @Override + public int charWidth(char c, int fontType) { + return symbolWidth; + } + }); + } + applianceManager.registerSoftWrapIfNecessary(); } + private static void checkSoftWraps(int... startOffsets) { + final List softWraps = getSoftWrapModel().getRegisteredSoftWraps(); + assertEquals("soft wraps number", startOffsets.length, softWraps.size()); + for (int i = 0; i < startOffsets.length; i++) { + assertEquals(startOffsets[i], softWraps.get(i).getStart()); + } + } + private static SoftWrapModelImpl getSoftWrapModel() { return (SoftWrapModelImpl)myEditor.getSoftWrapModel(); }