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
This commit is contained in:
Denis Zhdanov
2010-08-18 12:53:32 +04:00
parent 60f764e8b6
commit 38a23b8dc1
8 changed files with 96 additions and 31 deletions
@@ -116,4 +116,19 @@ public interface SoftWrapModelEx extends SoftWrapModel {
* @return <code>true</code> if given listener was not registered before; <code>false</code> 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.
* <p/>
* 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);
}
@@ -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);
}
}
@@ -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--;
@@ -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
* <code>(logical line number; temporary)</code> pairs.
*/
private final TIntObjectHashMap<Boolean> myProcessedLogicalLines = new TIntObjectHashMap<Boolean>();
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);
}
}
@@ -44,6 +44,8 @@ public interface SoftWrapApplianceManager {
* @param end end offset of the token to process within the given char array (exclusive)
* @param x <code>'x'</code> 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. <code>'Temporary'</code> 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);
}
@@ -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; <code>null</code> 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; <code>null</code> 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;
}
@@ -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);
@@ -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;