calculate column/offset: optimisation of the no-tabs case

This commit is contained in:
Alexey Kudravtsev
2013-08-27 13:19:36 +04:00
parent c0990c4068
commit 6da30a435f
2 changed files with 86 additions and 60 deletions
@@ -41,9 +41,9 @@ import java.util.Arrays;
import java.util.List;
public class EditorUtil {
private static final Logger LOG = Logger.getInstance("#" + EditorUtil.class.getName());
private EditorUtil() { }
public static int getLastVisualLineColumnNumber(@NotNull Editor editor, final int line) {
@@ -136,14 +136,14 @@ public class EditorUtil {
result += calcColumnNumber(editor, softWrap.getText(), softWrapStartOffset, softWrapEndOffset);
return result;
}
CharSequence editorInfo;
if (editor instanceof EditorImpl) {
editorInfo = ((EditorImpl)editor).dumpState();
}
else {
editorInfo = "editor's class: " + editor.getClass()
+ ", all soft wraps: " + editor.getSoftWrapModel().getSoftWrapsForRange(0, document.getTextLength())
+ ", all soft wraps: " + editor.getSoftWrapModel().getSoftWrapsForRange(0, document.getTextLength())
+ ", fold regions: " + Arrays.toString(editor.getFoldingModel().getAllFoldRegions());
}
LogMessageEx.error(LOG, "Can't calculate last visual column", String.format(
@@ -151,7 +151,7 @@ public class EditorUtil {
+ "the target logical line: %s. Editor info: %s",
line, resultLogLine, resVisStart, resVisEnd, softWraps, editorInfo
));
return resVisEnd.column;
}
@@ -294,36 +294,43 @@ public class EditorUtil {
"Starting calcSoftWrapUnawareOffset(). Target range: [%d; %d), target column number to map: %d, tab size: %d, "
+ "x: %d, current column: %d%n", start, end, columnNumber, tabSize, x, currentColumn[0]));
}
// The main problem in a calculation is that target text may contain tabulation symbols and every such symbol may take different
// number of logical columns to represent. E.g. it takes two columns if tab size is four and current column is two; three columns
// if tab size is four and current column is one etc. So, first of all we check if there are tabulation symbols at the target
// text fragment.
boolean useOptimization = true;
boolean hasNonTabs = false;
boolean hasTabs = false;
int scanEndOffset = Math.min(end, start + columnNumber - currentColumn[0] + 1);
for (int i = start; i < scanEndOffset; i++) {
char c = text.charAt(i);
if (debugBuffer != null) {
debugBuffer.append(String.format("Found symbol '%c' at the offset %d%n", c, i));
}
if (c == '\t') {
hasTabs = true;
if (hasNonTabs) {
useOptimization = false;
break;
boolean hasTabs;
if ((editor instanceof EditorImpl) && !((EditorImpl)editor).hasTabs()) {
hasTabs = false;
useOptimization = true;
}
else {
boolean hasNonTabs = false;
hasTabs = false;
int scanEndOffset = Math.min(end, start + columnNumber - currentColumn[0] + 1);
for (int i = start; i < scanEndOffset; i++) {
char c = text.charAt(i);
if (debugBuffer != null) {
debugBuffer.append(String.format("Found symbol '%c' at the offset %d%n", c, i));
}
if (c == '\t') {
hasTabs = true;
if (hasNonTabs) {
useOptimization = false;
break;
}
}
else {
hasNonTabs = true;
}
}
else {
hasNonTabs = true;
}
}
if (debugBuffer != null) {
debugBuffer.append(String.format("Has tabs: %b, use optimisation: %b%n", hasTabs, useOptimization));
}
// Perform optimized processing if possible. 'Optimized' here means the processing when we exactly know how many logical
// columns are occupied by tabulation symbols.
if (useOptimization) {
@@ -454,16 +461,23 @@ public class EditorUtil {
SoftWrap softWrap = editor.getSoftWrapModel().getSoftWrap(start);
useOptimization = softWrap == null;
}
boolean hasTabs = true;
if (useOptimization) {
boolean hasNonTabs = false;
for (int i = start; i < offset; i++) {
if (text.charAt(i) == '\t') {
if (hasNonTabs) {
useOptimization = false;
break;
if ((editor instanceof EditorImpl) && !((EditorImpl)editor).hasTabs()) {
hasTabs = false;
}
else {
boolean hasNonTabs = false;
for (int i = start; i < offset; i++) {
if (text.charAt(i) == '\t') {
if (hasNonTabs) {
useOptimization = false;
break;
}
}
else {
hasNonTabs = true;
}
} else {
hasNonTabs = true;
}
}
}
@@ -471,27 +485,26 @@ public class EditorUtil {
if (editor == null || useOptimization) {
int shift = 0;
for (int i = start; i < offset; i++) {
char c = text.charAt(i);
if (c == '\n' || c == '\r') {
String editorInfo = editor instanceof EditorImpl ? ". Editor info: " + ((EditorImpl)editor).dumpState() : "";
String documentInfo;
if (text instanceof Dumpable) {
documentInfo = ((Dumpable)text).dumpState();
}
else {
documentInfo = "Text holder class: " + text.getClass();
}
LogMessageEx.error(
LOG, "detected incorrect offset -> column number calculation",
String.format(
"Symbol: '%c', its index: %d, given start: %d, given offset: %d, given tab size: %d. %s%s",
c, i, start, offset, tabSize, documentInfo, editorInfo
)
);
Document document = editor == null ? null : editor.getDocument();
if (document != null && start < offset-1 && document.getLineNumber(start) != document.getLineNumber(offset-1)) {
String editorInfo = editor instanceof EditorImpl ? ". Editor info: " + ((EditorImpl)editor).dumpState() : "";
String documentInfo;
if (text instanceof Dumpable) {
documentInfo = ((Dumpable)text).dumpState();
}
if (c == '\t') {
shift += getTabLength(i + shift - start, tabSize) - 1;
else {
documentInfo = "Text holder class: " + text.getClass();
}
LogMessageEx.error(
LOG, "detected incorrect offset -> column number calculation",
"start: " + start + ", given offset: " + offset+", given tab size: " + tabSize + ". "+documentInfo+ editorInfo);
}
if (hasTabs) {
for (int i = start; i < offset; i++) {
char c = text.charAt(i);
if (c == '\t') {
shift += getTabLength(i + shift - start, tabSize) - 1;
}
}
}
return offset - start + shift;
@@ -580,7 +593,7 @@ public class EditorUtil {
char c = text.charAt(i);
int prevX = x;
switch (c) {
case '\t':
case '\t':
x = nextTabStop(x, editor);
result += columnsNumber(x - prevX, spaceSize);
break;
@@ -666,10 +679,10 @@ public class EditorUtil {
/**
* Delegates to the {@link #calcSurroundingRange(Editor, VisualPosition, VisualPosition)} with the
* {@link CaretModel#getVisualPosition() caret visual position} as an argument.
*
*
* @param editor target editor
* @return surrounding logical positions
* @see #calcSurroundingRange(Editor, VisualPosition, VisualPosition)
* @see #calcSurroundingRange(Editor, VisualPosition, VisualPosition)
*/
public static Pair<LogicalPosition, LogicalPosition> calcCaretLineRange(@NotNull Editor editor) {
return calcSurroundingRange(editor, editor.getCaretModel().getVisualPosition(), editor.getCaretModel().getVisualPosition());
@@ -719,7 +732,7 @@ public class EditorUtil {
}
line = foldEndLine;
}
LogicalPosition second = editor.visualToLogicalPosition(new VisualPosition(end.line, 0));
for (
@@ -739,7 +752,7 @@ public class EditorUtil {
}
line = foldEndLine;
}
if (second.line >= document.getLineCount()) {
second = editor.offsetToLogicalPosition(document.getTextLength());
}
@@ -152,6 +152,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
@NotNull private final EditorComponentImpl myEditorComponent;
@NotNull private final EditorGutterComponentImpl myGutterComponent;
private final TraceableDisposable myTraceableDisposable = new TraceableDisposable(new Throwable());
private volatile boolean hasTabs; // optimisation flag: when editor contains no tabs it is dramatically easier to calculate positions
static {
ComplementaryFontsRegistry.getFontAbleToDisplay(' ', 0, 0, UIManager.getFont("Label.font").getFamily()); // load costly font info
@@ -518,6 +519,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
}
});
}
updateHasTabsFlag(document.getCharsSequence());
}
public static boolean isPresentationMode(Project project) {
@@ -1693,6 +1695,16 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
Point caretLocation = visualPositionToXY(getCaretModel().getVisualPosition());
int scrollOffset = caretLocation.y - myCaretUpdateVShift;
getScrollingModel().scrollVertically(scrollOffset);
updateHasTabsFlag(e.getNewFragment());
}
private void updateHasTabsFlag(CharSequence newChars) {
if (!hasTabs) {
hasTabs = StringUtil.contains(newChars, 0, newChars.length(), '\t');
}
}
public boolean hasTabs() {
return hasTabs;
}
public boolean isScrollToCaret() {
@@ -2474,7 +2486,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
// there then.
VisualPosition selectionStartPosition = getSelectionModel().getSelectionStartPosition();
VisualPosition selectionEndPosition = getSelectionModel().getSelectionEndPosition();
if (selectionStartPosition == null || selectionEndPosition == null || selectionStartPosition.equals(selectionEndPosition)) {
if (selectionStartPosition.equals(selectionEndPosition)) {
return;
}
@@ -2535,7 +2547,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
// there then.
VisualPosition selectionStartPosition = getSelectionModel().getSelectionStartPosition();
VisualPosition selectionEndPosition = getSelectionModel().getSelectionEndPosition();
if (selectionStartPosition == null || selectionEndPosition == null || selectionStartPosition.equals(selectionEndPosition)) {
if (selectionStartPosition.equals(selectionEndPosition)) {
return;
}
@@ -3978,8 +3990,9 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
int x = myGutterComponent.convertX(e.getX());
if (x >= myGutterComponent.getLineNumberAreaOffset() &&
x < myGutterComponent.getLineNumberAreaOffset() + myGutterComponent.getLineNumberAreaWidth()) {
int lineNumberAreaOffset = EditorGutterComponentImpl.getLineNumberAreaOffset();
if (x >= lineNumberAreaOffset &&
x < lineNumberAreaOffset + myGutterComponent.getLineNumberAreaWidth()) {
return EditorMouseEventArea.LINE_NUMBERS_AREA;
}