From 38a23b8dc13a13729e3804948d6065744cd832b3 Mon Sep 17 00:00:00 2001 From: Denis Zhdanov Date: Wed, 18 Aug 2010 12:53:32 +0400 Subject: [PATCH] IDEA-56366 Soft wrap: "Go To Line" could go to logical line, not visual one, if code is wrapped Scrolling model requests for approximate soft wraps defining during vertical viewport offset calculation now --- .../openapi/editor/ex/SoftWrapModelEx.java | 15 ++++++ .../editor/impl/ScrollingModelImpl.java | 17 +++++-- .../editor/impl/SoftWrapModelImpl.java | 24 +++++++++- .../DefaultSoftWrapApplianceManager.java | 47 ++++++++++++------- .../softwrap/SoftWrapApplianceManager.java | 4 +- .../impl/softwrap/SoftWrapsStorage.java | 14 ++++-- .../DefaultSoftWrapApplianceManagerTest.java | 4 +- .../impl/softwrap/SoftWrapDataMapperTest.java | 2 +- 8 files changed, 96 insertions(+), 31 deletions(-) diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/ex/SoftWrapModelEx.java b/platform/platform-impl/src/com/intellij/openapi/editor/ex/SoftWrapModelEx.java index 3f309e1987d9..ac7a6aef1359 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/ex/SoftWrapModelEx.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/ex/SoftWrapModelEx.java @@ -116,4 +116,19 @@ public interface SoftWrapModelEx extends SoftWrapModel { * @return true if given listener was not registered before; false otherwise */ boolean addSoftWrapChangeListener(@NotNull SoftWrapChangeListener listener); + + /** + * Asks current model to define approximate soft wraps for the lines range defined by the given lines if necessary. + *

+ * The main idea is to calculate exact soft wraps positions during editor repainting because we have complete + * information about font types used for text representation there. However, there is a possible case that we need to + * perform intermediate soft wraps calculations. E.g. we may open big document and than may want to scroll to the middle + * of it, hence, need to define vertical offset to apply to viewport position. However, vertical offset value depends on + * soft wraps between current visible area and target logical line and that soft wraps are not applied yet. We may call this + * method in order to define approximate soft wraps number and positions then in order to make scrolling more precise. + * + * @param line1 one of the target lines boundaries (not imposed to be greater or less than the other boundary) + * @param line2 another boundary line (not imposed to be greater or less than the other boundary) + */ + void defineApproximateSoftWraps(int line1, int line2); } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/ScrollingModelImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/ScrollingModelImpl.java index 2995e30be3a6..8bb8addc586e 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/ScrollingModelImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/ScrollingModelImpl.java @@ -142,6 +142,13 @@ public class ScrollingModelImpl implements ScrollingModel { } private Point calcOffsetsToScroll(LogicalPosition pos, ScrollType scrollType, Rectangle viewRect) { + // There is a possible case that the user opens huge document with many number of soft-wrapped line. + // Suppose that he or she wants to move viewport to such a logical position that many document lines between current + // viewport position and the target one are not displayed before. That means that we can't be sure about vertical offset + // to be applied to the viewport. Hence, we ask soft wrap model to roughly define soft wraps on a trail. + LogicalPosition firstVisibleLineStart = myEditor.xyToLogicalPosition(viewRect.getLocation()); + myEditor.getSoftWrapModel().defineApproximateSoftWraps(firstVisibleLineStart.line, pos.line); + Point targetLocation = myEditor.logicalPositionToXY(pos); if (myEditor.getSettings().isRefrainFromScrolling() && viewRect.contains(targetLocation)) { @@ -424,9 +431,10 @@ public class ScrollingModelImpl implements ScrollingModel { return new Rectangle(myEndHOffset, myEndVOffset, viewRect.width, viewRect.height); } - public Runnable getStartCommand() { - return myStartCommand; - } + // Commented as the method is not used + //public Runnable getStartCommand() { + // return myStartCommand; + //} private void tick() { double time = (myTicksCount + 1) / (double)myStepCount; @@ -496,9 +504,8 @@ public class ScrollingModelImpl implements ScrollingModel { double lineDist = myTotalDist / lineHeight; double part = (lineDist - 1) / 10; if (part > 1) part = 1; - int duration = (int)(part * SCROLL_DURATION); //System.out.println("duration = " + duration); - return duration; + return (int)(part * SCROLL_DURATION); } } 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 11e1c6c9a711..d25baa66f843 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 @@ -172,6 +172,28 @@ public class SoftWrapModelImpl implements SoftWrapModelEx, DocumentListener { return result; } + @Override + public void defineApproximateSoftWraps(int line1, int line2) { + if (!isSoftWrappingEnabled()) { + return; + } + int startLine = line1; + int endLine = line2; + if (line1 > line2) { + startLine = line2; + endLine = line1; + } + + // Normalization. + Document document = myEditor.getDocument(); + startLine = Math.max(0, startLine); + endLine = Math.min(endLine, document.getLineCount() - 1); + + myApplianceManager.registerSoftWrapIfNecessary( + document.getCharsSequence(), document.getLineStartOffset(startLine), document.getLineEndOffset(endLine), 0, Font.PLAIN, true + ); + } + public void registerSoftWrapIfNecessary(@NotNull CharSequence text, int start, int end, int x, int fontType) { if (!isSoftWrappingEnabled()) { return; @@ -180,7 +202,7 @@ public class SoftWrapModelImpl implements SoftWrapModelEx, DocumentListener { myActive++; try { - myApplianceManager.registerSoftWrapIfNecessary(text, start, end, x, fontType); + myApplianceManager.registerSoftWrapIfNecessary(text, start, end, x, fontType, false); } finally { myActive--; diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/DefaultSoftWrapApplianceManager.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/DefaultSoftWrapApplianceManager.java index c54eff21b8e1..148c3e8f7de8 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/DefaultSoftWrapApplianceManager.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/DefaultSoftWrapApplianceManager.java @@ -16,8 +16,6 @@ package com.intellij.openapi.editor.impl.softwrap; import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.VisualPosition; -import com.intellij.openapi.editor.actions.EditorActionUtil; import com.intellij.openapi.editor.event.DocumentListener; import com.intellij.openapi.editor.ex.EditorEx; import com.intellij.openapi.editor.ex.util.EditorUtil; @@ -29,6 +27,7 @@ import com.intellij.psi.codeStyle.CodeStyleSettings; import com.intellij.psi.codeStyle.CodeStyleSettingsManager; import gnu.trove.TIntArrayList; import gnu.trove.TIntHashSet; +import gnu.trove.TIntObjectHashMap; import org.jetbrains.annotations.NotNull; import java.awt.*; @@ -103,7 +102,12 @@ public class DefaultSoftWrapApplianceManager implements SoftWrapApplianceManager SPECIAL_SYMBOLS_TO_WRAP_BEFORE.add('.'); } - private final TIntHashSet myProcessedLogicalLines = new TIntHashSet(); + /** + * Holds information about logical lines for which soft wrap is calculated as a set of + * (logical line number; temporary) pairs. + */ + private final TIntObjectHashMap myProcessedLogicalLines = new TIntObjectHashMap(); + private final DocumentListener myDocumentListener = new LineOrientedDocumentChangeAdapter() { @Override public void beforeDocumentChange(int startLine, int endLine, int symbolsDifference) { @@ -124,7 +128,7 @@ public class DefaultSoftWrapApplianceManager implements SoftWrapApplianceManager // Note: we don't update 'myProcessedLogicalLines' collection here, i.e. soft wraps will be recalculated precisely // during standard editor repainting iteration. if (i < document.getLineCount()) { - processLogicalLine(document.getCharsSequence(), i, Font.PLAIN, IndentType.NONE); + processLogicalLine(document.getCharsSequence(), i, Font.PLAIN, IndentType.NONE, true); } } } @@ -150,7 +154,7 @@ public class DefaultSoftWrapApplianceManager implements SoftWrapApplianceManager @SuppressWarnings({"AssignmentToForLoopParameter"}) @Override - public void registerSoftWrapIfNecessary(@NotNull CharSequence text, int start, int end, int x, int fontType) { + public void registerSoftWrapIfNecessary(@NotNull CharSequence text, int start, int end, int x, int fontType, boolean temporary) { dropDataIfNecessary(); if (myVisibleAreaWidth <= 0 || start >= end) { @@ -161,13 +165,13 @@ public class DefaultSoftWrapApplianceManager implements SoftWrapApplianceManager int startLine = document.getLineNumber(start); int endLine = document.getLineNumber(end); for (int i = startLine; i <= endLine; i++) { - if (!myProcessedLogicalLines.contains(i)) { + if (!myProcessedLogicalLines.contains(i) || (!temporary && myProcessedLogicalLines.get(i))) { IndentType indent = IndentType.NONE; if (!myEditor.isViewer() && !document.isWritable()) { indent = IndentType.TO_PREV_LINE_NON_WS_START; } - processLogicalLine(text, i, fontType, indent); - myProcessedLogicalLines.add(i); + processLogicalLine(text, i, fontType, indent, temporary); + myProcessedLogicalLines.put(i, temporary); } } } @@ -188,7 +192,7 @@ public class DefaultSoftWrapApplianceManager implements SoftWrapApplianceManager myVisibleAreaWidth = currentVisibleAreaWidth; } - private void processLogicalLine(CharSequence text, int line, int fontType, IndentType indentType) { + private void processLogicalLine(CharSequence text, int line, int fontType, IndentType indentType, boolean temporary) { Document document = myEditor.getDocument(); int startOffset = document.getLineStartOffset(line); int endOffset = document.getLineEndOffset(line); @@ -202,15 +206,26 @@ public class DefaultSoftWrapApplianceManager implements SoftWrapApplianceManager if (indentType == IndentType.NONE) { TIntArrayList offsets = calculateSoftWrapOffsets(text, startOffset, endOffset, fontType, 0); - registerSoftWraps(offsets, 0); + registerSoftWraps(offsets, 0, temporary); return; } // Understand if it's worth to define indent for soft wrap(s) to create and perform their actual construction and registration. + int prevLineIndentInColumns = 0; + + int firstNonSpaceSymbolIndex = startOffset; + for (; firstNonSpaceSymbolIndex < endOffset; firstNonSpaceSymbolIndex++) { + char c = text.charAt(firstNonSpaceSymbolIndex); + if (c != ' ' && c != '\t') { + break; + } + } + if (firstNonSpaceSymbolIndex > startOffset) { + prevLineIndentInColumns = myTextRepresentationHelper.toVisualColumnSymbolsNumber(text, startOffset, firstNonSpaceSymbolIndex, 0); + } + int spaceWidth = EditorUtil.getSpaceWidth(fontType, myEditor); int indentInColumns = getIndentSize(); - VisualPosition visual = myEditor.offsetToVisualPosition(startOffset); - int prevLineIndentInColumns = EditorActionUtil.findFirstNonSpaceColumnOnTheLine(myEditor, visual.line); int indentInColumnsToUse = 0; TIntArrayList softWrapOffsetsToUse = null; for (; indentInColumns >= 0; indentInColumns--) { @@ -229,10 +244,10 @@ public class DefaultSoftWrapApplianceManager implements SoftWrapApplianceManager } if (indentInColumnsToUse <= 0) { - processLogicalLine(text, line, fontType, IndentType.NONE); + processLogicalLine(text, line, fontType, IndentType.NONE, temporary); } else { - registerSoftWraps(softWrapOffsetsToUse, indentInColumnsToUse + prevLineIndentInColumns); + registerSoftWraps(softWrapOffsetsToUse, indentInColumnsToUse + prevLineIndentInColumns, temporary); } } @@ -275,10 +290,10 @@ public class DefaultSoftWrapApplianceManager implements SoftWrapApplianceManager return settings.getIndentSize(file.getFileType()); } - private void registerSoftWraps(TIntArrayList offsets, int indentInColumns) { + private void registerSoftWraps(TIntArrayList offsets, int indentInColumns, boolean temporary) { for (int i = 0; i < offsets.size(); i++) { int offset = offsets.getQuick(i); - myStorage.storeOrReplace(new TextChangeImpl("\n" + StringUtil.repeatSymbol(' ', indentInColumns), offset)); + myStorage.storeOrReplace(new TextChangeImpl("\n" + StringUtil.repeatSymbol(' ', indentInColumns), offset), !temporary); } } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/SoftWrapApplianceManager.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/SoftWrapApplianceManager.java index ccbef464f82c..c334cbb5b6d5 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/SoftWrapApplianceManager.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/SoftWrapApplianceManager.java @@ -44,6 +44,8 @@ public interface SoftWrapApplianceManager { * @param end end offset of the token to process within the given char array (exclusive) * @param x 'x' coordinate within the given graphics buffer that will be used to start drawing the text * @param fontType font type used for the target text fragment representation + * @param temporary defines type of the current call. 'Temporary' means that soft wraps registered during + * the processing should be recalculated on further invocations; they may be reused otherwise */ - void registerSoftWrapIfNecessary(@NotNull CharSequence text, int start, int end, int x, int fontType); + void registerSoftWrapIfNecessary(@NotNull CharSequence text, int start, int end, int x, int fontType, boolean temporary); } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/SoftWrapsStorage.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/SoftWrapsStorage.java index fe8f785fc8bb..ad24ee00ab42 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/SoftWrapsStorage.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/SoftWrapsStorage.java @@ -92,19 +92,23 @@ public class SoftWrapsStorage { /** * Inserts given soft wrap to {@link #myWraps} collection at the given index. * - * @param softWrap soft wrap to store - * @return previous soft wrap object stored for the same offset if any; null otherwise + * @param softWrap soft wrap to store + * @param notifyListeners flag that indicates if registered listeners should be notified about soft wrap registration + * @return previous soft wrap object stored for the same offset if any; null otherwise */ @Nullable - public TextChangeImpl storeOrReplace(TextChangeImpl softWrap) { int i = getSoftWrapIndex(softWrap.getStart()); + public TextChangeImpl storeOrReplace(TextChangeImpl softWrap, boolean notifyListeners) { + int i = getSoftWrapIndex(softWrap.getStart()); if (i >= 0) { return myWraps.set(i, softWrap); } i = -i - 1; myWraps.add(i, softWrap); - for (SoftWrapChangeListener listener : myListeners) { - listener.softWrapAdded(softWrap); + if (notifyListeners) { + for (SoftWrapChangeListener listener : myListeners) { + listener.softWrapAdded(softWrap); + } } return null; } diff --git a/platform/platform-impl/testSrc/com/intellij/openapi/editor/impl/softwrap/DefaultSoftWrapApplianceManagerTest.java b/platform/platform-impl/testSrc/com/intellij/openapi/editor/impl/softwrap/DefaultSoftWrapApplianceManagerTest.java index 5a74d03f650b..e2cff945fd24 100644 --- a/platform/platform-impl/testSrc/com/intellij/openapi/editor/impl/softwrap/DefaultSoftWrapApplianceManagerTest.java +++ b/platform/platform-impl/testSrc/com/intellij/openapi/editor/impl/softwrap/DefaultSoftWrapApplianceManagerTest.java @@ -111,7 +111,7 @@ public class DefaultSoftWrapApplianceManagerTest { allowing(myScrollingModel).getVisibleArea(); will(returnValue(new Rectangle(0, 0, context.visualWidth, Integer.MAX_VALUE))); allowing(myDocument).getLineEndOffset(0); will(returnValue(context.document.length())); }}); - myManager.registerSoftWrapIfNecessary(context.document, 0, context.document.length(), 0, Font.PLAIN); + myManager.registerSoftWrapIfNecessary(context.document, 0, context.document.length(), 0, Font.PLAIN, true); } private static TextChangeImpl createSoftWrap(int offset, int indent) { @@ -177,7 +177,7 @@ public class DefaultSoftWrapApplianceManagerTest { private void processWrap() { buffer.append(rawDocument.substring(index, wrapIndex)); myMockery.checking(new Expectations() {{ - one(myStorage).storeOrReplace(createSoftWrap(buffer.length(), 0)); + one(myStorage).storeOrReplace(createSoftWrap(buffer.length(), 0), false); }}); index = wrapIndex + WRAP_MARKER.length(); wrapIndex = rawDocument.indexOf(WRAP_MARKER, index); diff --git a/platform/platform-impl/testSrc/com/intellij/openapi/editor/impl/softwrap/SoftWrapDataMapperTest.java b/platform/platform-impl/testSrc/com/intellij/openapi/editor/impl/softwrap/SoftWrapDataMapperTest.java index 16dc3764f6ef..be4b1dd30734 100644 --- a/platform/platform-impl/testSrc/com/intellij/openapi/editor/impl/softwrap/SoftWrapDataMapperTest.java +++ b/platform/platform-impl/testSrc/com/intellij/openapi/editor/impl/softwrap/SoftWrapDataMapperTest.java @@ -654,7 +654,7 @@ public class SoftWrapDataMapperTest { } public void onSoftWrapEnd() { - myStorage.storeOrReplace(new TextChangeImpl(mySoftWrapBuffer.toString(), softWrapStartOffset)); + myStorage.storeOrReplace(new TextChangeImpl(mySoftWrapBuffer.toString(), softWrapStartOffset), false); mySoftWrapBuffer.setLength(0); insideSoftWrap = false; x += SOFT_WRAP_DRAWING_WIDTH;