diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/folding/JavaFoldingTest.groovy b/java/java-tests/testSrc/com/intellij/codeInsight/folding/JavaFoldingTest.groovy index 408130f74f66..7347fe3b190a 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/folding/JavaFoldingTest.groovy +++ b/java/java-tests/testSrc/com/intellij/codeInsight/folding/JavaFoldingTest.groovy @@ -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\tSystem.out.println(); +\t\t\t} +\t\t}); +\t} +}"""); + } + private int getFoldRegionsCount() { return myFixture.editor.foldingModel.allFoldRegions.length } diff --git a/platform/lang-impl/src/com/intellij/injected/editor/EditorWindowImpl.java b/platform/lang-impl/src/com/intellij/injected/editor/EditorWindowImpl.java index 10bf53bce6bb..f1619d483e85 100644 --- a/platform/lang-impl/src/com/intellij/injected/editor/EditorWindowImpl.java +++ b/platform/lang-impl/src/com/intellij/injected/editor/EditorWindowImpl.java @@ -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 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 237f272266f0..2776693048fa 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 @@ -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 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 e3c03c2fbbd4..ed7bd263cf6c 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 @@ -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. *

diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/ex/util/EditorUtil.java b/platform/platform-impl/src/com/intellij/openapi/editor/ex/util/EditorUtil.java index 7d9973fed2d3..037bbc8337bc 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/ex/util/EditorUtil.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/ex/util/EditorUtil.java @@ -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 [start; end) range and points to the target logical * column if any; -1 otherwise */ - private static int calcSoftWrapUnawareOffset(@NotNull Editor editor, + public static int calcSoftWrapUnawareOffset(@NotNull Editor editor, @NotNull CharSequence text, int start, int end, diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/CaretImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/CaretImpl.java index a84baee39f19..ca6de1b8c65f 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/CaretImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/CaretImpl.java @@ -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 ) ); } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/DefaultEditorTextRepresentationHelper.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/DefaultEditorTextRepresentationHelper.java index 61415eb90ce4..483d179fac3a 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/DefaultEditorTextRepresentationHelper.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/DefaultEditorTextRepresentationHelper.java @@ -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') { 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 861287d8558b..cb2d7cbf079b 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 @@ -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 diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorTextRepresentationHelper.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorTextRepresentationHelper.java index 85f5bd11ed5d..5c66c0bc64f5 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorTextRepresentationHelper.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorTextRepresentationHelper.java @@ -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 'x' 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 'x' 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 ([start; end)) starting @@ -52,17 +50,16 @@ public interface EditorTextRepresentationHelper { * Note: target region is allows to contain line feeds, the width is calculated as a difference between 'x' * 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 'x' 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). *

* Note: 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); } 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 94aad9480c12..83cc0387866c 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 @@ -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) { diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/SoftWrapDataMapper.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/SoftWrapDataMapper.java index 874b0c47355c..ae6d115dc270 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/SoftWrapDataMapper.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/SoftWrapDataMapper.java @@ -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; } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/AbstractMappingStrategy.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/AbstractMappingStrategy.java index bccdf4c59336..7e068caea39e 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/AbstractMappingStrategy.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/AbstractMappingStrategy.java @@ -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 implements MappingStrategy { - protected final Editor myEditor; + protected final EditorEx myEditor; protected final SoftWrapsStorage myStorage; protected final List myCache; @@ -46,7 +46,7 @@ abstract class AbstractMappingStrategy implements MappingStrategy { private T myEagerMatch; private int myLastEntryOffset; - AbstractMappingStrategy(@NotNull Editor editor, + AbstractMappingStrategy(@NotNull EditorEx editor, @NotNull SoftWrapsStorage storage, @NotNull List cache) { @@ -154,7 +154,11 @@ abstract class AbstractMappingStrategy implements MappingStrategy { 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 implements MappingStrategy { } position.advance(foldRegion, collapsedSymbolsWidthInColumns); - return null; } @Nullable 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 2f21b0319af2..38b03c706a15 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 @@ -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. diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/EditorPosition.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/EditorPosition.java index 1097f2824445..e1547d069686 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/EditorPosition.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/EditorPosition.java @@ -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; diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/LogicalToOffsetCalculationStrategy.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/LogicalToOffsetCalculationStrategy.java new file mode 100644 index 000000000000..1fcee26d6f77 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/LogicalToOffsetCalculationStrategy.java @@ -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 { + + private LogicalPosition targetPosition; + + LogicalToOffsetCalculationStrategy(@NotNull EditorEx editor, @NotNull SoftWrapsStorage storage, @NotNull List 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; + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/LogicalToVisualMappingStrategy.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/LogicalToVisualMappingStrategy.java index 6ed904624fa6..90375ca0a888 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/LogicalToVisualMappingStrategy.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/LogicalToVisualMappingStrategy.java @@ -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 cache) throws IllegalStateException { @@ -122,7 +123,7 @@ class LogicalToVisualMappingStrategy extends AbstractMappingStrategy 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; + } } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/OffsetToLogicalCalculationStrategy.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/OffsetToLogicalCalculationStrategy.java index 399406f2db32..a7bf6917c5da 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/OffsetToLogicalCalculationStrategy.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/OffsetToLogicalCalculationStrategy.java @@ -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 cache) + OffsetToLogicalCalculationStrategy(@NotNull EditorEx editor, @NotNull SoftWrapsStorage storage, @NotNull List cache) { super(editor, storage, cache); } @@ -157,13 +158,13 @@ class OffsetToLogicalCalculationStrategy extends AbstractMappingStrategy cache) + VisualToLogicalCalculationStrategy(@NotNull EditorEx editor, @NotNull SoftWrapsStorage storage, @NotNull List cache) { super(editor, storage, cache); mySearchKey = new CacheEntry(0, editor); diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/softwrap/MockEditorTextRepresentationHelper.java b/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/softwrap/MockEditorTextRepresentationHelper.java index 8d9f4eedb20b..6edd72f54219 100644 --- a/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/softwrap/MockEditorTextRepresentationHelper.java +++ b/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/softwrap/MockEditorTextRepresentationHelper.java @@ -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; + } } diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/softwrap/mapping/CachingSoftWrapDataMapperTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/softwrap/mapping/CachingSoftWrapDataMapperTest.java index 7448c6c867f6..6a0abd6a667c 100644 --- a/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/softwrap/mapping/CachingSoftWrapDataMapperTest.java +++ b/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/softwrap/mapping/CachingSoftWrapDataMapperTest.java @@ -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));