blob: d8b6697dc8d133d56c5fb4b9748cbd166f013e1a [file] [log] [blame]
/*
* 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;
import com.intellij.diagnostic.Dumpable;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.*;
import com.intellij.openapi.editor.colors.FontPreferences;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.editor.event.DocumentListener;
import com.intellij.openapi.editor.ex.*;
import com.intellij.openapi.editor.ex.util.EditorUtil;
import com.intellij.openapi.editor.impl.softwrap.*;
import com.intellij.openapi.editor.impl.softwrap.mapping.*;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
import com.intellij.reference.SoftReference;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import java.awt.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Default {@link SoftWrapModelEx} implementation.
* <p/>
* Works as a mix of <code>GoF Facade and Bridge</code>, i.e. delegates the processing to the target sub-components and provides
* utility methods built on top of sub-components API.
* <p/>
* Not thread-safe.
*
* @author Denis Zhdanov
* @since Jun 8, 2010 12:47:32 PM
*/
public class SoftWrapModelImpl implements SoftWrapModelEx, PrioritizedDocumentListener, DocumentBulkUpdateListener, FoldingListener,
PropertyChangeListener, Dumpable, Disposable
{
/**
* Holds name of JVM property which presence should trigger debug-aware soft wraps processing.
*/
public static final String DEBUG_PROPERTY_NAME = "idea.editor.wrap.soft.debug";
private static final Logger LOG = Logger.getInstance("#" + SoftWrapModelImpl.class.getName());
private final OffsetToLogicalTask myOffsetToLogicalTask = new OffsetToLogicalTask();
private final VisualToLogicalTask myVisualToLogicalTask = new VisualToLogicalTask();
private final LogicalToVisualTask myLogicalToVisualTask = new LogicalToVisualTask();
private final FoldProcessingEndTask myFoldProcessingEndTask = new FoldProcessingEndTask();
private final List<DocumentListener> myDocumentListeners = new ArrayList<DocumentListener>();
private final List<SoftWrapChangeListener> mySoftWrapListeners = new ArrayList<SoftWrapChangeListener>();
/**
* There is a possible case that particular activity performs batch fold regions operations (addition, removal etc).
* We don't want to process them at the same time we get notifications about that because there is a big chance that
* we see inconsistent state (e.g. there was a problem with {@link FoldingModel#getCollapsedRegionAtOffset(int)} because that
* method uses caching internally and cached data becomes inconsistent if, for example, the top region is removed).
* <p/>
* So, our strategy is to collect information about changed fold regions and process it only when batch folding processing ends.
*/
private final List<FoldRegionInfo> myDeferredFoldRegions = new ArrayList<FoldRegionInfo>();
private final SoftWrapFoldBasedApplianceStrategy myFoldBasedApplianceStrategy;
private final CachingSoftWrapDataMapper myDataMapper;
private final SoftWrapsStorage myStorage;
private SoftWrapPainter myPainter;
private final SoftWrapApplianceManager myApplianceManager;
private final SoftWrapAwareVisualSizeManager myVisualSizeManager;
private EditorTextRepresentationHelper myEditorTextRepresentationHelper;
private final EditorEx myEditor;
/**
* We don't want to use soft wraps-aware processing from non-EDT and profiling shows that 'is EDT' check that is called too
* often is rather expensive. Hence, we use caching here for performance improvement.
*/
private SoftReference<Thread> myLastEdt = new SoftReference<Thread>(null);
/** Holds number of 'active' calls, i.e. number of methods calls of the current object within the current call stack. */
private int myActive;
private boolean myUseSoftWraps;
private int myTabWidth = -1;
@NotNull
private FontPreferences myFontPreferences;
/**
* Soft wraps need to be kept up-to-date on all editor modification (changing text, adding/removing/expanding/collapsing fold
* regions etc). Hence, we need to react to all types of target changes. However, soft wraps processing uses various information
* provided by editor and there is a possible case that that information is inconsistent during update time (e.g. fold model
* advances fold region offsets when end-user types before it, hence, fold regions data is inconsistent between the moment
* when text changes are applied to the document and fold data is actually updated).
* <p/>
* Current field serves as a flag that indicates if all preliminary actions necessary for successful soft wraps processing is done.
*/
private boolean myUpdateInProgress;
private boolean myBulkUpdateInProgress;
/**
* There is a possible case that target document is changed while its editor is inactive (e.g. user opens two editors for classes
* <code>'Part'</code> and <code>'Whole'</code>; activates editor for the class <code>'Whole'</code> and performs 'rename class'
* for <code>'Part'</code> from it). Soft wraps cache is not recalculated during that because corresponding editor is not shown
* and we lack information about visible area width. Hence, we will need to recalculate the whole soft wraps cache as soon
* as target editor becomes visible.
* <p/>
* Current field serves as a flag for that <code>'dirty document, need complete soft wraps cache recalculation'</code> state.
*/
private boolean myDirty;
private boolean myForceAdditionalColumns;
public SoftWrapModelImpl(@NotNull EditorEx editor) {
myEditor = editor;
myStorage = new SoftWrapsStorage();
myPainter = new CompositeSoftWrapPainter(editor);
myEditorTextRepresentationHelper = new DefaultEditorTextRepresentationHelper(editor);
myDataMapper = new CachingSoftWrapDataMapper(editor, myStorage);
myApplianceManager = new SoftWrapApplianceManager(myStorage, editor, myPainter, myDataMapper);
myFoldBasedApplianceStrategy = new SoftWrapFoldBasedApplianceStrategy(editor);
myVisualSizeManager = new SoftWrapAwareVisualSizeManager(myPainter);
myDocumentListeners.add(myApplianceManager);
myApplianceManager.addListener(myVisualSizeManager);
myApplianceManager.addListener(new SoftWrapAwareDocumentParsingListenerAdapter() {
@Override
public void recalculationEnds() {
for (SoftWrapChangeListener listener : mySoftWrapListeners) {
listener.recalculationEnds();
}
}
});
EditorSettings settings = myEditor.getSettings();
myUseSoftWraps = settings.isUseSoftWraps();
myFontPreferences = myEditor.getColorsScheme().getFontPreferences();
editor.addPropertyChangeListener(this);
ApplicationManager.getApplication().getMessageBus().connect(this).subscribe(DocumentBulkUpdateListener.TOPIC, this);
myApplianceManager.addListener(myDataMapper);
}
/**
* 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();
int tabWidthBefore = myTabWidth;
myTabWidth = getCurrentTabWidth();
boolean fontsChanged = false;
if (!myFontPreferences.equals(myEditor.getColorsScheme().getFontPreferences())
&& myEditorTextRepresentationHelper instanceof DefaultEditorTextRepresentationHelper) {
fontsChanged = true;
myFontPreferences = myEditor.getColorsScheme().getFontPreferences();
((DefaultEditorTextRepresentationHelper)myEditorTextRepresentationHelper).clearSymbolWidthCache();
}
if ((myUseSoftWraps ^ softWrapsUsedBefore) || (tabWidthBefore >= 0 && myTabWidth != tabWidthBefore) || fontsChanged) {
myApplianceManager.reset();
myDeferredFoldRegions.clear();
myEditor.getScrollingModel().scrollToCaret(ScrollType.CENTER);
}
}
/**
* @return tab width for the file used at the current editor (if it's possible to calculate the one);
* <code>'-1'</code> otherwise
*/
private int getCurrentTabWidth() {
final CodeStyleSettings settings = CodeStyleSettingsManager.getSettings(myEditor.getProject());
final VirtualFile file = myEditor.getVirtualFile();
if (file == null) {
return -1;
}
final CommonCodeStyleSettings.IndentOptions indentOptions = settings.getIndentOptions(file.getFileType());
return indentOptions.TAB_SIZE;
}
@Override
public boolean isRespectAdditionalColumns() {
return myForceAdditionalColumns || !isSoftWrappingEnabled() || myApplianceManager.hasLinesWithFailedWrap();
}
@Override
public void forceAdditionalColumnsUsage() {
myForceAdditionalColumns = true;
}
@Override
public boolean isSoftWrappingEnabled() {
if (!myUseSoftWraps || myEditor.isOneLineMode() || myEditor.isPurePaintingMode()) {
return false;
}
// We check that current thread is EDT because attempt to retrieve information about visible area width may fail otherwise
Application application = ApplicationManager.getApplication();
Thread lastEdt = myLastEdt.get();
Thread currentThread = Thread.currentThread();
if (lastEdt != currentThread) {
if (application.isDispatchThread()) {
myLastEdt = new SoftReference<Thread>(currentThread);
}
else {
myLastEdt = new SoftReference<Thread>(null);
return false;
}
}
if (application.isUnitTestMode()) return true;
Rectangle visibleArea = myEditor.getScrollingModel().getVisibleArea();
return visibleArea.width > 0 && visibleArea.height > 0;
}
@Override
@Nullable
public SoftWrap getSoftWrap(int offset) {
if (!isSoftWrappingEnabled()) {
return null;
}
return myStorage.getSoftWrap(offset);
}
@Override
public int getSoftWrapIndex(int offset) {
return myStorage.getSoftWrapIndex(offset);
}
@NotNull
@Override
public List<? extends SoftWrap> getSoftWrapsForRange(int start, int end) {
if (!isSoftWrappingEnabled() || end < start) {
return Collections.emptyList();
}
List<? extends SoftWrap> softWraps = myStorage.getSoftWraps();
int startIndex = myStorage.getSoftWrapIndex(start);
if (startIndex < 0) {
startIndex = -startIndex - 1;
if (startIndex >= softWraps.size() || softWraps.get(startIndex).getStart() > end) {
return Collections.emptyList();
}
}
int endIndex = myStorage.getSoftWrapIndex(end);
if (endIndex >= 0) {
return softWraps.subList(startIndex, endIndex + 1);
}
else {
endIndex = -endIndex - 1;
return softWraps.subList(startIndex, endIndex);
}
}
@Override
@NotNull
public List<? extends SoftWrap> getSoftWrapsForLine(int documentLine) {
if (!isSoftWrappingEnabled() || documentLine < 0) {
return Collections.emptyList();
}
Document document = myEditor.getDocument();
if (documentLine >= document.getLineCount()) {
return Collections.emptyList();
}
int start = document.getLineStartOffset(documentLine);
int end = document.getLineEndOffset(documentLine);
return getSoftWrapsForRange(start, end + 1/* it's theoretically possible that soft wrap is registered just before the line feed,
* hence, we add '1' here assuming that end line offset points to line feed symbol */
);
}
/**
* @return total number of soft wrap-introduced new visual lines
*/
public int getSoftWrapsIntroducedLinesNumber() {
if (!isSoftWrappingEnabled()) {
return 0;
}
return myStorage.getSoftWraps().size(); // Assuming that soft wrap has single line feed all the time
}
/**
* Callback method that is expected to be invoked before editor painting.
* <p/>
* It's primary purpose is to recalculate soft wraps at least for the painted area if necessary.
*/
public void registerSoftWrapsIfNecessary() {
if (!isSoftWrappingEnabled()) {
return;
}
myActive++;
try {
myApplianceManager.registerSoftWrapIfNecessary();
}
finally {
myActive--;
}
}
@Override
public List<? extends SoftWrap> getRegisteredSoftWraps() {
if (!isSoftWrappingEnabled()) {
return Collections.emptyList();
}
return myStorage.getSoftWraps();
}
@Override
public boolean isVisible(SoftWrap softWrap) {
FoldingModel foldingModel = myEditor.getFoldingModel();
int start = softWrap.getStart();
if (foldingModel.isOffsetCollapsed(start)) {
return false;
}
// There is a possible case that soft wrap and collapsed folding region share the same offset, i.e. soft wrap is represented
// before the folding. We need to return 'true' in such situation. Hence, we check if offset just before the soft wrap
// is collapsed as well.
return start <= 0 || !foldingModel.isOffsetCollapsed(start - 1);
}
@Override
public int paint(@NotNull Graphics g, @NotNull SoftWrapDrawingType drawingType, int x, int y, int lineHeight) {
if (!isSoftWrappingEnabled()) {
return 0;
}
return myPainter.paint(g, drawingType, x, y, lineHeight);
}
@Override
public int getMinDrawingWidthInPixels(@NotNull SoftWrapDrawingType drawingType) {
return myPainter.getMinDrawingWidth(drawingType);
}
@NotNull
@Override
public LogicalPosition visualToLogicalPosition(@NotNull VisualPosition visual) {
if (myBulkUpdateInProgress || myUpdateInProgress || !prepareToMapping()) {
return myEditor.visualToLogicalPosition(visual, false);
}
myActive++;
try {
myVisualToLogicalTask.input = visual;
executeSafely(myVisualToLogicalTask);
return myVisualToLogicalTask.output;
} finally {
myActive--;
}
}
@NotNull
@Override
public LogicalPosition offsetToLogicalPosition(int offset) {
if (myBulkUpdateInProgress || myUpdateInProgress || !prepareToMapping()) {
return myEditor.offsetToLogicalPosition(offset, false);
}
myActive++;
try {
myOffsetToLogicalTask.input = offset;
executeSafely(myOffsetToLogicalTask);
return myOffsetToLogicalTask.output;
} finally {
myActive--;
}
}
@NotNull
public LogicalPosition adjustLogicalPosition(LogicalPosition defaultLogical, int offset) {
if (myBulkUpdateInProgress || myUpdateInProgress || !prepareToMapping()) {
return defaultLogical;
}
myActive++;
try {
myOffsetToLogicalTask.input = offset;
executeSafely(myOffsetToLogicalTask);
return myOffsetToLogicalTask.output;
} finally {
myActive--;
}
}
@Override
@NotNull
public VisualPosition adjustVisualPosition(@NotNull LogicalPosition logical, @NotNull VisualPosition defaultVisual) {
if (myBulkUpdateInProgress || myUpdateInProgress || !prepareToMapping()) {
return defaultVisual;
}
myActive++;
try {
myLogicalToVisualTask.input = logical;
myLogicalToVisualTask.defaultOutput = defaultVisual;
executeSafely(myLogicalToVisualTask);
return myLogicalToVisualTask.output;
}
finally {
myActive--;
}
}
/**
* Encapsulates preparations for performing document dimension mapping (e.g. visual to logical position) and answers
* if soft wraps-aware processing should be used (e.g. there is no need to consider soft wraps if user configured them
* not to be used).
*
* @return <code>true</code> if soft wraps-aware processing should be used; <code>false</code> otherwise
*/
private boolean prepareToMapping() {
boolean useSoftWraps = myActive <= 0 && isSoftWrappingEnabled() && myEditor.getDocument().getTextLength() > 0
&& myFoldBasedApplianceStrategy.processSoftWraps();
if (!useSoftWraps) {
return false;
}
if (myDirty) {
myApplianceManager.reset();
myDeferredFoldRegions.clear();
myDirty = false;
}
return myApplianceManager.recalculateIfNecessary();
}
/**
* Allows to answer if given visual position points to soft wrap-introduced virtual space.
*
* @param visual target visual position to check
* @return <code>true</code> if given visual position points to soft wrap-introduced virtual space;
* <code>false</code> otherwise
*/
@Override
public boolean isInsideSoftWrap(@NotNull VisualPosition visual) {
return isInsideSoftWrap(visual, false);
}
/**
* Allows to answer if given visual position points to soft wrap-introduced virtual space or points just before soft wrap.
*
* @param visual target visual position to check
* @return <code>true</code> if given visual position points to soft wrap-introduced virtual space;
* <code>false</code> otherwise
*/
@Override
public boolean isInsideOrBeforeSoftWrap(@NotNull VisualPosition visual) {
return isInsideSoftWrap(visual, true);
}
private boolean isInsideSoftWrap(@NotNull VisualPosition visual, boolean countBeforeSoftWrap) {
if (!isSoftWrappingEnabled()) {
return false;
}
SoftWrapModel model = myEditor.getSoftWrapModel();
if (!model.isSoftWrappingEnabled()) {
return false;
}
LogicalPosition logical = myEditor.visualToLogicalPosition(visual);
int offset = myEditor.logicalPositionToOffset(logical);
if (offset <= 0) {
// Never expect to be here, just a defensive programming.
return false;
}
SoftWrap softWrap = model.getSoftWrap(offset);
if (softWrap == null) {
return false;
}
// We consider visual positions that point after the last symbol before soft wrap and the first symbol after soft wrap to not
// belong to soft wrap-introduced virtual space.
VisualPosition visualAfterSoftWrap = myEditor.offsetToVisualPosition(offset);
if (visualAfterSoftWrap.line == visual.line && visualAfterSoftWrap.column <= visual.column) {
return false;
}
VisualPosition visualBeforeSoftWrap = myEditor.offsetToVisualPosition(offset - 1);
int x = 0;
LogicalPosition logLineStart = myEditor.visualToLogicalPosition(new VisualPosition(visualBeforeSoftWrap.line, 0));
if (logLineStart.softWrapLinesOnCurrentLogicalLine > 0) {
int offsetLineStart = myEditor.logicalPositionToOffset(logLineStart);
softWrap = model.getSoftWrap(offsetLineStart);
if (softWrap != null) {
x = softWrap.getIndentInPixels();
}
}
int width = EditorUtil.textWidthInColumns(myEditor, myEditor.getDocument().getCharsSequence(), offset - 1, offset, x);
int softWrapStartColumn = visualBeforeSoftWrap.column + width;
if (visual.line > visualBeforeSoftWrap.line) {
return true;
}
return countBeforeSoftWrap ? visual.column >= softWrapStartColumn : visual.column > softWrapStartColumn;
}
@Override
public void beforeDocumentChangeAtCaret() {
CaretModel caretModel = myEditor.getCaretModel();
VisualPosition visualCaretPosition = caretModel.getVisualPosition();
if (!isInsideSoftWrap(visualCaretPosition)) {
return;
}
SoftWrap softWrap = myStorage.getSoftWrap(caretModel.getOffset());
if (softWrap == null) {
return;
}
myEditor.getDocument().replaceString(softWrap.getStart(), softWrap.getEnd(), softWrap.getText());
caretModel.moveToVisualPosition(visualCaretPosition);
}
public void setPlace(@NotNull SoftWrapAppliancePlaces place) {
myFoldBasedApplianceStrategy.setCurrentPlace(place);
}
@Override
public boolean addSoftWrapChangeListener(@NotNull SoftWrapChangeListener listener) {
mySoftWrapListeners.add(listener);
return myStorage.addSoftWrapChangeListener(listener);
}
public boolean addVisualSizeChangeListener(@NotNull VisualSizeChangeListener listener) {
return myVisualSizeManager.addVisualSizeChangeListener(listener);
}
@Override
public int getPriority() {
return EditorDocumentPriorities.SOFT_WRAP_MODEL;
}
@Override
public void beforeDocumentChange(DocumentEvent event) {
if (myBulkUpdateInProgress) {
return;
}
myUpdateInProgress = true;
if (!isSoftWrappingEnabled()) {
myDirty = true;
return;
}
for (DocumentListener listener : myDocumentListeners) {
listener.beforeDocumentChange(event);
}
}
@Override
public void documentChanged(DocumentEvent event) {
if (myBulkUpdateInProgress) {
return;
}
myUpdateInProgress = false;
if (!isSoftWrappingEnabled()) {
return;
}
for (DocumentListener listener : myDocumentListeners) {
listener.documentChanged(event);
}
}
@Override
public void updateStarted(@NotNull Document doc) {
if (doc != myEditor.getDocument()) return;
myBulkUpdateInProgress = true;
}
@Override
public void updateFinished(@NotNull Document doc) {
if (doc != myEditor.getDocument()) return;
myBulkUpdateInProgress = false;
if (!isSoftWrappingEnabled()) {
return;
}
recalculate();
}
@Override
public void onFoldRegionStateChange(@NotNull FoldRegion region) {
myUpdateInProgress = true;
if (!isSoftWrappingEnabled() || !region.isValid()) {
myDirty = true;
return;
}
// We delay processing of changed fold regions till the invocation of onFoldProcessingEnd(), as
// FoldingModel can return inconsistent data before that moment.
myDeferredFoldRegions.add(new FoldRegionInfo(region));
}
@Override
public void onFoldProcessingEnd() {
myUpdateInProgress = false;
if (!isSoftWrappingEnabled()) {
return;
}
executeSafely(myFoldProcessingEndTask);
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (EditorEx.PROP_FONT_SIZE.equals(evt.getPropertyName())) {
myDirty = true;
}
}
@NotNull
public CachingSoftWrapDataMapper getDataMapper() {
return myDataMapper;
}
@Override
public void dispose() {
release();
}
@Override
public void release() {
myDataMapper.release();
myApplianceManager.release();
myStorage.removeAll();
myDeferredFoldRegions.clear();
}
public void recalculate() {
myApplianceManager.reset();
myStorage.removeAll();
myDeferredFoldRegions.clear();
myEditor.getScrollingModel().scrollToCaret(ScrollType.CENTER);
myApplianceManager.recalculateIfNecessary();
}
public SoftWrapApplianceManager getApplianceManager() {
return myApplianceManager;
}
/**
* We know that there are problems with incremental soft wraps cache update at the moment. Hence, we may implement full cache
* reconstruction when the problem is encountered in order to avoid customer annoyance.
* <p/>
* However, the problems still should be fixed, hence, we report them only if dedicated flag is set.
* <p/>
* Current method encapsulates the logic mentioned above.
*
* @param task command object that which execution may trigger incremental update of update soft wraps cache
*/
@SuppressWarnings({"UseOfArchaicSystemPropertyAccessors"})
private void executeSafely(SoftWrapAwareTask task) {
try {
task.run(true);
} catch (Throwable e) {
if (Boolean.getBoolean(DEBUG_PROPERTY_NAME) || ApplicationManager.getApplication().isUnitTestMode()) {
String info = "";
if (myEditor instanceof EditorImpl) {
info = ((EditorImpl)myEditor).dumpState();
}
LOG.error(String.format("Unexpected exception occurred during performing '%s'", task), e, info);
}
myEditor.getFoldingModel().rebuild();
myDataMapper.release();
myApplianceManager.reset();
myStorage.removeAll();
myApplianceManager.recalculateIfNecessary();
try {
task.run(true);
}
catch (Throwable e1) {
String info = "";
if (myEditor instanceof EditorImpl) {
info = ((EditorImpl)myEditor).dumpState();
}
LOG.error(String.format("Can't perform %s even with complete soft wraps cache re-parsing", task), e1, info);
myEditor.getSettings().setUseSoftWraps(false);
task.run(false);
}
}
}
@TestOnly
public void setSoftWrapPainter(SoftWrapPainter painter) {
myPainter = painter;
myApplianceManager.setSoftWrapPainter(painter);
myVisualSizeManager.setSoftWrapPainter(painter);
}
public static EditorTextRepresentationHelper getEditorTextRepresentationHelper(@NotNull Editor editor) {
return ((SoftWrapModelEx)editor.getSoftWrapModel()).getEditorTextRepresentationHelper();
}
public EditorTextRepresentationHelper getEditorTextRepresentationHelper() {
return myEditorTextRepresentationHelper;
}
@TestOnly
public void setEditorTextRepresentationHelper(EditorTextRepresentationHelper editorTextRepresentationHelper) {
myEditorTextRepresentationHelper = editorTextRepresentationHelper;
myApplianceManager.reset();
}
@NotNull
@Override
public String dumpState() {
return String.format("appliance manager state: %s; soft wraps mapping info: %s",
myApplianceManager.dumpState(), myDataMapper.dumpState());
}
@Override
public String toString() {
return dumpState();
}
/**
* Defines generic interface for the command that may be proceeded in both <code>'soft wraps aware'</code> and
* <code>'soft wraps unaware'</code> modes.
*/
private interface SoftWrapAwareTask {
/**
* Asks current task to do the job.
* <p/>
* It's assumed that input data (if any) is already stored at the task object. Processing result (if any) is assumed
* to be stored there as well for further retrieval in implementation-specific manner.
*
* @param softWrapAware flag that indicates if soft wraps-aware processing should be performed
* @throws IllegalStateException in case of inability to do the job
*/
void run(boolean softWrapAware) throws IllegalStateException;
}
private class OffsetToLogicalTask implements SoftWrapAwareTask {
public int input;
public LogicalPosition output;
@Override
public void run(boolean softWrapAware) throws IllegalStateException {
if (softWrapAware) {
output = myDataMapper.offsetToLogicalPosition(input);
}
else {
output = myEditor.offsetToLogicalPosition(input, false);
}
}
@Override
public String toString() {
return "mapping from offset (" + input + ") to logical position";
}
}
private class VisualToLogicalTask implements SoftWrapAwareTask {
public VisualPosition input;
public LogicalPosition output;
@Override
public void run(boolean softWrapAware) throws IllegalStateException {
if (softWrapAware) {
output = myDataMapper.visualToLogical(input);
}
else {
output = myEditor.visualToLogicalPosition(input, false);
}
}
@Override
public String toString() {
return "mapping from visual position (" + input + ") to logical position";
}
}
private class LogicalToVisualTask implements SoftWrapAwareTask {
public LogicalPosition input;
public VisualPosition defaultOutput;
public VisualPosition output;
@Override
public void run(boolean softWrapAware) throws IllegalStateException {
output = softWrapAware ? myDataMapper.logicalToVisualPosition(input, defaultOutput) : defaultOutput;
}
@Override
public String toString() {
return "mapping from logical position (" + input + ") to visual position";
}
}
private class FoldProcessingEndTask implements SoftWrapAwareTask {
@Override
public void run(boolean softWrapAware) {
if (!softWrapAware) {
return;
}
try {
if (!myDirty) { // no need to recalculate specific areas if the whole document will be reprocessed
for (FoldRegionInfo info : myDeferredFoldRegions) {
// There is a possible case that given fold region is contained inside another collapsed fold region. We don't want to process
// such nested region then.
FoldRegion outerRegion = myEditor.getFoldingModel().getCollapsedRegionAtOffset(info.start);
if (outerRegion != null && outerRegion != info.region && outerRegion.getStartOffset() <= info.start
&& outerRegion.getEndOffset() >= info.end)
{
continue;
}
myApplianceManager.onFoldRegionStateChange(info.start, info.end);
}
}
}
finally {
myDeferredFoldRegions.clear();
}
myApplianceManager.onFoldProcessingEnd();
}
@Override
public String toString() {
return "fold regions state change processing";
}
}
private static class FoldRegionInfo {
public final FoldRegion region;
public final int start;
public final int end;
FoldRegionInfo(@NotNull FoldRegion region) {
this.region = region;
start = region.getStartOffset();
end = region.getEndOffset();
}
}
}