IDEA-53596 Soft wrap for editors

First soft wraps implementation iteration. Current state:
* soft wraps are represented at the editor during its repainting;
* line numbers representations is consistent with soft-wrapped lines presence;
* logical position representation is consistent with soft-wrapped lines presence;
This commit is contained in:
Denis Zhdanov
2010-06-24 14:21:58 +04:00
parent cce6b7a7b9
commit dbfead834d
13 changed files with 956 additions and 53 deletions
@@ -2,7 +2,7 @@
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.intellij.application.options.editor.EditorOptionsPanel">
<tabbedpane id="97a1e">
<constraints>
<xy x="22" y="49" width="622" height="455"/>
<xy x="22" y="49" width="781" height="495"/>
</constraints>
<properties/>
<border type="none"/>
@@ -64,7 +64,7 @@
</component>
</children>
</grid>
<grid id="5342a" layout-manager="GridLayoutManager" row-count="3" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<grid id="5342a" layout-manager="GridLayoutManager" row-count="4" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="1" fill="3" indent="0" use-parent-layout="false">
@@ -76,7 +76,7 @@
<children>
<component id="400d4" class="javax.swing.JCheckBox" binding="myCbVirtualPageAtBottom">
<constraints>
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="7" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
<grid row="3" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="7" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text resource-bundle="messages/ApplicationBundle" key="checkbox.show.virtual.space.at.file.bottom"/>
@@ -84,7 +84,7 @@
</component>
<component id="f762" class="javax.swing.JCheckBox" binding="myCbCaretInsideTabs">
<constraints>
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text resource-bundle="messages/ApplicationBundle" key="checkbox.allow.placement.of.caret.inside.tabs"/>
@@ -92,13 +92,22 @@
</component>
<component id="bb18b" class="javax.swing.JCheckBox" binding="myCbVirtualSpace">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<selected value="true"/>
<text resource-bundle="messages/ApplicationBundle" key="checkbox.allow.placement.of.caret.after.end.of.line"/>
</properties>
</component>
<component id="37683" class="javax.swing.JCheckBox" binding="myCbUseSoftWraps">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<selected value="true"/>
<text resource-bundle="messages/ApplicationBundle" key="checkbox.use.soft.wraps"/>
</properties>
</component>
</children>
</grid>
<grid id="dcf48" layout-manager="GridLayoutManager" row-count="3" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
@@ -72,6 +72,7 @@ public class EditorOptionsPanel {
private JCheckBox myCbEnsureBlankLineBeforeCheckBox;
private JCheckBox myShowReformatCodeDialogCheckBox;
private JCheckBox myShowOptimizeImportsDialogCheckBox;
private JCheckBox myCbUseSoftWraps;
private final ErrorHighlightingPanel myErrorHighlightingPanel = new ErrorHighlightingPanel();
private final MyConfigurable myConfigurable;
@@ -115,6 +116,7 @@ public class EditorOptionsPanel {
// Virtual space
myCbUseSoftWraps.setSelected(editorSettings.isUseSoftWraps());
myCbVirtualSpace.setSelected(editorSettings.isVirtualSpace());
myCbCaretInsideTabs.setSelected(editorSettings.isCaretInsideTabs());
myCbVirtualPageAtBottom.setSelected(editorSettings.isAdditionalPageAtBottom());
@@ -175,6 +177,7 @@ public class EditorOptionsPanel {
// Virtual space
editorSettings.setUseSoftWraps(myCbUseSoftWraps.isSelected());
editorSettings.setVirtualSpace(myCbVirtualSpace.isSelected());
editorSettings.setCaretInsideTabs(myCbCaretInsideTabs.isSelected());
editorSettings.setAdditionalPageAtBottom(myCbVirtualPageAtBottom.isSelected());
@@ -292,6 +295,7 @@ public class EditorOptionsPanel {
isModified |= isModified(myCbHighlightIdentifierUnderCaret, codeInsightSettings.HIGHLIGHT_IDENTIFIER_UNDER_CARET);
// Virtual space
isModified |= isModified(myCbUseSoftWraps, editorSettings.isUseSoftWraps());
isModified |= isModified(myCbVirtualSpace, editorSettings.isVirtualSpace());
isModified |= isModified(myCbCaretInsideTabs, editorSettings.isCaretInsideTabs());
isModified |= isModified(myCbVirtualPageAtBottom, editorSettings.isAdditionalPageAtBottom());
@@ -96,6 +96,8 @@ public interface EditorSettings {
void setRefrainFromScrolling(boolean b);
boolean isIndentGuidesShown();
void setIndentGuidesShown(boolean val);
boolean isUseSoftWraps();
void setUseSoftWraps(boolean use);
}
@@ -21,6 +21,8 @@ import org.jetbrains.annotations.NonNls;
* Represents a logical position in the editor. Logical positions ignore folding -
* for example, if the top 10 lines of the document are folded, the 10th line in the document
* will have the line number 10 in its logical position.
* <p/>
* //TODO den add doc equals/compareTo() behavior difference because of soft wraps.
*
* @see Editor#offsetToLogicalPosition(int)
* @see Editor#logicalPositionToOffset(LogicalPosition)
@@ -33,30 +35,56 @@ import org.jetbrains.annotations.NonNls;
public class LogicalPosition implements Comparable<LogicalPosition> {
public final int line;
public final int column;
//TODO den add doc
public final int softWrapLines;
//TODO den add doc
public final int linesFromActiveSoftWrap;
//TODO den add doc
public final int softWrapColumns;
public LogicalPosition(int line, int column) {
this(line, column, 0, 0, 0);
}
public LogicalPosition(int line, int column, int softWrapLines, int linesFromActiveSoftWrap, int softWrapColumns) {
assert linesFromActiveSoftWrap <= softWrapLines;
this.line = line;
this.column = column;
this.softWrapLines = softWrapLines;
this.linesFromActiveSoftWrap = linesFromActiveSoftWrap;
this.softWrapColumns = softWrapColumns;
}
//TODO den add doc
public boolean isOnSoftWrappedLine() {
return softWrapColumns != 0;
}
public boolean equals(Object o) {
if (!(o instanceof LogicalPosition)) return false;
final LogicalPosition logicalPosition = (LogicalPosition) o;
return column == logicalPosition.column && line == logicalPosition.line;
return column == logicalPosition.column && line == logicalPosition.line && softWrapLines == logicalPosition.softWrapLines
&& softWrapColumns == logicalPosition.softWrapColumns;
}
public int hashCode() {
return 29 * line + column;
int result = 29 * line + column;
result = result * 29 + softWrapLines;
return 29 * result + softWrapColumns;
}
@NonNls
public String toString() {
return "LogicalPosition: line = " + line + " column = " + column;
return "LogicalPosition: line = " + line + " column = " + column + "; soft wrap: lines = " + softWrapLines
+ " (active = " + linesFromActiveSoftWrap + ") columns = " + softWrapColumns;
}
public int compareTo(LogicalPosition position) {
if (line != position.line) return line - position.line;
return column - position.column;
if (column != position.column) return column - position.column;
if (softWrapLines != position.softWrapLines) return softWrapLines - position.softWrapLines;
return softWrapColumns - position.softWrapColumns;
}
}
@@ -0,0 +1,123 @@
/*
* Copyright 2000-2010 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;
import gnu.trove.TIntObjectHashMap;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
/**
* Defines contract for the services that bring <code>'soft wrapping'</code> to the editor.
* <p/>
* <code>'Soft wrap'</code> here means representation of the line that exceeds right component viewport margin as if it is wrapped.
* No changes are introduced to the actual document/file system during that, i.e. it's a pure <code>'view'</code> facility.
* <p/>
* <b>Example:</b>
* <p/>
* <b>Raw text (as stored at file system)</b>
* <p/>
* <pre>
* public class Test { | &lt;- right margin
* |
* public void baz() { |
* foo("test1", "test2", "test3", "test4", "test5", "test6");
* } |
* |
* public void foo(Object ... args) { |
* } |
* } |
* </pre>
* <p/>
* <b>The same text with soft wraps as shown to end-user at editor</b>
* <p/>
* <pre>
* public class Test { | &lt;- right margin
* |
* public void baz() { |
* foo("test1", "test2", "test3", "test4", "test5", |
* "test6"); |
* } |
* |
* public void foo(Object ... args) { |
* } |
* } |
* </pre>
* <p/>
* Another important soft wrap feature is that as soon as the user starts typing on a line which representation is affected by
* soft wrap (e.g. starts adding new call argument after <code>"test6"</code> at example above), that soft wraps becomes
* <code>'hard wrap'</code>, i.e. virtual changes introduced by it are flushed to the underlying document.
* <p/>
* <b>Note:</b> soft wrap is assumed to provide as user-friendly indentation for those wrapped line as possible
* (note that <code>"test6"</code> at example below is aligned to the parameters start).
* <p/>
* Implementations of this interface are not obliged to be thread-safe.
*
* @author Denis Zhdanov
* @since Jun 8, 2010 3:15:18 PM
*/
public interface SoftWrapModel {
/**
* Allows to answer if <code>'soft wrap'</code> feature is enabled.
*
* @return <code>true</code> if <code>'soft wraps'</code> are enabled; <code>false</code> otherwise
*/
boolean isSoftWrappingEnabled();
/**
* Allows to retrieve number of lines feeds from <code>'soft wraps'</code> located <b>before</b> the given visual position.
* <p/>
* There is a possible case that given visual position points to location <b>inside</b> soft wrap - line feeds of that soft wrap
* are not counted then.
* <p/>
* <b>Example:</b>
* <p/>
* <pre>
* public class Test { | &lt;- right margin
* |
* public void baz() { |
* foo("test1", "test2", "test3", [p1]
* [p2 "test4", "test5", "test6", |
* "test7", "test8", "test8"); [p3]
* } |
* |
* [p4] public void foo(Object ... args) { |
* } |
* } |
* </pre>
* <p/>
* Here all <code>'foo()'</code> call arguments are soft wrapped into three lines. However zero is returned for visual position
* <code>'p1'</code>-<code>'p3'</code> because they are located <b>inside</b> soft wrap;
* <code>'2'</code> is returned for <code>'p4'</code>.
*
* @param position target visual position
* @return number of lines feeds from <code>'soft wraps'</code> located <b>before</b> the given visual position if any;
* <code>0</code> otherwise
*/
int getSoftWrapLineFeedsBefore(VisualPosition position);
/**
* Asks current model for the soft wrap registered for the given document offset if any.
*
* @param offset target document offset
* @return soft wrap registered for the given offset within the current model if any; <code>null</code> otherwise
*/
@Nullable
TextChange getSoftWrap(int offset);
LogicalPosition adjustLogicalPositionIfNecessary(LogicalPosition logical, VisualPosition visual);
}
@@ -0,0 +1,175 @@
/*
* Copyright 2000-2010 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;
import com.intellij.util.text.CharArrayUtil;
import com.intellij.util.text.CharSequenceBackedByArray;
import org.jetbrains.annotations.NotNull;
import java.util.concurrent.atomic.AtomicReference;
/**
* Provides generic contract for object encapsulating information about single unit of text change.
* <p/>
* Thread-safe.
*
* @author Denis Zhdanov
* @since May 31, 2010 12:26:51 PM
*/
public class TextChange {
private final AtomicReference<char[]> myChars = new AtomicReference<char[]>();
private final CharSequence myText;
private final int myStart;
private final int myEnd;
/**
* Shorthand for creating {@link TextChange} with the given arguments where <code>'end index'</code> has the same value as
* <code>'start index'</code>.
*
* @param text text affected by the current change
* @param start start index (inclusive) of text range affected by the change encapsulated by the current object
* @throws IllegalArgumentException if given start index is invalid
*/
public TextChange(@NotNull CharSequence text, int start) throws IllegalArgumentException {
this(text, start, start);
}
/**
* Creates new <code>TextChange</code> object with the given arguments. It encapsulates information about the change that
* may be applied to the target document.
*
* @param text text that is introduced by the current change
* @param start start index of the target document location where current change is to be applied
* @param end end index of the target document where current change is to be applied, i.e. it's assumed that current text
* change appliance to particular document causes replacement of its original text at <code>[start; end)</code>
* interval by the text encapsulated by the current change. I.e. original text is replaced by the new one
* @throws IllegalArgumentException if given start or end index in invalid or they are inconsistent to each other
*/
public TextChange(@NotNull CharSequence text, int start, int end) throws IllegalArgumentException {
if (start < 0) {
throw new IllegalArgumentException(String.format("Can't construct new %s object. Reason: given start index (%d) is negative. "
+ "End index: %d, text: '%s'", getClass().getName(), start, end, text));
}
if (end < start) {
throw new IllegalArgumentException(String.format("Can't construct new %s object. Reason: given end index (%d) is less than "
+ "start index (%d). Text: '%s'", getClass().getName(), end, start, text));
}
myText = text;
myStart = start;
myEnd = end;
}
/**
* @return start index (inclusive) of text range affected by the change encapsulated at the current object
*/
public int getStart() {
return myStart;
}
/**
* @return end index (exclusive) of text range affected by the change encapsulated at the current object
*/
public int getEnd() {
return myEnd;
}
/**
* Allows to retrieve text that is directly affected by the change encapsulated by the current object.
*
* @return text related to the change encapsulated by the current object
*/
@NotNull
public CharSequence getText() {
return myText;
}
/**
* Allows to get change text as a char array. Note that it's not guaranteed that change text directly maps to the returned char array,
* i.e. change to array content is not obeyed to be reflected in {@link #getText()} result.
* <p/>
* Generally speaking, this method is introduced just as a step toward existing high-performance services that work in terms
* of char arrays. Resulting array is instantiated on-demand via {@link CharArrayUtil#fromSequence(CharSequence)}, hence, it
* doesn't hit memory if, for example, {@link CharSequenceBackedByArray} is used as initial change text.
*
* @return stored change text as a char array
*/
@NotNull
public char[] getChars() {
char[] result = myChars.get();
if (result != null) {
return result;
}
myChars.compareAndSet(null, CharArrayUtil.fromSequence(myText));
return myChars.get();
}
/**
* Difference in document symbols number after current change appliance.
* <p/>
* <b>Note:</b> returned number may be either positive or not. For example it may be negative for <code>'remove'</code>
* or <code>'replace'</code> changes (number of text symbols is less than number of symbols at target change interval)
*
* @return difference in document symbols number after current change appliance
*/
public int getDiff() {
return myText.length() - myEnd + myStart;
}
/**
* Creates new {@link TextChange} on the basis of the current object with given offset applied to its {@link #getStart() start}
* and {@link #getEnd() end} properties.
*
* @param offset offset to apply to the current change object
* @return text change that is built on the basis of the current object that with {@link #getStart() start}
* and {@link #getEnd() end} positions shifted to the given offset
* @throws IllegalArgumentException if start index becomes zero after given offset appliance (it is not applied then)
*/
public TextChange advance(int offset) throws IllegalArgumentException {
if (offset == 0) {
return this;
}
int newStart = myStart + offset;
if (newStart < 0) {
throw new IllegalArgumentException(String.format("Can't apply given offset (%d) to the current text change object (%s). Reason: "
+ "new start index becomes negative after that (%d)", offset, this, newStart));
}
return new TextChange(myText, newStart, myEnd + offset);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TextChange that = (TextChange)o;
return myText.equals(that.myText) && myStart == that.myStart && myEnd == that.myEnd;
}
@Override
public int hashCode() {
int result = myText.hashCode();
result = 31 * result + myStart;
return 31 * result + myEnd;
}
@Override
public String toString() {
return String.format("%d-%d: '%s'", myStart, myEnd, myText);
}
}
@@ -40,6 +40,7 @@ public class EditorSettingsExternalizable implements NamedJDOMExternalizable, Ex
//Q: make it interface?
public static class OptionSet implements Cloneable {
public String LINE_SEPARATOR;
public boolean USE_SOFT_WRAPS = true;
public boolean IS_VIRTUAL_SPACE = true;
public boolean IS_CARET_INSIDE_TABS;
@NonNls public String STRIP_TRAILING_SPACES = "Changed";
@@ -211,6 +212,14 @@ public class EditorSettingsExternalizable implements NamedJDOMExternalizable, Ex
myOptions.SMART_HOME = val;
}
public boolean isUseSoftWraps() {
return myOptions.USE_SOFT_WRAPS;
}
public void setUseSoftWraps(boolean use) {
myOptions.USE_SOFT_WRAPS = use;
}
public boolean isVirtualSpace() {
return myOptions.IS_VIRTUAL_SPACE;
}
@@ -227,11 +227,21 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener {
validateCallContext();
int column = pos.column;
int line = pos.line;
int softWrapLines = pos.softWrapLines;
int linesFromCurrentSoftWrap = pos.linesFromActiveSoftWrap;
int softWrapColumns = pos.softWrapColumns;
Document doc = myEditor.getDocument();
if (column < 0) column = 0;
if (line < 0) line = 0;
if (column < 0) {
column = 0;
softWrapColumns = 0;
}
if (line < 0) {
line = 0;
softWrapLines = 0;
linesFromCurrentSoftWrap = 0;
}
int lineCount = doc.getLineCount();
if (lineCount == 0) {
@@ -239,6 +249,8 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener {
}
else if (line > lineCount - 1) {
line = lineCount - 1;
softWrapLines = 0;
linesFromCurrentSoftWrap = 0;
}
EditorSettings editorSettings = myEditor.getSettings();
@@ -248,6 +260,9 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener {
int lineEndColumnNumber = myEditor.offsetToLogicalPosition(lineEndOffset).column;
if (column > lineEndColumnNumber) {
column = lineEndColumnNumber;
if (softWrapColumns != 0) {
softWrapColumns -= column - lineEndColumnNumber;
}
}
}
@@ -257,7 +272,7 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener {
LogicalPosition oldCaretPosition = myLogicalCaret;
myLogicalCaret = new LogicalPosition(line, column);
myLogicalCaret = new LogicalPosition(line, column, softWrapLines, linesFromCurrentSoftWrap, softWrapColumns);
final int offset = myEditor.logicalPositionToOffset(myLogicalCaret);
@@ -327,7 +327,11 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse
}
for (int i = startLineNumber; i < endLineNumber; i++) {
int logLine = myEditor.visualToLogicalPosition(new VisualPosition(i, 0)).line;
LogicalPosition logicalPosition = myEditor.visualToLogicalPosition(new VisualPosition(i, 0));
if (logicalPosition.isOnSoftWrappedLine()) {
continue;
}
int logLine = logicalPosition.line;
String s = String.valueOf(logLine + 1);
g.drawString(s,
getLineNumberAreaOffset() + getLineNumberAreaWidth() -
@@ -49,6 +49,7 @@ import com.intellij.openapi.editor.highlighter.EditorHighlighter;
import com.intellij.openapi.editor.highlighter.HighlighterClient;
import com.intellij.openapi.editor.impl.event.MarkupModelEvent;
import com.intellij.openapi.editor.impl.event.MarkupModelListener;
import com.intellij.openapi.editor.TextChange;
import com.intellij.openapi.editor.markup.*;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.progress.ProgressManager;
@@ -68,7 +69,7 @@ import com.intellij.util.ui.ButtonlessScrollBarUI;
import com.intellij.util.ui.EmptyClipboardOwner;
import com.intellij.util.ui.UIUtil;
import com.intellij.util.ui.update.UiNotifyConnector;
import gnu.trove.TIntArrayList;
import gnu.trove.*;
import org.jdom.Element;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
@@ -157,6 +158,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
private final FoldingModelImpl myFoldingModel;
private final ScrollingModelImpl myScrollingModel;
private final CaretModelImpl myCaretModel;
private final SoftWrapModelImpl mySoftWrapModel;
private static final RepaintCursorCommand ourCaretBlinkingCommand;
@@ -173,7 +175,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
private EditorHighlighter myHighlighter;
private int myScrollbarOrientation;
private int myScrollBarOrientation;
private boolean myMousePressedInsideSelection;
private FontMetrics myPlainFontMetrics;
private FontMetrics myBoldFontMetrics;
@@ -243,6 +245,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
myMarkupModel = new EditorMarkupModelImpl(this);
myFoldingModel = new FoldingModelImpl(this);
myCaretModel = new CaretModelImpl(this);
mySoftWrapModel = new SoftWrapModelImpl(this);
mySizeContainer.reset();
myCommandProcessor = CommandProcessor.getInstance();
@@ -316,7 +319,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
myCaretCursor = new CaretCursor();
myFoldingModel.flushCaretShift();
myScrollbarOrientation = VERTICAL_SCROLLBAR_RIGHT;
myScrollBarOrientation = VERTICAL_SCROLLBAR_RIGHT;
EditorHighlighter highlighter = new EmptyEditorHighlighter(myScheme.getAttributes(HighlighterColors.TEXT));
setHighlighter(highlighter);
@@ -611,11 +614,11 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
final DropTarget dropTarget = myEditorComponent.getDropTarget();
if (dropTarget != null) { // might be null in headless environment
dropTarget.addDropTargetListener(new DropTargetAdapter() {
public void drop(DropTargetDropEvent dtde) {
public void drop(DropTargetDropEvent e) {
}
public void dragOver(DropTargetDragEvent dtde) {
Point location = dtde.getLocation();
public void dragOver(DropTargetDragEvent e) {
Point location = e.getLocation();
moveCaretToScreenPos(location.x, location.y);
getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
@@ -782,11 +785,19 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
}
}
int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line, 0)));
LogicalPosition logicalPosition = visualToLogicalPosition(new VisualPosition(line, 0));
int offset = logicalPositionToOffset(logicalPosition);
int textLength = myDocument.getTextLength();
if (offset >= textLength) return new VisualPosition(line, 0);
// There is a possible case that starting logical line is split by soft-wraps and it's part after the split should be drawn.
// We mark that we're under such circumstances then.
boolean activeSoftWrapProcessed = true;
if (logicalPosition.isOnSoftWrappedLine()) {
activeSoftWrapProcessed = false;
}
int column = 0;
int prevX = 0;
CharSequence text = myDocument.getCharsNoThreadCheck();
@@ -799,7 +810,9 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
int x = 0;
outer:
while (true) {
if (offset >= textLength) break;
if (offset >= textLength) {
break;
}
if (offset >= state.getEndOffset()) {
state.advance();
@@ -812,32 +825,56 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
for (char aPlaceholder : placeholder) {
c = aPlaceholder;
x += EditorUtil.charWidth(c, fontType, this);
if (x >= px) break outer;
if (x >= px) {
break outer;
}
column++;
}
offset = region.getEndOffset();
}
else {
TextChange softWrap = mySoftWrapModel.getSoftWrap(offset);
if (softWrap != null) {
// There is a possible case that soft wrap contains more than one line feed inside and we need to start counting not
// from its first line.
int softWrapLinesToSkip = activeSoftWrapProcessed ? 0 : logicalPosition.linesFromActiveSoftWrap;
CharSequence softWrapText = softWrap.getText();
for (int i = 0; i < softWrapText.length(); i++) {
c = softWrapText.charAt(i);
if (softWrapLinesToSkip > 0) {
if (c == '\n') {
softWrapLinesToSkip--;
}
continue;
}
prevX = x;
int diff = charToVisibleWidth(c, fontType, x);
if (diff == 0) {
break outer;
}
x += diff;
if (x >= px) {
break outer;
}
column += columnsNumber(c, x, prevX, spaceSize);
}
activeSoftWrapProcessed = true;
}
prevX = x;
c = text.charAt(offset);
if (c == '\n') {
int diff = charToVisibleWidth(c, fontType, x);
if (diff == 0) {
break;
} else {
x += diff;
}
if (x >= px) {
break;
}
if (c == '\t') {
x = EditorUtil.nextTabStop(x, this);
}
else {
x += EditorUtil.charWidth(c, fontType, this);
}
if (x >= px) break;
if (c == '\t') {
column += (x - prevX) / spaceSize;
}
else {
column++;
}
column += columnsNumber(c, x, prevX, spaceSize);
offset++;
}
@@ -868,6 +905,42 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
return new VisualPosition(line, column);
}
/**
* Allows to answer how much width requires given char to be represented on a screen.
*
* @param c target character
* @param fontType font type to use for representation of the given character
* @param currentX current <code>'x'</code> position on a line where given character should be displayed
* @return width required to represent given char with the given settings on a screen;
* <code>'0'</code> if given char is a line break
*/
private int charToVisibleWidth(char c, int fontType, int currentX) {
if (c == '\n') {
return 0;
}
if (c == '\t') {
return EditorUtil.nextTabStop(currentX, this) - currentX;
}
return EditorUtil.charWidth(c, fontType, this);
}
/**
* Allows to answer how many columns are necessary for representation of the given char on a screen.
*
* @param c target char
* @param x <code>'x'</code> coordinate of the line where given char is represented that indicates char end location
* @param prevX <code>'x'</code> coordinate of the line where given char is represented that indicates char start location
* @param spaceSize <code>'space'</code> symbol width
* @return number of columns necessary for representation of the given char on a screen.
*/
private static int columnsNumber(char c, int x, int prevX, int spaceSize) {
if (c != '\t') {
return 1;
}
return (x - prevX) / spaceSize;
}
@NotNull
public VisualPosition offsetToVisualPosition(int offset) {
return logicalToVisualPosition(offsetToLogicalPosition(offset));
@@ -1031,7 +1104,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
return myGutterComponent != null && myGutterComponent.isShowing();
}
private void repaintToScreenBotton(int startLine) {
private void repaintToScreenBottom(int startLine) {
Rectangle visibleRect = getScrollingModel().getVisibleArea();
int yStartLine = logicalLineToY(startLine);
int yEndLine = visibleRect.y + visibleRect.height;
@@ -1082,7 +1155,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
if (countLineFeeds(e.getOldFragment()) != countLineFeeds(e.getNewFragment())) {
// Lines removed. Need to repaint till the end of the screen
repaintToScreenBotton(startLine);
repaintToScreenBottom(startLine);
painted = true;
}
}
@@ -1598,7 +1671,8 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
int visibleLineNumber = clip.y / lineHeight;
int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
LogicalPosition logicalPosition = xyToLogicalPosition(new Point(0, clip.y));
int startLineNumber = logicalPosition.line;
Point position = new Point(0, visibleLineNumber * lineHeight);
if (startLineNumber == 0 && myPrefixText != null) {
@@ -1610,7 +1684,16 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
return;
}
// There is a possible case that starting logical line is split by soft-wraps and it's part after the split should be drawn.
// We do the following then:
// 1. Mark that we're under such circumstances ('activeSoftWrapProcessed = false');
// 2. Use active soft wrap offset as a start offset in order to paint its symbols in correct way;
boolean activeSoftWrapProcessed = true;
int start = myDocument.getLineStartOffset(startLineNumber);
if (logicalPosition.isOnSoftWrappedLine()) {
start = logicalPositionToOffset(logicalPosition);
activeSoftWrapProcessed = false;
}
IterationState iterationState = new IterationState(this, start, paintSelection());
@@ -1640,7 +1723,9 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
drawString(g, chars, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor, effectType, fontType,
currentColor);
position.x = 0;
if (position.y > clip.y + clip.height) break;
if (position.y > clip.y + clip.height) {
break;
}
position.y += lineHeight;
start = lEnd;
}
@@ -1663,6 +1748,63 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
fontType, currentColor);
}
else {
boolean breakWholeIteration = false;
if (mySoftWrapModel.shouldWrap(chars, start, hEnd, position)) {
TextChange softWrap = mySoftWrapModel.wrap(start);
// There is a possible case that soft wrap contains more than one line feed inside and we need to start drawing not
// from its first line.
int softWrapLinesToSkip = activeSoftWrapProcessed ? 0 : logicalPosition.linesFromActiveSoftWrap;
char[] softWrapChars = softWrap.getChars();
// We don't draw every soft wrap symbol one-by-one but whole visual line. Current variable holds index that points
// to the first soft wrap symbol that is not drawn yet.
int softWrapSegmentStartIndex = 0;
for (int i = 0; i < softWrapChars.length; i++) {
// Delay soft wraps symbols drawing until EOL if found.
if (softWrapChars[i] != '\n') {
continue;
}
// Skip top soft wrap lines if necessary.
if (softWrapLinesToSkip-- > 0) {
position.x = 0;
softWrapSegmentStartIndex = i + 1;
continue;
}
// Draw soft wrap symbols on current visual line if any.
if (i - softWrapSegmentStartIndex > 0) {
drawString(
g, softWrapChars, softWrapSegmentStartIndex, i, position, clip, currentColor, effectType, fontType, currentColor
);
}
// Reset 'x' coordinate because of new line start.
position.x = 0;
// Stop the processing if we drew the whole clip.
if (position.y > clip.y + clip.height) {
breakWholeIteration = true;
break;
}
position.y += lineHeight;
softWrapSegmentStartIndex = i + 1;
}
// Draw remaining soft wrap symbols from its last line if any.
if (softWrapSegmentStartIndex < softWrapChars.length - 1) {
position.x += drawString(
g, softWrapChars, softWrapSegmentStartIndex, softWrapChars.length, position, clip, effectColor, effectType,
fontType, currentColor
);
}
activeSoftWrapProcessed = true;
}
if (breakWholeIteration) {
break;
}
position.x = drawString(g, chars, start, hEnd, position, clip, effectColor, effectType, fontType, currentColor);
}
}
@@ -2203,7 +2345,9 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
@NotNull
public VisualPosition logicalToVisualPosition(@NotNull LogicalPosition logicalPos) {
assertReadAccess();
if (!myFoldingModel.isFoldingEnabled()) return new VisualPosition(logicalPos.line, logicalPos.column);
if (!myFoldingModel.isFoldingEnabled() && !mySoftWrapModel.isSoftWrappingEnabled()) {
return new VisualPosition(logicalPos.line, logicalPos.column);
}
int offset = logicalPositionToOffset(logicalPos);
@@ -2224,9 +2368,9 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
line -= myFoldingModel.getFoldedLinesCountBefore(offset);
FoldRegion[] toplevel = myFoldingModel.fetchTopLevel();
FoldRegion[] topLevel = myFoldingModel.fetchTopLevel();
for (int idx = myFoldingModel.getLastTopLevelIndexBefore(offset); idx >= 0; idx--) {
FoldRegion region = toplevel[idx];
FoldRegion region = topLevel[idx];
if (region.isValid()) {
if (region.getDocument().getLineNumber(region.getEndOffset()) == logicalPos.line && region.getEndOffset() <= offset) {
LogicalPosition foldStart = offsetToLogicalPosition(region.getStartOffset());
@@ -2243,7 +2387,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
LOG.assertTrue(line >= 0);
return new VisualPosition(line, Math.max(0, column));
return new VisualPosition(line + logicalPos.softWrapLines, Math.max(0, column + logicalPos.softWrapColumns));
}
@Nullable
@@ -2312,7 +2456,9 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
@NotNull
public LogicalPosition visualToLogicalPosition(@NotNull VisualPosition visiblePos) {
assertReadAccess();
if (!myFoldingModel.isFoldingEnabled()) return new LogicalPosition(visiblePos.line, visiblePos.column);
if (!myFoldingModel.isFoldingEnabled() && !mySoftWrapModel.isSoftWrappingEnabled()) {
return new LogicalPosition(visiblePos.line, visiblePos.column);
}
int line = visiblePos.line;
int column = visiblePos.column;
@@ -2329,14 +2475,14 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
column = logFoldEnd.column + (visiblePos.column - visFoldEnd.column);
}
else {
return offsetToLogicalPosition(lastCollapsedBefore.getStartOffset());
return offsetToLogicalPosition(lastCollapsedBefore.getStartOffset()); //TODO den check if we need soft-wrap processing here
}
}
}
if (column < 0) column = 0;
return new LogicalPosition(line, column);
return mySoftWrapModel.adjustLogicalPositionIfNecessary(new LogicalPosition(line, column), visiblePos);
}
private int calcLogicalLineNumber(int offset) {
@@ -2372,10 +2518,16 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
int columnNumber = pos.column;
int lineNumber = pos.line;
int softWrapLines = pos.softWrapLines;
int linesFromCurrentSoftWrap = pos.linesFromActiveSoftWrap;
int softWrapColumns = pos.softWrapColumns;
if (lineNumber < 0) {
lineNumber = 0;
columnNumber = 0;
softWrapLines = 0;
linesFromCurrentSoftWrap = 0;
softWrapColumns = 0;
}
final int totalLines = myDocument.getLineCount();
@@ -2394,6 +2546,9 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
int lineEndColumnNumber = calcColumnNumber(lineEndOffset, lineNumber);
if (columnNumber > lineEndColumnNumber) {
columnNumber = lineEndColumnNumber;
if (softWrapColumns != 0) {
softWrapColumns -= columnNumber - lineEndColumnNumber;
}
}
}
@@ -2406,7 +2561,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
}
}
}
LogicalPosition pos1 = new LogicalPosition(lineNumber, columnNumber);
LogicalPosition pos1 = new LogicalPosition(lineNumber, columnNumber, softWrapLines, linesFromCurrentSoftWrap, softWrapColumns);
getCaretModel().moveToLogicalPosition(pos1);
}
@@ -3197,7 +3352,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
public void setVerticalScrollbarOrientation(int type) {
assertIsDispatchThread();
int currentHorOffset = myScrollingModel.getHorizontalScrollOffset();
myScrollbarOrientation = type;
myScrollBarOrientation = type;
if (type == VERTICAL_SCROLLBAR_LEFT) {
myScrollPane.setLayout(new LeftHandScrollbarLayout());
}
@@ -3226,7 +3381,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
}
int getVerticalScrollbarOrientation() {
return myScrollbarOrientation;
return myScrollBarOrientation;
}
MyScrollBar getVerticalScrollBar() {
@@ -72,6 +72,7 @@ public class SettingsImpl implements EditorSettings {
private Boolean myIsMouseClickSelectionHonorsCamelWords = null;
private Boolean myIsRenameVariablesInplace = null;
private Boolean myIsRefrainFromScrolling = null;
private Boolean myUseSoftWraps = null;
public boolean isRightMarginShown() {
return myIsRightMarginShown != null
@@ -383,6 +384,18 @@ public class SettingsImpl implements EditorSettings {
myIsRefrainFromScrolling = b ? Boolean.TRUE : Boolean.FALSE;
}
public boolean isUseSoftWraps() {
return myUseSoftWraps != null ? myUseSoftWraps.booleanValue()
: EditorSettingsExternalizable.getInstance().isUseSoftWraps();
}
public void setUseSoftWraps(boolean use) {
final Boolean newValue = use ? Boolean.TRUE : Boolean.FALSE;
if (newValue.equals(myUseSoftWraps)) return;
myUseSoftWraps = newValue;
fireEditorRefresh();
}
private void fireEditorRefresh() {
if (myEditor != null) {
myEditor.reinitSettings();
@@ -0,0 +1,365 @@
/*
* Copyright 2000-2010 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.openapi.editor.*;
import com.intellij.openapi.editor.event.VisibleAreaEvent;
import com.intellij.openapi.editor.event.VisibleAreaListener;
import com.intellij.openapi.editor.ex.util.EditorUtil;
import com.intellij.openapi.editor.TextChange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.text.CharArrayUtil;
import gnu.trove.TIntArrayList;
import gnu.trove.TIntObjectHashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.awt.*;
/**
* //TODO den add doc
* <p/>
* Not thread-safe.
*
* @author Denis Zhdanov
* @since Jun 8, 2010 12:47:32 PM
*/
public class SoftWrapModelImpl implements SoftWrapModel {
private static final TIntObjectHashMap<TextChange> EMPTY = new TIntObjectHashMap<TextChange>();
/** Holds mappings like {@code 'soft wrap start offset at document' -> 'soft wrap indent spaces'}. */
private final TIntObjectHashMap<TextChange> myWraps = new TIntObjectHashMap<TextChange>();
private final EditorImpl myEditor;
private int myFirstVisibleSymbolOffset = -1;
private int myFirstVisibleLine = -1;
private int myRightEdgeLocation = -1;
private boolean myInitialized;
/**
* There is a possible case that visible area is changed but document representation of shown at viewport is still the same
* (e.g. the document doesn't contain long lines and viewport width is expanded).
* <p/>
* We don't drop previously registered soft wraps during visible area change then but mark them as <code>'dirty'</code> in order to
* drop when document content is being repainted.
*/
private boolean myDataIsDirty;
/** Holds number of 'active' calls, i.e. number of methods calls of the current object within the current call stack. */
private int myActive;
public SoftWrapModelImpl(@NotNull EditorImpl editor) {
myEditor = editor;
}
public boolean isSoftWrappingEnabled() {
return myEditor.getSettings().isUseSoftWraps();
}
//TODO den add doc
public boolean shouldWrap(char[] chars, int start, int end, Point position) {
if (!isSoftWrappingEnabled() || containsOnlyWhiteSpaces(chars, start, end)) {
return false;
}
initIfNecessary();
dropDataIfNecessary();
if (myWraps.contains(start)) {
return true;
}
if (myRightEdgeLocation < 0) {
assert false;
return false;
}
//TODO den implement
boolean b = position.x + (end - start) * 7 > myRightEdgeLocation;
//TODO den remove
if (b) {
int i = 1;
}
return b;
}
private static boolean containsOnlyWhiteSpaces(char[] chars, int start, int end) {
int i = CharArrayUtil.shiftForward(chars, start, "\n \t");
return i >= end;
}
//TODO den add doc
public TextChange wrap(int offset) {
TextChange result = myWraps.get(offset);
if (result != null) {
return result;
}
dropDataIfNecessary();
result = new TextChange("\n ", offset); //TODO den implement indent calculation on formatting options basis.
myWraps.put(offset, result);
return result;
}
/**
* Drops information about registered soft wraps if they are marked as <code>'dirty'</code>.
*
* @see #myDataIsDirty
*/
private void dropDataIfNecessary() {
if (myDataIsDirty) {
myWraps.clear();
myDataIsDirty = false;
}
}
public TextChange getSoftWrap(int offset) {
return myWraps.get(offset);
}
public LogicalPosition adjustLogicalPositionIfNecessary(LogicalPosition logical, VisualPosition visual) {
if (myActive > 0 || !isSoftWrappingEnabled() || myWraps.isEmpty()) {
return logical;
}
myActive++;
try {
return doAdjustLogicalPositionIfNecessary(logical, visual);
}
finally {
myActive--;
}
}
@SuppressWarnings({"AssignmentToForLoopParameter"})
private LogicalPosition doAdjustLogicalPositionIfNecessary(LogicalPosition logical, VisualPosition visual) {
CharSequence chars = ((DocumentImpl)myEditor.getDocument()).getCharsNoThreadCheck();
int softWrapIntroducedLines = 0;
int linesFromCurrentSoftWrap = 0;
int symbolsOnCurrentLogicalLine = 0;
int symbolsOnCurrentVisibleLine = 0;
int softWrapsSymbolsOnCurrentVisibleLine = 0;
FoldingModel foldingModel = myEditor.getFoldingModel();
int currentLine = myFirstVisibleLine;
for (int i = myFirstVisibleSymbolOffset, max = chars.length(); i < max && currentLine <= visual.line; i++) {
if (currentLine == visual.line) {
if (symbolsOnCurrentVisibleLine >= visual.column) {
int softWrapColumns = softWrapsSymbolsOnCurrentVisibleLine > 0
? symbolsOnCurrentVisibleLine - symbolsOnCurrentLogicalLine : 0;
return new LogicalPosition(
logical.line - softWrapIntroducedLines, symbolsOnCurrentLogicalLine, softWrapIntroducedLines,
linesFromCurrentSoftWrap, softWrapColumns
);
}
}
FoldRegion region = foldingModel.getCollapsedRegionAtOffset(i);
if (region != null && !region.isExpanded()) {
// Assuming that folded region placeholder doesn't contain line feed symbols.
i = region.getEndOffset();
continue;
}
TextChange softWrap = myWraps.get(i);
if (softWrap != null) {
CharSequence softWrapText = softWrap.getText();
for (int j = 0; j < softWrapText.length(); j++) {
if (currentLine == visual.line && symbolsOnCurrentVisibleLine >= visual.column) {
return new LogicalPosition(
logical.line - softWrapIntroducedLines, symbolsOnCurrentLogicalLine, softWrapIntroducedLines,
linesFromCurrentSoftWrap, softWrapsSymbolsOnCurrentVisibleLine - symbolsOnCurrentLogicalLine
);
}
if (softWrapText.charAt(j) == '\n') {
if (currentLine == visual.line) {
return new LogicalPosition(
logical.line - softWrapIntroducedLines, symbolsOnCurrentLogicalLine, softWrapIntroducedLines,
linesFromCurrentSoftWrap, visual.column - symbolsOnCurrentLogicalLine
);
}
else {
softWrapIntroducedLines++;
linesFromCurrentSoftWrap++;
currentLine++;
symbolsOnCurrentVisibleLine = 0;
softWrapsSymbolsOnCurrentVisibleLine = 0;
}
}
else {
symbolsOnCurrentVisibleLine++;
softWrapsSymbolsOnCurrentVisibleLine++;
}
}
}
if (currentLine == visual.line && symbolsOnCurrentVisibleLine >= visual.column) {
return new LogicalPosition(
logical.line - softWrapIntroducedLines, symbolsOnCurrentLogicalLine, softWrapIntroducedLines,
linesFromCurrentSoftWrap, softWrapsSymbolsOnCurrentVisibleLine - symbolsOnCurrentLogicalLine
);
}
char c = chars.charAt(i);
// Check if there is a line break at the document.
if (c == '\n') {
if (currentLine == visual.line) {
int columnToUse = symbolsOnCurrentLogicalLine + visual.column - symbolsOnCurrentVisibleLine;
return new LogicalPosition(
logical.line - softWrapIntroducedLines, columnToUse, softWrapIntroducedLines,
linesFromCurrentSoftWrap, visual.column - columnToUse
);
}
else {
currentLine++;
linesFromCurrentSoftWrap = 0;
symbolsOnCurrentVisibleLine = 0;
symbolsOnCurrentLogicalLine = 0;
softWrapsSymbolsOnCurrentVisibleLine = 0;
}
}
else {
symbolsOnCurrentLogicalLine++;
symbolsOnCurrentVisibleLine++;
}
}
return logical;
}
public int getSoftWrapLineFeedsBefore(VisualPosition position) {
if (myActive > 0 || !isSoftWrappingEnabled() || myWraps.isEmpty()) {
return 0;
}
myActive++;
try {
return doGetSoftWrappedLinesFor(position);
}
finally {
myActive--;
}
}
@SuppressWarnings({"AssignmentToForLoopParameter"})
private int doGetSoftWrappedLinesFor(VisualPosition position) {
// It's assumed that there are two possible cases when visual position may differ from logical - folding and soft wraps.
// The main idea is to start from document offset that corresponds to the top left position of visible editor area and count
// number of occurred soft wraps before the line of given position (avoiding to count soft wraps from folded regions)
CharSequence chars = ((DocumentImpl)myEditor.getDocument()).getCharsNoThreadCheck();
int result = 0;
int processedLinesNumber = 0;
FoldingModel foldingModel = myEditor.getFoldingModel();
int maxLinesToCheck = position.line - myFirstVisibleLine; // Number of the first line that shouldn't be checked.
int charsInRow = 0;
for (int i = myFirstVisibleSymbolOffset, max = chars.length(); i < max && processedLinesNumber < maxLinesToCheck; i++) {
// There is a possible situation that there is a soft wrap at the end of the row denoted by given position.
// We need to avoid counting it then, hence, we stop processing if current line is a line of visual position
// and we checked necessary number of chars.
if (processedLinesNumber == maxLinesToCheck && charsInRow >= position.column) {
break;
}
TextChange softWrap = myWraps.get(i);
if (softWrap != null) {
CharSequence softWrapText = softWrap.getText();
for (int j = 0; j < softWrapText.length(); j++) {
if (softWrapText.charAt(j) == '\n') {
processedLinesNumber++;
charsInRow = -1; // We set this to '-1' assuming that it's incremented in loop's 'update' block
continue;
}
charsInRow += charToVisibleSymbolsNumber(softWrapText.charAt(j));
}
continue;
}
char c = chars.charAt(i);
// Check if there is a line break at the document.
if (c == '\n') {
processedLinesNumber++;
charsInRow = -1; // We set this to '-1' assuming that it's incremented in loop's 'update' block
continue;
}
FoldRegion region = foldingModel.getCollapsedRegionAtOffset(i);
if (region != null && !region.isExpanded()) {
i = region.getEndOffset();
// Assuming that folded region is not represented in more than one line.
charsInRow += region.getPlaceholderText().length() - 1;
continue;
}
charsInRow += charToVisibleSymbolsNumber(c);
}
return result;
}
private int charToVisibleSymbolsNumber(char c) {
if (c != '\t') {
return 1;
}
int result = EditorUtil.getTabSize(myEditor);
return result <= 0 ? 1 : result;
}
private void initIfNecessary() {
if (myInitialized) {
return;
}
myInitialized = true;
ScrollingModel scrollingModel = myEditor.getScrollingModel();
scrollingModel.addVisibleAreaListener(new VisibleAreaListener() {
public void visibleAreaChanged(VisibleAreaEvent e) {
updateVisibleAreaChange(e.getNewRectangle());
}
});
updateVisibleAreaChange(scrollingModel.getVisibleArea());
}
private void updateVisibleAreaChange(@Nullable Rectangle visibleArea) {
if (visibleArea == null || !isSoftWrappingEnabled()) {
return;
}
myActive++;
try {
doUpdateVisibleAreaChange(visibleArea);
}
finally {
myActive--;
}
}
/**
* Notifies current model that visible rectangle is changed in order for it to update the state accordingly.
* <p/>
* This method is introduced mostly for business-logic vs try/finally separation.
*
* @param visibleArea current visible area of the editor managed by the current model
*/
private void doUpdateVisibleAreaChange(@NotNull Rectangle visibleArea) {
// Update information about the first visible line.
LogicalPosition logicalPositionOfVisibleAreaStart = myEditor.xyToLogicalPosition(visibleArea.getLocation());
myFirstVisibleSymbolOffset = myEditor.logicalPositionToOffset(logicalPositionOfVisibleAreaStart);
myFirstVisibleLine = myEditor.logicalToVisualPosition(logicalPositionOfVisibleAreaStart).line;
// Update right edge.
int currentRightEdgeLocation = visibleArea.x + visibleArea.width;
if (myRightEdgeLocation != currentRightEdgeLocation) {
myDataIsDirty = true;
myRightEdgeLocation = currentRightEdgeLocation;
}
}
}
@@ -298,6 +298,7 @@ radio.close.less.frequently.used.files=Close less frequently used files
radio.close.non.modified.files.first=Close non-modified files first
label.when.number.of.opened.editors.exceeds.tab.limit=<html>When number of opened editors exceeds tab limit:</html>
group.virtual.space=Virtual Space
checkbox.use.soft.wraps=Use soft wraps
checkbox.allow.placement.of.caret.after.end.of.line=Allow placement of caret after end of line
checkbox.allow.placement.of.caret.inside.tabs=Allow placement of caret inside tabs
checkbox.show.virtual.space.at.file.bottom=Show virtual space at file bottom