IDEA-58130 Improve editor performance during working with large files written in a single line

Make logicalPositionToOffset calculations faster for long lines (when soft wraps are enabled).
Position calculations are more consistent now so this is also supposed to fix EA-53787.
This commit is contained in:
Dmitry Batrak
2014-10-29 12:20:20 +04:00
parent 922316eb62
commit 1429ce728f
21 changed files with 375 additions and 64 deletions
@@ -33,6 +33,7 @@ import com.intellij.psi.PsiClass
import com.intellij.psi.PsiLiteralExpression
import com.intellij.psi.PsiMethod
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.testFramework.EditorTestUtil
import com.intellij.testFramework.LightProjectDescriptor
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase
@@ -1087,6 +1088,35 @@ class Foo {
assertEquals 0, expandedFoldRegionsCount
}
public void "test processing of tabs inside fold regions"() {
String text = """public class Foo {
\tpublic static void main(String[] args) {
\t\tjavax.swing.SwingUtilities.invokeLater(new Runnable() {
\t\t\t@Override
\t\t\tpublic void run() {
\t\t\t\tSystem.out.println();
\t\t\t}
\t\t});
\t}
}""";
configure text
assert myFixture.editor.getFoldingModel().getCollapsedRegionAtOffset(text.indexOf("new"))
myFixture.editor.settings.useTabCharacter = true
EditorTestUtil.configureSoftWraps(myFixture.editor, 1000)
myFixture.editor.caretModel.moveToOffset(text.indexOf("System"))
myFixture.performEditorAction(IdeActions.ACTION_EDITOR_TAB)
myFixture.checkResult("""public class Foo {
\tpublic static void main(String[] args) {
\t\tjavax.swing.SwingUtilities.invokeLater(new Runnable() {
\t\t\t@Override
\t\t\tpublic void run() {
\t\t\t\t\t<caret>System.out.println();
\t\t\t}
\t\t});
\t}
}""");
}
private int getFoldRegionsCount() {
return myFixture.editor.foldingModel.allFoldRegions.length
}
@@ -570,9 +570,15 @@ public class EditorWindowImpl extends UserDataHolderBase implements EditorWindow
@Override
public int logicalPositionToOffset(@NotNull final LogicalPosition pos) {
return logicalPositionToOffset(pos, true);
}
@Override
public int logicalPositionToOffset(@NotNull LogicalPosition pos, boolean softWrapAware) {
int lineStartOffset = myDocumentWindow.getLineStartOffset(pos.line);
return calcOffset(pos.column, pos.line, lineStartOffset);
}
private int calcLogicalColumnNumber(int offsetInLine, int lineNumber, int lineStartOffset) {
if (myDocumentWindow.getTextLength() == 0) return 0;
@@ -583,13 +589,25 @@ public class EditorWindowImpl extends UserDataHolderBase implements EditorWindow
CharSequence text = myDocumentWindow.getCharsSequence();
return EditorUtil.calcColumnNumber(this, text, lineStartOffset, lineStartOffset +offsetInLine);
}
private int calcOffset(int col, int lineNumber, int lineStartOffset) {
if (myDocumentWindow.getTextLength() == 0) return 0;
int end = myDocumentWindow.getLineEndOffset(lineNumber);
CharSequence text = myDocumentWindow.getCharsSequence();
return EditorUtil.calcOffset(this, text, lineStartOffset, end, col, EditorUtil.getTabSize(myDelegate), null);
int x = getDocument().getLineNumber(lineStartOffset) == 0 ? getPrefixTextWidthInPixels() : 0;
// There is a possible case that target column points inside soft wrap-introduced virtual space.
if (col <= 0) {
return lineStartOffset;
}
int result = EditorUtil.calcSoftWrapUnawareOffset(this, myDocumentWindow.getCharsSequence(), lineStartOffset, end, col,
EditorUtil.getTabSize(myDelegate), x, new int[]{0}, null);
if (result >= 0) {
return result;
}
return end;
}
@Override
@@ -178,6 +178,8 @@ public interface EditorEx extends Editor {
@NotNull
VisualPosition logicalToVisualPosition(@NotNull LogicalPosition logicalPos, boolean softWrapAware);
int logicalPositionToOffset(@NotNull LogicalPosition logicalPos, boolean softWrapAware);
/**
* Creates color scheme delegate which is bound to current editor. E.g. all schema changes will update editor state.
* @param customGlobalScheme
@@ -50,6 +50,14 @@ public interface SoftWrapModelEx extends SoftWrapModel {
@NotNull
LogicalPosition offsetToLogicalPosition(int offset);
/**
* Asks current model to map given logical position to document offset
*
* @param logicalPosition target editor logical position
* @return document offset for the given editor logical position
*/
int logicalPositionToOffset(@NotNull LogicalPosition logicalPosition);
/**
* Asks current model to adjust visual position that corresponds to the given logical position if necessary.
* <p/>
@@ -222,7 +222,12 @@ public final class EditorUtil {
* @param tabSize number of desired visual columns to use for tabulation representation
* @param debugBuffer buffer to hold debug info during the processing (if any)
* @return given text offset that identifies the same position that is pointed by the given visual column
*
* @deprecated This function can give incorrect results when soft wraps are enabled in editor. It is also slow in case of
* long document lines - {@link com.intellij.openapi.editor.Editor#logicalPositionToOffset(com.intellij.openapi.editor.LogicalPosition)}
* should be faster when soft wraps are enabled. To be removed in IDEA 16.
*/
@SuppressWarnings("UnusedDeclaration")
public static int calcOffset(@NotNull EditorEx editor,
@NotNull CharSequence text,
int start,
@@ -291,7 +296,7 @@ public final class EditorUtil {
* @return target offset that belongs to the <code>[start; end)</code> range and points to the target logical
* column if any; <code>-1</code> otherwise
*/
private static int calcSoftWrapUnawareOffset(@NotNull Editor editor,
public static int calcSoftWrapUnawareOffset(@NotNull Editor editor,
@NotNull CharSequence text,
int start,
int end,
@@ -200,15 +200,14 @@ public class CaretImpl extends UserDataHolderBase implements Caret {
final DocumentEx document = myEditor.getDocument();
int textEnd = Math.min(document.getTextLength() - 1, Math.max(offset, myOffset) + 1);
CharSequence text = document.getCharsSequence().subSequence(textStart, textEnd);
StringBuilder positionToOffsetTrace = new StringBuilder();
int inverseOffset = myEditor.logicalPositionToOffset(logicalPosition, positionToOffsetTrace);
int inverseOffset = myEditor.logicalPositionToOffset(logicalPosition);
LogMessageEx.error(
LOG, "caret moved to wrong offset. Please submit a dedicated ticket and attach current editor's text to it.",
String.format(
"Requested: offset=%d, logical position='%s' but actual: offset=%d, logical position='%s' (%s). %s%n"
+ "interested text [%d;%d): '%s'%n debug trace: %s%nLogical position -> offset ('%s'->'%d') trace: %s",
+ "interested text [%d;%d): '%s'%n debug trace: %s%nLogical position -> offset ('%s'->'%d')",
offset, logicalPosition, myOffset, myLogicalCaret, positionByOffsetAfterMove, myEditor.dumpState(),
textStart, textEnd, text, debugBuffer, logicalPosition, inverseOffset, positionToOffsetTrace
textStart, textEnd, text, debugBuffer, logicalPosition, inverseOffset
)
);
}
@@ -19,7 +19,6 @@ import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.ex.util.EditorUtil;
import gnu.trove.TObjectIntHashMap;
import org.intellij.lang.annotations.JdkConstants;
import org.jetbrains.annotations.NotNull;
/**
* Not thread-safe. Performs caching of char widths, so cache reset must be invoked (via {@link #clearSymbolWidthCache()} method) when
@@ -52,8 +51,8 @@ public class DefaultEditorTextRepresentationHelper implements EditorTextRepresen
}
@Override
public int toVisualColumnSymbolsNumber(@NotNull CharSequence text, int start, int end, int x) {
return EditorUtil.textWidthInColumns(myEditor, text, start, end, x);
public int toVisualColumnSymbolsNumber(int start, int end, int x) {
return EditorUtil.textWidthInColumns(myEditor, myEditor.getDocument().getImmutableCharSequence(), start, end, x);
}
@Override
@@ -67,7 +66,14 @@ public class DefaultEditorTextRepresentationHelper implements EditorTextRepresen
}
@Override
public int textWidth(@NotNull CharSequence text, int start, int end, int fontType, int x) {
public int calcSoftWrapUnawareOffset(int startOffset, int endOffset, int startColumn, int column, int startX) {
return EditorUtil.calcSoftWrapUnawareOffset(myEditor, myEditor.getDocument().getImmutableCharSequence(), startOffset, endOffset,
column, EditorUtil.getTabSize(myEditor), startX, new int[]{startColumn}, null);
}
@Override
public int textWidth(int start, int end, int fontType, int x) {
CharSequence text = myEditor.getDocument().getImmutableCharSequence();
int startToUse = start;
for (int i = end - 1; i >= start; i--) {
if (text.charAt(i) == '\n') {
@@ -3656,11 +3656,14 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
@Override
public int logicalPositionToOffset(@NotNull LogicalPosition pos) {
return logicalPositionToOffset(pos, null);
return logicalPositionToOffset(pos, true);
}
public int logicalPositionToOffset(@NotNull LogicalPosition pos, @Nullable StringBuilder debugBuffer) {
@Override
public int logicalPositionToOffset(@NotNull LogicalPosition pos, boolean softWrapAware) {
if (softWrapAware) {
return mySoftWrapModel.logicalPositionToOffset(pos);
}
assertReadAccess();
if (myDocument.getLineCount() == 0) return 0;
@@ -3675,9 +3678,15 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
if (pos.column == 0) return start;
int end = myDocument.getLineEndOffset(pos.line);
CharSequence text = myDocument.getImmutableCharSequence();
int x = getDocument().getLineNumber(start) == 0 ? getPrefixTextWidthInPixels() : 0;
return EditorUtil.calcOffset(this, text, start, end, pos.column, EditorUtil.getTabSize(this), debugBuffer);
int result = EditorUtil.calcSoftWrapUnawareOffset(this, myDocument.getImmutableCharSequence(), start, end, pos.column,
EditorUtil.getTabSize(this), x, new int[]{0}, null);
if (result >= 0) {
return result;
}
return end;
}
@Override
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2010 JetBrains s.r.o.
* Copyright 2000-2014 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.
@@ -16,7 +16,6 @@
package com.intellij.openapi.editor.impl;
import org.intellij.lang.annotations.JdkConstants;
import org.jetbrains.annotations.NotNull;
/**
* Strategy interface for various utility methods used for representing document text at the editor.
@@ -37,13 +36,12 @@ public interface EditorTextRepresentationHelper {
* Allows to answer how many visual columns is necessary for representing target fragment of the given text assuming
* that it belongs to the single visual line and should be shown at given <code>'x'</code> offset from the visual line start.
*
* @param text target text holder
* @param start start offset of the target text sub-sequence (inclusive)
* @param end end offset of the target text sub-sequence (exclusive)
* @param x <code>'x'</code> offset from the visual line start
* @return number of visual columns necessary for the target text sub-sequence representation
*/
int toVisualColumnSymbolsNumber(@NotNull CharSequence text, int start, int end, int x);
int toVisualColumnSymbolsNumber(int start, int end, int x);
/**
* Allows to retrieve width (in pixels) necessary to represent given region (<code>[start; end)</code>) starting
@@ -52,17 +50,16 @@ public interface EditorTextRepresentationHelper {
* <b>Note:</b> target region is allows to contain line feeds, the width is calculated as a difference between <code>'x'</code>
* coordinates of the last and first symbols.
*
* @param text target text holder
* @param start start offset of the target text sub-sequence (inclusive)
* @param end end offset of the target text sub-sequence (exclusive)
* @param fontType font type used for the given substring representation
* @param x <code>'x'</code> offset from the visual line start
* @return width in pixels necessary for the target text sub-sequence representation
*/
int textWidth(@NotNull CharSequence text, int start, int end, @JdkConstants.FontStyle int fontType, int x);
int textWidth(int start, int end, @JdkConstants.FontStyle int fontType, int x);
/**
* This is specification of {@link #textWidth(CharSequence, int, int, int, int)} in case of the single character
* This is specification of {@link #textWidth(int, int, int, int)} in case of the single character
* (in comparison with the situation when we need to calculate width of particular text).
* <p/>
* <b>Note:</b> it's assumed that given symbols is a regular one (non-tabulation, non-line feed etc).
@@ -72,4 +69,16 @@ public interface EditorTextRepresentationHelper {
* @return width in pixels necessary for the target char representation
*/
int charWidth(char c, @JdkConstants.FontStyle int fontType);
/**
* Translates column number to offset in the given document interval (which is assumed not to contain line breaks or soft wraps).
*
* @param startOffset interval start offset
* @param endOffset interval end offset
* @param startColumn interval start column
* @param column target column
* @param startX interval start visual coordinate
* @return offset, corresponding to the given column, in the range [startOffset; endOffset), or -1 if such offset doesn't exist
*/
int calcSoftWrapUnawareOffset(int startOffset, int endOffset, int startColumn, int column, int startX);
}
@@ -16,6 +16,7 @@
package com.intellij.openapi.editor.impl;
import com.intellij.diagnostic.Dumpable;
import com.intellij.injected.editor.EditorWindow;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
@@ -62,6 +63,7 @@ public class SoftWrapModelImpl implements SoftWrapModelEx, PrioritizedDocumentLi
private static final Logger LOG = Logger.getInstance("#" + SoftWrapModelImpl.class.getName());
private final LogicalPositionToOffsetTask myLogicalToOffsetTask = new LogicalPositionToOffsetTask();
private final OffsetToLogicalTask myOffsetToLogicalTask = new OffsetToLogicalTask();
private final VisualToLogicalTask myVisualToLogicalTask = new VisualToLogicalTask();
private final LogicalToVisualTask myLogicalToVisualTask = new LogicalToVisualTask();
@@ -148,8 +150,7 @@ public class SoftWrapModelImpl implements SoftWrapModelEx, PrioritizedDocumentLi
}
}
});
EditorSettings settings = myEditor.getSettings();
myUseSoftWraps = settings.isUseSoftWraps();
myUseSoftWraps = areSoftWrapsEnabledInEditor();
myFontPreferences = myEditor.getColorsScheme().getFontPreferences();
editor.addPropertyChangeListener(this);
@@ -157,13 +158,16 @@ public class SoftWrapModelImpl implements SoftWrapModelEx, PrioritizedDocumentLi
myApplianceManager.addListener(myDataMapper);
}
private boolean areSoftWrapsEnabledInEditor() {
return !(myEditor instanceof EditorWindow) && myEditor.getSettings().isUseSoftWraps();
}
/**
* Called on editor settings change. Current model is expected to drop all cached information about the settings if any.
*/
public void reinitSettings() {
boolean softWrapsUsedBefore = myUseSoftWraps;
EditorSettings settings = myEditor.getSettings();
myUseSoftWraps = settings.isUseSoftWraps();
myUseSoftWraps = areSoftWrapsEnabledInEditor();
int tabWidthBefore = myTabWidth;
myTabWidth = EditorUtil.getTabSize(myEditor);
@@ -372,6 +376,21 @@ public class SoftWrapModelImpl implements SoftWrapModelEx, PrioritizedDocumentLi
}
}
@Override
public int logicalPositionToOffset(@NotNull LogicalPosition logicalPosition) {
if (myBulkUpdateInProgress || myUpdateInProgress || !prepareToMapping()) {
return myEditor.logicalPositionToOffset(logicalPosition, false);
}
myActive++;
try {
myLogicalToOffsetTask.input = logicalPosition;
executeSafely(myLogicalToOffsetTask);
return myLogicalToOffsetTask.output;
} finally {
myActive--;
}
}
@NotNull
public LogicalPosition adjustLogicalPosition(LogicalPosition defaultLogical, int offset) {
if (myBulkUpdateInProgress || myUpdateInProgress || !prepareToMapping()) {
@@ -788,6 +807,22 @@ public class SoftWrapModelImpl implements SoftWrapModelEx, PrioritizedDocumentLi
}
}
private class LogicalPositionToOffsetTask implements SoftWrapAwareTask {
public LogicalPosition input;
public int output;
@Override
public void run(boolean softWrapAware) throws IllegalStateException {
output = softWrapAware ? myDataMapper.logicalPositionToOffset(input) : myEditor.logicalPositionToOffset(input, false);
}
@Override
public String toString() {
return "mapping from logical position (" + input + ") to offset";
}
}
private class FoldProcessingEndTask implements SoftWrapAwareTask {
@Override
public void run(boolean softWrapAware) {
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2010 JetBrains s.r.o.
* Copyright 2000-2014 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.
@@ -62,4 +62,13 @@ public interface SoftWrapDataMapper {
*/
VisualPosition logicalToVisualPosition(@NotNull LogicalPosition logical, @NotNull VisualPosition softWrapUnawareVisual)
throws IllegalStateException;
/**
* Maps given logical position to corresponding offset.
*
* @param logical logical position to map
* @return offset that corresponds to the given logical position
* @throws IllegalStateException if it's not possible to perform a mapping
*/
int logicalPositionToOffset(@NotNull LogicalPosition logical) throws IllegalStateException;
}
@@ -16,9 +16,9 @@
package com.intellij.openapi.editor.impl.softwrap.mapping;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.FoldRegion;
import com.intellij.openapi.editor.LogicalPosition;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.impl.softwrap.SoftWrapsStorage;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -37,7 +37,7 @@ import java.util.List;
*/
abstract class AbstractMappingStrategy<T> implements MappingStrategy<T> {
protected final Editor myEditor;
protected final EditorEx myEditor;
protected final SoftWrapsStorage myStorage;
protected final List<CacheEntry> myCache;
@@ -46,7 +46,7 @@ abstract class AbstractMappingStrategy<T> implements MappingStrategy<T> {
private T myEagerMatch;
private int myLastEntryOffset;
AbstractMappingStrategy(@NotNull Editor editor,
AbstractMappingStrategy(@NotNull EditorEx editor,
@NotNull SoftWrapsStorage storage,
@NotNull List<CacheEntry> cache)
{
@@ -154,7 +154,11 @@ abstract class AbstractMappingStrategy<T> implements MappingStrategy<T> {
if (result != null) {
return result;
}
advancePositionOnFolding(position, foldRegion);
return null;
}
protected void advancePositionOnFolding(@NotNull EditorPosition position, @NotNull FoldRegion foldRegion) {
Document document = myEditor.getDocument();
int endOffsetLogicalLine = document.getLineNumber(foldRegion.getEndOffset());
if (position.logicalLine != endOffsetLogicalLine) {
@@ -171,7 +175,6 @@ abstract class AbstractMappingStrategy<T> implements MappingStrategy<T> {
}
position.advance(foldRegion, collapsedSymbolsWidthInColumns);
return null;
}
@Nullable
@@ -67,6 +67,7 @@ public class CachingSoftWrapDataMapper implements SoftWrapDataMapper, SoftWrapAw
private final CacheState myBeforeChangeState = new CacheState();
private final CacheState myAfterChangeState = new CacheState();
private final LogicalToOffsetCalculationStrategy myLogicalToOffsetStrategy;
private final OffsetToLogicalCalculationStrategy myOffsetToLogicalStrategy;
private final VisualToLogicalCalculationStrategy myVisualToLogicalStrategy;
private final EditorEx myEditor;
@@ -79,6 +80,7 @@ public class CachingSoftWrapDataMapper implements SoftWrapDataMapper, SoftWrapAw
myStorage = storage;
mySearchKey = new CacheEntry(0, editor);
myLogicalToOffsetStrategy = new LogicalToOffsetCalculationStrategy(editor, storage, myCache);
myOffsetToLogicalStrategy = new OffsetToLogicalCalculationStrategy(editor, storage, myCache);
myVisualToLogicalStrategy = new VisualToLogicalCalculationStrategy(editor, storage, myCache);
}
@@ -101,6 +103,12 @@ public class CachingSoftWrapDataMapper implements SoftWrapDataMapper, SoftWrapAw
return calculate(myOffsetToLogicalStrategy);
}
@Override
public int logicalPositionToOffset(@NotNull LogicalPosition logical) throws IllegalStateException {
myLogicalToOffsetStrategy.init(logical);
return calculate(myLogicalToOffsetStrategy);
}
@Override
public VisualPosition logicalToVisualPosition(@NotNull LogicalPosition logical, @NotNull VisualPosition softWrapUnawareVisual)
throws IllegalStateException
@@ -144,7 +152,7 @@ public class CachingSoftWrapDataMapper implements SoftWrapDataMapper, SoftWrapAw
// Count soft wrap column offset only if it's located at the same line as the target offset.
if (column < 0 && softWrap.getStart() >= targetLogicalLineStartOffset) {
column = softWrap.getIndentInColumns() + SoftWrapModelImpl.getEditorTextRepresentationHelper(myEditor).toVisualColumnSymbolsNumber(
myEditor.getDocument().getCharsSequence(), softWrap.getStart(), maxOffset, softWrap.getIndentInPixels()
softWrap.getStart(), maxOffset, softWrap.getIndentInPixels()
);
// Count lines introduced by the current soft wrap. We assume that every soft wrap has a single line feed.
@@ -144,10 +144,7 @@ class EditorPosition implements Cloneable {
// Single-line fold region.
if (collapsedSymbolsWidthInColumns < 0) {
collapsedSymbolsWidthInColumns = SoftWrapModelImpl.getEditorTextRepresentationHelper(myEditor)
.toVisualColumnSymbolsNumber(document.getCharsSequence(),
foldRegion.getStartOffset(),
foldRegion.getEndOffset(),
x);
.toVisualColumnSymbolsNumber(foldRegion.getStartOffset(), foldRegion.getEndOffset(), x);
}
logicalColumn += collapsedSymbolsWidthInColumns;
foldingColumnDiff += placeholder.length() - collapsedSymbolsWidthInColumns;
@@ -156,10 +153,7 @@ class EditorPosition implements Cloneable {
// Multi-line fold region.
if (collapsedSymbolsWidthInColumns < 0) {
collapsedSymbolsWidthInColumns = SoftWrapModelImpl.getEditorTextRepresentationHelper(myEditor)
.toVisualColumnSymbolsNumber(document.getCharsSequence(),
foldRegion.getStartOffset(),
foldRegion.getEndOffset(),
0);
.toVisualColumnSymbolsNumber(foldRegion.getStartOffset(), foldRegion.getEndOffset(), 0);
}
int linesDiff = endOffsetLogicalLine - logicalLine;
logicalLine += linesDiff;
@@ -0,0 +1,120 @@
/*
* Copyright 2000-2014 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.openapi.editor.impl.softwrap.mapping;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.FoldRegion;
import com.intellij.openapi.editor.LogicalPosition;
import com.intellij.openapi.editor.SoftWrap;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.impl.SoftWrapModelImpl;
import com.intellij.openapi.editor.impl.softwrap.SoftWrapsStorage;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
class LogicalToOffsetCalculationStrategy extends AbstractMappingStrategy<Integer> {
private LogicalPosition targetPosition;
LogicalToOffsetCalculationStrategy(@NotNull EditorEx editor, @NotNull SoftWrapsStorage storage, @NotNull List<CacheEntry> cache) {
super(editor, storage, cache);
}
void init(LogicalPosition logical) {
CacheEntry entry = MappingUtil.getCacheEntryForLogicalPosition(logical, myCache);
if (entry == null) {
if (logical.line >= myEditor.getDocument().getLineCount()) {
setEagerMatch(myEditor.getDocument().getTextLength());
}
else {
setEagerMatch(Math.min(myEditor.getDocument().getLineStartOffset(logical.line) + logical.column,
myEditor.getDocument().getLineEndOffset(logical.line)));
}
return;
}
if (entry.endLogicalLine == logical.line && entry.endLogicalColumn <= logical.column) {
setEagerMatch(entry.endOffset);
return;
}
reset();
targetPosition = logical;
setTargetEntry(entry, true);
}
@Nullable
@Override
protected Integer buildIfExceeds(EditorPosition position, int offset) {
if (position.logicalLine != targetPosition.line) {
return null;
}
int result = position.offset + targetPosition.column - position.logicalColumn;
return result > offset ? null : result;
}
@Override
public Integer processFoldRegion(@NotNull EditorPosition position, @NotNull FoldRegion foldRegion) {
int startLine = position.logicalLine;
int startColumn = position.logicalColumn;
int startX = position.x;
advancePositionOnFolding(position, foldRegion);
if (position.logicalLine < targetPosition.line
|| position.logicalLine == targetPosition.line && position.logicalColumn <= targetPosition.column) {
return null;
}
Document document = myEditor.getDocument();
int lineEndOffset = document.getLineEndOffset(targetPosition.line);
int result = SoftWrapModelImpl.getEditorTextRepresentationHelper(myEditor)
.calcSoftWrapUnawareOffset(targetPosition.line == startLine ? foldRegion.getStartOffset()
: document.getLineStartOffset(targetPosition.line),
lineEndOffset,
targetPosition.line == startLine ? startColumn : 0,
targetPosition.column,
targetPosition.line == startLine ? startX : 0);
return result < 0 ? lineEndOffset : result;
}
@Nullable
@Override
protected Integer buildIfExceeds(@NotNull EditorPosition position, @NotNull FoldRegion foldRegion) {
throw new RuntimeException("Unexpected invocation");
}
@Nullable
@Override
protected Integer buildIfExceeds(EditorPosition position, TabData tabData) {
if (position.logicalLine != targetPosition.line) {
return null;
}
return position.logicalColumn + tabData.widthInColumns > targetPosition.column ? position.offset : null;
}
@Nullable
@Override
public Integer processSoftWrap(@NotNull EditorPosition position, SoftWrap softWrap) {
return null;
}
@NotNull
@Override
public Integer build(@NotNull EditorPosition position) {
return position.offset + targetPosition.column - position.logicalColumn;
}
}
@@ -16,6 +16,7 @@
package com.intellij.openapi.editor.impl.softwrap.mapping;
import com.intellij.openapi.editor.*;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.impl.SoftWrapModelImpl;
import com.intellij.openapi.editor.impl.softwrap.SoftWrapsStorage;
import org.jetbrains.annotations.NotNull;
@@ -31,7 +32,7 @@ class LogicalToVisualMappingStrategy extends AbstractMappingStrategy<VisualPosit
private LogicalPosition myTargetLogical;
LogicalToVisualMappingStrategy(@NotNull LogicalPosition logical, @NotNull Editor editor, @NotNull SoftWrapsStorage storage,
LogicalToVisualMappingStrategy(@NotNull LogicalPosition logical, @NotNull EditorEx editor, @NotNull SoftWrapsStorage storage,
@NotNull List<CacheEntry> cache)
throws IllegalStateException
{
@@ -122,7 +123,7 @@ class LogicalToVisualMappingStrategy extends AbstractMappingStrategy<VisualPosit
if (data == null) {
int xStart = myEditor.getDocument().getLineNumber(foldRegion.getStartOffset()) == foldEndLine ? context.x : 0;
foldEndColumn = SoftWrapModelImpl.getEditorTextRepresentationHelper(myEditor).toVisualColumnSymbolsNumber(
myEditor.getDocument().getCharsSequence(), foldRegion.getStartOffset(), foldRegion.getEndOffset(), xStart
foldRegion.getStartOffset(), foldRegion.getEndOffset(), xStart
);
} else {
foldEndColumn = data.getCollapsedSymbolsWidthInColumns();
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2010 JetBrains s.r.o.
* Copyright 2000-2014 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.
@@ -16,6 +16,9 @@
package com.intellij.openapi.editor.impl.softwrap.mapping;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.LogicalPosition;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
@@ -79,4 +82,32 @@ public class MappingUtil {
return -(start + 1);
}
@Nullable
public static CacheEntry getCacheEntryForLogicalPosition(@NotNull LogicalPosition position, @NotNull List<CacheEntry> cache) {
int start = 0;
int end = cache.size() - 1;
while (start <= end) {
int i = (end + start) >>> 1;
CacheEntry cacheEntry = cache.get(i);
if (cacheEntry.startLogicalLine < position.line
|| cacheEntry.startLogicalLine == position.line && cacheEntry.startLogicalColumn < position.column) {
start = i + 1;
continue;
}
if (cacheEntry.startLogicalLine > position.line
|| cacheEntry.startLogicalLine == position.line && cacheEntry.startLogicalColumn > position.column) {
end = i - 1;
continue;
}
return assertEnd(position, cache.get(i));
}
return end < 0 ? null : assertEnd(position, cache.get(end));
}
@Nullable
private static CacheEntry assertEnd(@NotNull LogicalPosition position, @NotNull CacheEntry entry) {
return position.line <= entry.endLogicalLine ? entry : null;
}
}
@@ -16,6 +16,7 @@
package com.intellij.openapi.editor.impl.softwrap.mapping;
import com.intellij.openapi.editor.*;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.impl.SoftWrapModelImpl;
import com.intellij.openapi.editor.impl.softwrap.SoftWrapsStorage;
import org.jetbrains.annotations.NotNull;
@@ -31,7 +32,7 @@ class OffsetToLogicalCalculationStrategy extends AbstractMappingStrategy<Logical
private int myTargetOffset;
OffsetToLogicalCalculationStrategy(@NotNull Editor editor, @NotNull SoftWrapsStorage storage, @NotNull List<CacheEntry> cache)
OffsetToLogicalCalculationStrategy(@NotNull EditorEx editor, @NotNull SoftWrapsStorage storage, @NotNull List<CacheEntry> cache)
{
super(editor, storage, cache);
}
@@ -157,13 +158,13 @@ class OffsetToLogicalCalculationStrategy extends AbstractMappingStrategy<Logical
if (targetLogicalLine == position.logicalLine) {
// Target offset is located on the same logical line as folding start.
position.logicalColumn += SoftWrapModelImpl.getEditorTextRepresentationHelper(myEditor).toVisualColumnSymbolsNumber(
document.getCharsSequence(), foldRegion.getStartOffset(), myTargetOffset, position.x
foldRegion.getStartOffset(), myTargetOffset, position.x
);
}
else {
// Target offset is located on a different line with folding start.
position.logicalColumn = SoftWrapModelImpl.getEditorTextRepresentationHelper(myEditor).toVisualColumnSymbolsNumber(
document.getCharsSequence(), foldRegion.getStartOffset(), myTargetOffset, 0
foldRegion.getStartOffset(), myTargetOffset, 0
);
position.softWrapColumnDiff = 0;
int linesDiff = document.getLineNumber(myTargetOffset) - document.getLineNumber(foldRegion.getStartOffset());
@@ -16,6 +16,7 @@
package com.intellij.openapi.editor.impl.softwrap.mapping;
import com.intellij.openapi.editor.*;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.impl.softwrap.SoftWrapsStorage;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -32,7 +33,7 @@ class VisualToLogicalCalculationStrategy extends AbstractMappingStrategy<Logical
private final CacheEntry mySearchKey;
private VisualPosition myTargetVisual;
VisualToLogicalCalculationStrategy(@NotNull Editor editor, @NotNull SoftWrapsStorage storage, @NotNull List<CacheEntry> cache)
VisualToLogicalCalculationStrategy(@NotNull EditorEx editor, @NotNull SoftWrapsStorage storage, @NotNull List<CacheEntry> cache)
{
super(editor, storage, cache);
mySearchKey = new CacheEntry(0, editor);
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2010 JetBrains s.r.o.
* Copyright 2000-2014 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.
@@ -16,7 +16,6 @@
package com.intellij.openapi.editor.impl.softwrap;
import com.intellij.openapi.editor.impl.EditorTextRepresentationHelper;
import org.jetbrains.annotations.NotNull;
/**
* @author Denis Zhdanov
@@ -24,23 +23,26 @@ import org.jetbrains.annotations.NotNull;
*/
public class MockEditorTextRepresentationHelper implements EditorTextRepresentationHelper {
private final CharSequence myText;
private final int mySpaceSizeInPixels;
private final int myTabSizeInColumns;
public MockEditorTextRepresentationHelper(int spaceSizeInPixels, int tabSizeInColumns) {
public MockEditorTextRepresentationHelper(CharSequence text, int spaceSizeInPixels, int tabSizeInColumns) {
myText = text;
mySpaceSizeInPixels = spaceSizeInPixels;
myTabSizeInColumns = tabSizeInColumns;
}
public int toVisualColumnSymbolsNumber(char c, int x) {
return toVisualColumnSymbolsNumber(new String(new char[] {c}), 0, 1, x);
return new MockEditorTextRepresentationHelper(new String(new char[] {c}), mySpaceSizeInPixels, myTabSizeInColumns)
.toVisualColumnSymbolsNumber(0, 1, x);
}
@Override
public int toVisualColumnSymbolsNumber(@NotNull CharSequence text, int start, int end, int x) {
public int toVisualColumnSymbolsNumber(int start, int end, int x) {
int result = 0;
for (int i = start; i < end; i++) {
char c = text.charAt(i);
char c = myText.charAt(i);
if (c == '\n') {
result = 0;
x = 0;
@@ -57,10 +59,10 @@ public class MockEditorTextRepresentationHelper implements EditorTextRepresentat
}
@Override
public int textWidth(@NotNull CharSequence text, int start, int end, int fontType, int x) {
public int textWidth(int start, int end, int fontType, int x) {
int result = 0;
for (int i = start; i < end; i++) {
char c = text.charAt(i);
char c = myText.charAt(i);
switch (c) {
case '\n': result = 0; break;
default: result += charWidth(c, result);
@@ -79,4 +81,23 @@ public class MockEditorTextRepresentationHelper implements EditorTextRepresentat
return mySpaceSizeInPixels;
}
}
@Override
public int calcSoftWrapUnawareOffset(int startOffset, int endOffset, int startColumn, int targetColumn, int startX) {
int x = startX;
int column = startColumn;
for (int i = startOffset; i < endOffset; i++) {
if (column == targetColumn) {
return i;
}
char c = myText.charAt(i);
int width = charWidth(c, x);
column += width / mySpaceSizeInPixels;
if (width % mySpaceSizeInPixels > 0) {
column++;
}
x += width;
}
return -1;
}
}
@@ -104,6 +104,7 @@ public class CachingSoftWrapDataMapperTest {
private Mockery myMockery;
private EditorEx myEditor;
private DocumentEx myDocument;
private StringBuilder myChars;
private SoftWrapsStorage myStorage;
private SoftWrapModelEx mySoftWrapModel;
private FoldingModelEx myFoldingModel;
@@ -115,6 +116,7 @@ public class CachingSoftWrapDataMapperTest {
myEditor = myMockery.mock(EditorEx.class);
myDocument = myMockery.mock(DocumentEx.class);
myChars = new StringBuilder();
myStorage = new SoftWrapsStorage();
mySoftWrapModel = myMockery.mock(SoftWrapModelEx.class);
myFoldingModel = myMockery.mock(FoldingModelEx.class);
@@ -122,7 +124,7 @@ public class CachingSoftWrapDataMapperTest {
final Project project = myMockery.mock(Project.class);
final SoftWrapPainter painter = myMockery.mock(SoftWrapPainter.class);
myRepresentationHelper = new MockEditorTextRepresentationHelper(SPACE_SIZE, TAB_SIZE);
myRepresentationHelper = new MockEditorTextRepresentationHelper(myChars, SPACE_SIZE, TAB_SIZE);
myMockery.checking(new Expectations() {{
// Document
@@ -584,19 +586,19 @@ public class CachingSoftWrapDataMapperTest {
allowing(myDocument).getCharsSequence(); will(new CustomAction("getCharsSequence()") {
@Override
public Object invoke(Invocation invocation) throws Throwable {
return context.document;
return myChars;
}
});
allowing(myDocument).getText(); will(new CustomAction("getCharsSequence()") {
@Override
public Object invoke(Invocation invocation) throws Throwable {
return context.document.toString();
return myChars.toString();
}
});
allowing(myDocument).getTextLength(); will(new CustomAction("getTextLength()") {
@Override
public Object invoke(Invocation invocation) throws Throwable {
return context.document.length();
return myChars.length();
}
});
}});
@@ -629,7 +631,7 @@ public class CachingSoftWrapDataMapperTest {
context.onNewSymbol(c);
}
myLineRanges.add(new TextRange(context.logicalLineStartOffset, context.document.length()));
myLineRanges.add(new TextRange(context.logicalLineStartOffset, myChars.length()));
}
private static boolean isSoftWrapStart(String document, int index) {
@@ -685,7 +687,6 @@ public class CachingSoftWrapDataMapperTest {
private class TestEditorPosition extends EditorPosition {
private final StringBuilder mySoftWrapBuffer = new StringBuilder();
final StringBuilder document = new StringBuilder();
EditorPosition lineStartPosition;
@@ -741,7 +742,7 @@ public class CachingSoftWrapDataMapperTest {
insideFolding = false;
MockFoldRegion foldRegion = new MockFoldRegion(foldingStartOffset, offset);
myFoldRegions.add(foldRegion);
myMapper.onCollapsedFoldRegion(foldRegion, myRepresentationHelper.toVisualColumnSymbolsNumber(document, foldingStartOffset, offset, prevX), foldingStartVisualLine);
myMapper.onCollapsedFoldRegion(foldRegion, myRepresentationHelper.toVisualColumnSymbolsNumber(foldingStartOffset, offset, prevX), foldingStartVisualLine);
}
public void onNewSymbol(char c) {
@@ -868,7 +869,7 @@ public class CachingSoftWrapDataMapperTest {
}
private void onNonSoftWrapSymbol(char c) {
document.append(c);
myChars.append(c);
if (c == '\n') {
myLineRanges.add(new TextRange(logicalLineStartOffset, offset));