mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
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:
+14
-5
@@ -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 { | <- 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 { | <- 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 { | <- 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);
|
||||
}
|
||||
}
|
||||
+9
@@ -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);
|
||||
|
||||
|
||||
+5
-1
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user