From c9f641faba93b968a7aaf75dccbe82efb534520d Mon Sep 17 00:00:00 2001 From: Denis Zhdanov Date: Mon, 28 Jun 2010 12:58:51 +0400 Subject: [PATCH] IDEA-53596 Soft wrap for editors 1. 'Home' and 'End' keys typing processing is corrected; 2. Number of minor refactorings are introduced in order to make spell checker plugin happy (green code policy); --- .../injected/editor/EditorWindow.java | 4 +- .../openapi/editor/SoftWrapModel.java | 11 + .../hint/EditorFragmentComponent.java | 2 +- .../editor/actions/EditorActionUtil.java | 313 +++++++++++++----- .../intellij/openapi/editor/ex/EditorEx.java | 2 +- .../openapi/editor/ex/SoftWrapModelEx.java | 19 +- .../openapi/editor/ex/util/EditorUtil.java | 92 ++++- .../impl/EditorGutterComponentImpl.java | 10 +- .../openapi/editor/impl/EditorImpl.java | 49 +-- .../editor/impl/SoftWrapModelImpl.java | 36 +- .../textarea/TextComponentSoftWrapModel.java | 14 + .../openapi/vcs/ex/LineStatusTracker.java | 2 +- 12 files changed, 408 insertions(+), 146 deletions(-) diff --git a/platform/lang-impl/src/com/intellij/injected/editor/EditorWindow.java b/platform/lang-impl/src/com/intellij/injected/editor/EditorWindow.java index c47b95fb2640..a107bb1d9691 100644 --- a/platform/lang-impl/src/com/intellij/injected/editor/EditorWindow.java +++ b/platform/lang-impl/src/com/intellij/injected/editor/EditorWindow.java @@ -398,8 +398,8 @@ public class EditorWindow implements EditorEx, UserDataHolderEx { myDelegate.setBackgroundColor(color); } - public Color getBackroundColor() { - return myDelegate.getBackroundColor(); + public Color getBackgroundColor() { + return myDelegate.getBackgroundColor(); } public int getMaxWidthInRange(final int startOffset, final int endOffset) { diff --git a/platform/platform-api/src/com/intellij/openapi/editor/SoftWrapModel.java b/platform/platform-api/src/com/intellij/openapi/editor/SoftWrapModel.java index 67bd7828d84c..ae447fe5ee2f 100644 --- a/platform/platform-api/src/com/intellij/openapi/editor/SoftWrapModel.java +++ b/platform/platform-api/src/com/intellij/openapi/editor/SoftWrapModel.java @@ -18,6 +18,8 @@ package com.intellij.openapi.editor; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.List; + /** * Defines contract for the services that bring 'soft wrapping' to the editor. *

@@ -85,6 +87,15 @@ public interface SoftWrapModel { @Nullable TextChange getSoftWrap(int offset); + /** + * Allows to ask current model about all soft wraps registered for the given document line. + * + * @param documentLine target document line + * @return all soft wraps registered for the given document line + */ + @NotNull + List getSoftWrapsForLine(int documentLine); + /** * Notifies current model that target document is about to be changed at location identified by the given visual position. *

diff --git a/platform/platform-impl/src/com/intellij/codeInsight/hint/EditorFragmentComponent.java b/platform/platform-impl/src/com/intellij/codeInsight/hint/EditorFragmentComponent.java index 41decf45c2c5..2da522b899e7 100644 --- a/platform/platform-impl/src/com/intellij/codeInsight/hint/EditorFragmentComponent.java +++ b/platform/platform-impl/src/com/intellij/codeInsight/hint/EditorFragmentComponent.java @@ -160,7 +160,7 @@ public class EditorFragmentComponent extends JPanel { int endLine, boolean showFolding, boolean showGutter) { final EditorEx editorEx = (EditorEx)editor; - final Color old = editorEx.getBackroundColor(); + final Color old = editorEx.getBackgroundColor(); Color backColor = getBackgroundColor(editor); editorEx.setBackgroundColor(backColor); EditorFragmentComponent fragmentComponent = new EditorFragmentComponent(editorEx, startLine, endLine, diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/actions/EditorActionUtil.java b/platform/platform-impl/src/com/intellij/openapi/editor/actions/EditorActionUtil.java index aff8e62e1515..841a909719df 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/actions/EditorActionUtil.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/actions/EditorActionUtil.java @@ -24,6 +24,11 @@ */ package com.intellij.openapi.editor.actions; +import com.intellij.ide.ui.customization.CustomActionsSchema; +import com.intellij.openapi.actionSystem.ActionGroup; +import com.intellij.openapi.actionSystem.ActionManager; +import com.intellij.openapi.actionSystem.ActionPlaces; +import com.intellij.openapi.actionSystem.ActionPopupMenu; import com.intellij.openapi.editor.*; import com.intellij.openapi.editor.event.EditorMouseEvent; import com.intellij.openapi.editor.event.EditorMouseEventArea; @@ -31,17 +36,20 @@ import com.intellij.openapi.editor.ex.EditorEx; import com.intellij.openapi.editor.ex.util.EditorUtil; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Key; -import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.EditorPopupHandler; -import com.intellij.ide.ui.customization.CustomActionsSchema; import java.awt.*; import java.awt.event.MouseEvent; +import java.util.List; public class EditorActionUtil { protected static final Object EDIT_COMMAND_GROUP = Key.create("EditGroup"); protected static final Object DELETE_COMMAND_GROUP = Key.create("DeleteGroup"); + private EditorActionUtil() { + } + public static void scrollRelatively(Editor editor, int lineShift) { if (lineShift != 0) { editor.getScrollingModel().scrollVertically( @@ -69,10 +77,10 @@ public class EditorActionUtil { int columnShift, int lineShift, boolean withSelection) { - Rectangle viewRect = editor.getScrollingModel().getVisibleArea(); + Rectangle visibleArea = editor.getScrollingModel().getVisibleArea(); VisualPosition pos = editor.getCaretModel().getVisualPosition(); Point caretLocation = editor.visualPositionToXY(pos); - int caretVShift = caretLocation.y - viewRect.y; + int caretVShift = caretLocation.y - visibleArea.y; editor.getCaretModel().moveCaretRelatively(columnShift, lineShift, withSelection, false, false); @@ -82,6 +90,7 @@ public class EditorActionUtil { editor.getScrollingModel().scrollVertically(caretLocation2.y - caretVShift); } + @SuppressWarnings({"AssignmentToForLoopParameter"}) public static void indentLine(Project project, Editor editor, int lineNumber, int indent) { EditorSettings editorSettings = editor.getSettings(); Document document = editor.getDocument(); @@ -172,21 +181,21 @@ public class EditorActionUtil { char current = text.charAt(offset); char next = offset + 1 < text.length() ? text.charAt(offset + 1) : 0; - final boolean firstIsIdentifiePart = Character.isJavaIdentifierPart(prev); + final boolean firstIsIdentifierPart = Character.isJavaIdentifierPart(prev); final boolean secondIsIdentifierPart = Character.isJavaIdentifierPart(current); - if (firstIsIdentifiePart && !secondIsIdentifierPart) { + if (firstIsIdentifierPart && !secondIsIdentifierPart) { return true; } if (isCamel) { - if (firstIsIdentifiePart && secondIsIdentifierPart && + if (firstIsIdentifierPart && secondIsIdentifierPart && (Character.isLowerCase(prev) && Character.isUpperCase(current) || prev != '_' && current == '_' || Character.isUpperCase(prev) && Character.isUpperCase(current) && Character.isLowerCase(next))) { return true; } } - return !Character.isWhitespace(prev) && !firstIsIdentifiePart && + return !Character.isWhitespace(prev) && !firstIsIdentifierPart && (Character.isWhitespace(current) || secondIsIdentifierPart); } @@ -198,98 +207,197 @@ public class EditorActionUtil { LogicalPosition blockSelectionStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : caretModel.getLogicalPosition(); - - int columnNumber = editor.getCaretModel().getLogicalPosition().column; - int lineNumber = editor.getCaretModel().getLogicalPosition().line; - - VisualPosition visCaret = editor.getCaretModel().getVisualPosition(); - while (lineNumber >= 0 && editor.logicalToVisualPosition(new LogicalPosition(lineNumber, 0)).line == visCaret.line) lineNumber--; - lineNumber++; - EditorSettings editorSettings = editor.getSettings(); - if (!editorSettings.isSmartHome()) { - LogicalPosition pos = new LogicalPosition(lineNumber, 0); - editor.getCaretModel().moveToLogicalPosition(pos); + int logCaretLine = caretModel.getLogicalPosition().line; + VisualPosition currentVisCaret = caretModel.getVisualPosition(); + VisualPosition caretLogLineStartVis = editor.offsetToVisualPosition(document.getLineStartOffset(logCaretLine)); + + if (currentVisCaret.line > caretLogLineStartVis.line) { + // Caret is located not at the first visual line of soft-wrapped logical line. + moveCaretToStartOfSoftWrappedLine(editor, currentVisCaret); setupSelection(editor, isWithSelection, selectionStart, blockSelectionStart); editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); return; } - int offset = editor.getCaretModel().getOffset(); - if (lineNumber >= document.getLineCount() || offset >= document.getTextLength()) { - int newColNumber = 0; - if (columnNumber == 0) { - newColNumber = findSmartIndent(editor, offset); + // Skip folded lines. + int logLineToUse = logCaretLine - 1; + while (logLineToUse >= 0 && editor.offsetToVisualPosition(document.getLineEndOffset(logLineToUse)).line == currentVisCaret.line) { + logLineToUse--; + } + logLineToUse++; + + if (logLineToUse >= document.getLineCount()) { + editor.getCaretModel().moveToVisualPosition(new VisualPosition(logLineToUse, 0)); + } + else if (logLineToUse == logCaretLine) { + int line = currentVisCaret.line; + int column = 0; + if (currentVisCaret.column == 0) { + column = findSmartIndentColumn(editor, currentVisCaret.line); } - LogicalPosition pos = new LogicalPosition(lineNumber, newColNumber); - editor.getCaretModel().moveToLogicalPosition(pos); - - setupSelection(editor, isWithSelection, selectionStart, blockSelectionStart); - editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); - return; + caretModel.moveToVisualPosition(new VisualPosition(line, column)); } - - int start = findFirstNonspaceOffsetOnTheLine(document, lineNumber); - int lineEnd = document.getLineEndOffset(lineNumber); - if (lineNumber > 0 && lineEnd == start && columnNumber == 0) { - int newColNumber = findSmartIndent(editor, offset); - LogicalPosition pos = new LogicalPosition(lineNumber, newColNumber); - editor.getCaretModel().moveToLogicalPosition(pos); - setupSelection(editor, isWithSelection, selectionStart, blockSelectionStart); - editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); - return; + else { + LogicalPosition logLineEndLog = editor.offsetToLogicalPosition(document.getLineEndOffset(logLineToUse)); + VisualPosition logLineEndVis = editor.logicalToVisualPosition(logLineEndLog); + if (logLineEndLog.isOnSoftWrappedLine()) { + moveCaretToStartOfSoftWrappedLine(editor, logLineEndVis); + } + else { + int line = logLineEndVis.line; + int column = 0; + if (currentVisCaret.column == 0 && editorSettings.isSmartHome()) { + findSmartIndentColumn(editor, line); + } + caretModel.moveToVisualPosition(new VisualPosition(line, column)); + } } - int lineStart = document.getLineStartOffset(lineNumber); - int newOffset = lineStart; - if (start < offset || columnNumber == 0) { - newOffset = start; - } - editor.getCaretModel().moveToOffset(newOffset); - editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); - setupSelection(editor, isWithSelection, selectionStart, blockSelectionStart); + editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); } - private static int findSmartIndent(Editor editor, int offset) { - int nonSpaceLineNumber = findFirstNonspaceLineBack(editor.getDocument(), offset); - if (nonSpaceLineNumber >= 0) { - int newOffset = findFirstNonspaceOffsetOnTheLine(editor.getDocument(), nonSpaceLineNumber); - return editor.offsetToLogicalPosition(newOffset).column; + private static void moveCaretToStartOfSoftWrappedLine(Editor editor, VisualPosition currentVisual) { + CaretModel caretModel = editor.getCaretModel(); + int line = currentVisual.line; + int column = 0; + + if (currentVisual.column == 0) { + line--; + int nonSpaceColumn = findFirstNonSpaceColumnOnTheLine(editor, line); + column = nonSpaceColumn >= 0 ? nonSpaceColumn : 0; + } + else { + int nonSpaceColumn = findFirstNonSpaceColumnOnTheLine(editor, currentVisual.line); + if (nonSpaceColumn > 0 /* current visual line is not empty */ && nonSpaceColumn != currentVisual.column) { + column = nonSpaceColumn; + } + } + + caretModel.moveToVisualPosition(new VisualPosition(line, column)); + } + + private static int findSmartIndentColumn(Editor editor, int visualLine) { + for (int i = visualLine; i >= 0; i--) { + int column = findFirstNonSpaceColumnOnTheLine(editor, visualLine); + if (column >= 0) { + return column; + } } return 0; } - private static int findFirstNonspaceLineBack(Document document, int offset) { - CharSequence text = document.getCharsSequence(); - int foundOffset = offset - 1; - for (; foundOffset > 0; foundOffset--) { - char c = text.charAt(foundOffset); - if (c != ' ' && c != '\t' && c != '\r' && c != '\n') { - break; + /** + * Tries to find visual column that points to the first non-white space symbol at the visual line at the given editor. + * + * @param editor target editor + * @param visualLineNumber target visual line + * @return visual column that points to the first non-white space symbol at the target visual line if the one exists; + * '-1' otherwise + */ + public static int findFirstNonSpaceColumnOnTheLine(Editor editor, int visualLineNumber) { + Document document = editor.getDocument(); + VisualPosition visLine = new VisualPosition(visualLineNumber, 0); + int logLine = editor.visualToLogicalPosition(visLine).line; + int logLineStartOffset = document.getLineStartOffset(logLine); + int logLineEndOffset = document.getLineEndOffset(logLine); + LogicalPosition logLineStart = editor.offsetToLogicalPosition(logLineStartOffset); + VisualPosition visLineStart = editor.logicalToVisualPosition(logLineStart); + + boolean softWrapIntroducedLine = visLineStart.line != visualLineNumber; + if (!softWrapIntroducedLine) { + int offset = findFirstNonSpaceOffsetInRange(document.getCharsSequence(), logLineStartOffset, logLineEndOffset); + if (offset >= 0) { + return EditorUtil.calcColumnNumber(editor, document.getCharsSequence(), logLineStartOffset, offset); + } + else { + return -1; } } - if (foundOffset == -1) { - return -1; - } - else { - return document.getLineNumber(foundOffset); + + int lineFeedsToSkip = visualLineNumber - visLineStart.line; + List softWraps = editor.getSoftWrapModel().getSoftWrapsForLine(logLine); + for (TextChange softWrap : softWraps) { + int softWrapLineFeedsNumber = StringUtil.countNewLines(softWrap.getText()); + + if (softWrapLineFeedsNumber < lineFeedsToSkip) { + lineFeedsToSkip -= softWrapLineFeedsNumber; + continue; + } + + // Point to the first non-white space symbol at the target soft wrap visual line or to the first non-white space symbol + // of document line that follows it if possible. + CharSequence softWrapText = softWrap.getText(); + int softWrapTextLength = softWrapText.length(); + boolean skip = true; + for (int j = 0; j < softWrapTextLength; j++) { + if (softWrapText.charAt(j) == '\n') { + skip = --lineFeedsToSkip > 0; + continue; + } + if (skip) { + continue; + } + + int nextSoftWrapLineFeedOffset = StringUtil.indexOf(softWrapText, '\n', j, softWrapTextLength); + + int end = findFirstNonSpaceOffsetInRange(softWrapText, j, softWrapTextLength); + if (end >= 0) { + // Non space symbol is contained at soft wrap text after offset that corresponds to the target visual line start. + if (nextSoftWrapLineFeedOffset < 0 || end < nextSoftWrapLineFeedOffset) { + return EditorUtil.calcColumnNumber(editor, softWrapText, j, end); + } + else { + return -1; + } + } + + if (nextSoftWrapLineFeedOffset >= 0) { + // There are soft wrap-introduced visual lines after the target one + return -1; + } + + end = findFirstNonSpaceOffsetInRange(document.getCharsSequence(), softWrap.getStart(), logLineEndOffset); + if (end >= 0) { + // Width of soft wrap virtual text that is located at the target visual line. + int result = EditorUtil.calcColumnNumber(editor, softWrapText, j, softWrapTextLength); + result += EditorUtil.calcColumnNumber(editor, document.getCharsSequence(), softWrap.getStart(), end); + return result; + } + else { + return -1; + } + } } + return -1; } - public static int findFirstNonspaceOffsetOnTheLine(Document document, int lineNumber) { + public static int findFirstNonSpaceOffsetOnTheLine(Document document, int lineNumber) { int lineStart = document.getLineStartOffset(lineNumber); int lineEnd = document.getLineEndOffset(lineNumber); - CharSequence text = document.getCharsSequence(); - int start = lineStart; - for (; start < lineEnd; start++) { + int result = findFirstNonSpaceOffsetInRange(document.getCharsSequence(), lineStart, lineEnd); + return result >= 0 ? result : lineEnd; + } + + /** + * Tries to find non white space symbol at the given range at the given document. + * + * @param text text to be inspected + * @param start target start offset (inclusive) + * @param end target end offset (exclusive) + * @return index of the first non-white space character at the given document at the given range if the one is found; + * '-1' otherwise + */ + public static int findFirstNonSpaceOffsetInRange(CharSequence text, int start, int end) { + for (; start < end; start++) { char c = text.charAt(start); if (c != ' ' && c != '\t') { return start; } } - return lineEnd; + return -1; } public static void moveCaretToLineEnd(Editor editor, boolean isWithSelection) { @@ -309,10 +417,33 @@ public class EditorActionUtil { editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); return; } - VisualPosition visCaret = editor.getCaretModel().getVisualPosition(); - visCaret = new VisualPosition(visCaret.line, EditorUtil.getLastVisualLineColumnNumber(editor, visCaret.line)); + VisualPosition currentVisualCaret = editor.getCaretModel().getVisualPosition(); + VisualPosition visualEndOfLineWithCaret + = new VisualPosition(currentVisualCaret.line, EditorUtil.getLastVisualLineColumnNumber(editor, currentVisualCaret.line)); - LogicalPosition logLineEnd = editor.visualToLogicalPosition(visCaret); + // There is a possible case that the caret is already located at the visual end of line and the line is soft wrapped. + // We want to move the caret to the end of the next visual line then. + if (currentVisualCaret.equals(visualEndOfLineWithCaret)) { + LogicalPosition logical = editor.visualToLogicalPosition(visualEndOfLineWithCaret); + int offset = editor.logicalPositionToOffset(logical); + if (offset < editor.getDocument().getTextLength()) { + SoftWrapModel softWrapModel = editor.getSoftWrapModel(); + TextChange softWrap = softWrapModel.getSoftWrap(offset); + if (softWrap == null) { + // Same offset may correspond to positions on different visual lines in case of soft wraps presence + // (all soft-wrap introduced virtual text is mapped to the same offset as the first document symbol after soft wrap). + // Hence, we check for soft wraps presence at two offsets. + softWrap = softWrapModel.getSoftWrap(offset + 1); + } + if (softWrap != null) { + int line = currentVisualCaret.line + 1; + int column = EditorUtil.getLastVisualLineColumnNumber(editor, line); + visualEndOfLineWithCaret = new VisualPosition(line, column); + } + } + } + + LogicalPosition logLineEnd = editor.visualToLogicalPosition(visualEndOfLineWithCaret); int offset = editor.logicalPositionToOffset(logLineEnd); lineNumber = logLineEnd.line; int newOffset = offset; @@ -325,11 +456,17 @@ public class EditorActionUtil { newOffset = i; } - if (newOffset == editor.getCaretModel().getOffset()) { - newOffset = offset; + // Check if there are white space symbols between the current caret position and visual line end. + if (newOffset == offset) { + caretModel.moveToVisualPosition(visualEndOfLineWithCaret); + } + else { + if (newOffset == caretModel.getOffset()) { + newOffset = offset; + } + caretModel.moveToOffset(newOffset); } - editor.getCaretModel().moveToOffset(newOffset); editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); setupSelection(editor, isWithSelection, selectionStart, blockSelectionStart); @@ -416,9 +553,9 @@ public class EditorActionUtil { public static void moveCaretPageUp(Editor editor, boolean isWithSelection) { ((EditorEx)editor).stopOptimizedScrolling(); int lineHeight = editor.getLineHeight(); - Rectangle viewRect = editor.getScrollingModel().getVisibleArea(); - int linesIncrement = viewRect.height / lineHeight; - editor.getScrollingModel().scrollVertically(viewRect.y - viewRect.y % lineHeight - linesIncrement * lineHeight); + Rectangle visibleArea = editor.getScrollingModel().getVisibleArea(); + int linesIncrement = visibleArea.height / lineHeight; + editor.getScrollingModel().scrollVertically(visibleArea.y - visibleArea.y % lineHeight - linesIncrement * lineHeight); int lineShift = -linesIncrement; editor.getCaretModel().moveCaretRelatively(0, lineShift, isWithSelection, editor.isColumnMode(), true); } @@ -426,11 +563,11 @@ public class EditorActionUtil { public static void moveCaretPageDown(Editor editor, boolean isWithSelection) { ((EditorEx)editor).stopOptimizedScrolling(); int lineHeight = editor.getLineHeight(); - Rectangle viewRect = editor.getScrollingModel().getVisibleArea(); - int linesIncrement = viewRect.height / lineHeight; - int allowedBottom = ((EditorEx)editor).getContentSize().height - viewRect.height; + Rectangle visibleArea = editor.getScrollingModel().getVisibleArea(); + int linesIncrement = visibleArea.height / lineHeight; + int allowedBottom = ((EditorEx)editor).getContentSize().height - visibleArea.height; editor.getScrollingModel().scrollVertically( - Math.min(allowedBottom, viewRect.y - viewRect.y % lineHeight + linesIncrement * lineHeight)); + Math.min(allowedBottom, visibleArea.y - visibleArea.y % lineHeight + linesIncrement * lineHeight)); editor.getCaretModel().moveCaretRelatively(0, linesIncrement, isWithSelection, editor.isColumnMode(), true); } @@ -442,9 +579,9 @@ public class EditorActionUtil { LogicalPosition blockSelectionStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : caretModel.getLogicalPosition(); - Rectangle viewRect = editor.getScrollingModel().getVisibleArea(); - int lineNumber = viewRect.y / lineHeight; - if (viewRect.y % lineHeight > 0) { + Rectangle visibleArea = editor.getScrollingModel().getVisibleArea(); + int lineNumber = visibleArea.y / lineHeight; + if (visibleArea.y % lineHeight > 0) { lineNumber++; } VisualPosition pos = new VisualPosition(lineNumber, editor.getCaretModel().getVisualPosition().column); @@ -460,8 +597,8 @@ public class EditorActionUtil { LogicalPosition blockSelectionStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : caretModel.getLogicalPosition(); - Rectangle viewRect = editor.getScrollingModel().getVisibleArea(); - int lineNumber = (viewRect.y + viewRect.height) / lineHeight - 1; + Rectangle visibleArea = editor.getScrollingModel().getVisibleArea(); + int lineNumber = (visibleArea.y + visibleArea.height) / lineHeight - 1; VisualPosition pos = new VisualPosition(lineNumber, editor.getCaretModel().getVisualPosition().column); editor.getCaretModel().moveToVisualPosition(pos); setupSelection(editor, isWithSelection, selectionStart, blockSelectionStart); diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/ex/EditorEx.java b/platform/platform-impl/src/com/intellij/openapi/editor/ex/EditorEx.java index 9860fb8bdefc..d57e1cfb1dc5 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/ex/EditorEx.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/ex/EditorEx.java @@ -107,7 +107,7 @@ public interface EditorEx extends Editor { void setFontSize(int fontSize); - Color getBackroundColor(); + Color getBackgroundColor(); void setBackgroundColor(Color color); diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/ex/SoftWrapModelEx.java b/platform/platform-impl/src/com/intellij/openapi/editor/ex/SoftWrapModelEx.java index a819f812971e..e67356537f17 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/ex/SoftWrapModelEx.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/ex/SoftWrapModelEx.java @@ -43,6 +43,16 @@ public interface SoftWrapModelEx extends SoftWrapModel { @NotNull LogicalPosition adjustLogicalPosition(@NotNull LogicalPosition defaultLogical, @NotNull VisualPosition visual); + /** + * Asks current model to adjust logical position for the given document offset. + * + * @param defaultLogical default logical position that corresponds to the given document offset + * @param offset target editor document offset + * @return logical position for the given editor document offset + */ + @NotNull + LogicalPosition adjustLogicalPosition(LogicalPosition defaultLogical, int offset); + /** * Asks current model to adjust visual position that corresponds to the given logical position if necessary. *

@@ -55,15 +65,6 @@ public interface SoftWrapModelEx extends SoftWrapModel { @NotNull VisualPosition adjustVisualPosition(@NotNull LogicalPosition logical, @NotNull VisualPosition defaultVisual); - /** - * Asks current model to calculate logical position for the given editor document offset. - * - * @param offset target editor document offset - * @return logical position for the given editor document offset - */ - @NotNull - LogicalPosition offsetToLogicalPosition(int offset); - /** * @return unmodifiable collection of soft wraps currently registered within the current model */ diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/ex/util/EditorUtil.java b/platform/platform-impl/src/com/intellij/openapi/editor/ex/util/EditorUtil.java index 4106dfa79e74..fb514e5382fd 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/ex/util/EditorUtil.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/ex/util/EditorUtil.java @@ -17,55 +17,115 @@ package com.intellij.openapi.editor.ex.util; import com.intellij.openapi.application.Result; import com.intellij.openapi.application.WriteAction; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.EditorModificationUtil; -import com.intellij.openapi.editor.LogicalPosition; -import com.intellij.openapi.editor.VisualPosition; +import com.intellij.openapi.editor.*; import com.intellij.openapi.editor.colors.EditorColorsScheme; import com.intellij.openapi.editor.ex.EditorEx; import com.intellij.openapi.editor.impl.ComplementaryFontsRegistry; import com.intellij.openapi.editor.impl.FontInfo; import com.intellij.openapi.editor.impl.IterationState; +import com.intellij.openapi.util.text.StringUtil; import java.awt.*; +import java.util.List; public class EditorUtil { private EditorUtil() { } public static int getLastVisualLineColumnNumber(Editor editor, int line) { + Document document = editor.getDocument(); + int lastLine = document.getLineCount() - 1; + if (lastLine < 0) { + return 0; + } + + // Filter all lines that are not shown because of collapsed folding region. VisualPosition visStart = new VisualPosition(line, 0); LogicalPosition logStart = editor.visualToLogicalPosition(visStart); int lastLogLine = logStart.line; - while (lastLogLine < editor.getDocument().getLineCount() - 1) { + while (lastLogLine < document.getLineCount() - 1) { logStart = new LogicalPosition(logStart.line + 1, logStart.column); VisualPosition tryVisible = editor.logicalToVisualPosition(logStart); if (tryVisible.line != visStart.line) break; lastLogLine = logStart.line; } - int lastLine = editor.getDocument().getLineCount() - 1; - if (lastLine < 0) { - return 0; + int resultLogLine = Math.min(lastLogLine, lastLine); + VisualPosition resVisStart = editor.offsetToVisualPosition(document.getLineStartOffset(resultLogLine)); + VisualPosition resVisEnd = editor.offsetToVisualPosition(document.getLineEndOffset(resultLogLine)); + + // Target logical line is not soft wrap affected. + if (resVisStart.line == resVisEnd.line) { + return resVisEnd.column; } - return editor.offsetToVisualPosition(editor.getDocument().getLineEndOffset(Math.min(lastLogLine, lastLine))).column; + + int visualLinesToSkip = line - resVisStart.line; + List softWraps = editor.getSoftWrapModel().getSoftWrapsForLine(resultLogLine); + for (int i = 0; i < softWraps.size(); i++) { + TextChange softWrap = softWraps.get(i); + int softWrapLineFeeds = StringUtil.countNewLines(softWrap.getText()); + if (softWrapLineFeeds < visualLinesToSkip) { + visualLinesToSkip -= softWrapLineFeeds; + continue; + } + + // Target visual column is the one just before line feed introduced by the next line feed. + if (softWrapLineFeeds == visualLinesToSkip) { + if (i >= softWraps.size() - 1) { + return resVisEnd.column; + } + // We need to find visual column for line feed of the next soft wrap. + TextChange nextSoftWrap = softWraps.get(i + 1); + int result = editor.offsetToVisualPosition(nextSoftWrap.getStart() - 1).column; + + // We need to add '1' because current column points to the last symbol before the next soft wrap; + result++; + + int lineFeedIndex = StringUtil.indexOf(nextSoftWrap.getText(), '\n'); + result += calcColumnNumber(editor, nextSoftWrap.getText(), 0, lineFeedIndex); + return result; + } + + // Target visual column is the one before line feed introduced by the current soft wrap. + int softWrapStartOffset = 0; + int softWrapEndOffset = 0; + int softWrapTextLength = softWrap.getText().length(); + while (visualLinesToSkip-- > 0) { + softWrapStartOffset = softWrapEndOffset + 1; + if (softWrapStartOffset >= softWrapTextLength) { + assert false; + return resVisEnd.column; + } + softWrapEndOffset = StringUtil.indexOf(softWrap.getText(), '\n', softWrapStartOffset, softWrapTextLength); + if (softWrapEndOffset < 0) { + assert false; + return resVisEnd.column; + } + } + int result = editor.offsetToVisualPosition(softWrap.getStart() - 1).column; + result += calcColumnNumber(editor, softWrap.getText(), softWrapStartOffset, softWrapEndOffset); + return result; + } + + assert false; + return resVisEnd.column; } public static float calcVerticalScrollProportion(Editor editor) { - Rectangle viewRect = editor.getScrollingModel().getVisibleAreaOnScrollingFinished(); - if (viewRect.height == 0) { + Rectangle viewArea = editor.getScrollingModel().getVisibleAreaOnScrollingFinished(); + if (viewArea.height == 0) { return 0; } LogicalPosition pos = editor.getCaretModel().getLogicalPosition(); Point location = editor.logicalPositionToXY(pos); - return (location.y - viewRect.y) / (float) viewRect.height; + return (location.y - viewArea.y) / (float) viewArea.height; } public static void setVerticalScrollProportion(Editor editor, float proportion) { - Rectangle viewRect = editor.getScrollingModel().getVisibleArea(); + Rectangle viewArea = editor.getScrollingModel().getVisibleArea(); LogicalPosition caretPosition = editor.getCaretModel().getLogicalPosition(); Point caretLocation = editor.logicalPositionToXY(caretPosition); int yPos = caretLocation.y; - yPos -= viewRect.height * proportion; + yPos -= viewArea.height * proportion; editor.getScrollingModel().scrollVertically(yPos); } @@ -163,6 +223,10 @@ public class EditorUtil { return tabSize - colNumber % tabSize; } + public static int calcColumnNumber(Editor editor, CharSequence text, int start, int offset) { + return calcColumnNumber(editor, text, start, offset, getTabSize(editor)); + } + public static int calcColumnNumber(Editor editor, CharSequence text, int start, int offset, int tabSize) { boolean useOptimization = true; boolean hasNonTabs = false; diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorGutterComponentImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorGutterComponentImpl.java index 8160cff3ef07..b540e898fe13 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorGutterComponentImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorGutterComponentImpl.java @@ -239,7 +239,7 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse else { g.setColor(Color.white); int x = getWhitespaceSeparatorOffset() - 1; - UIUtil.drawVDottedLine((Graphics2D)g, x, clip.y, clip.y + clip.height, myEditor.getBackroundColor(), getFoldingColor(false)); + UIUtil.drawVDottedLine((Graphics2D)g, x, clip.y, clip.y + clip.height, myEditor.getBackgroundColor(), getFoldingColor(false)); } } @@ -693,7 +693,7 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse int lineX = getWhitespaceSeparatorOffset(); paintBackground(g, clip, getFoldingAreaOffset(), getFoldingAreaWidth()); - g.setColor(myEditor.getBackroundColor()); + g.setColor(myEditor.getBackgroundColor()); g.fillRect(lineX, clip.y, getFoldingAreaWidth(), clip.height); paintCaretRowBackground(g, lineX, getFoldingAnchorWidth()); @@ -705,7 +705,7 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse if (!isFoldingOutlineShown()) return; Rectangle clip = g.getClipBounds(); - UIUtil.drawVDottedLine(g, getWhitespaceSeparatorOffset(), clip.y, clip.y + clip.height, myEditor.getBackroundColor(), getFoldingColor(false)); + UIUtil.drawVDottedLine(g, getWhitespaceSeparatorOffset(), clip.y, clip.y + clip.height, myEditor.getBackgroundColor(), getFoldingColor(false)); int anchorX = getFoldingAreaOffset(); int width = getFoldingAnchorWidth(); @@ -814,7 +814,7 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse int[] yPoints = {y, y, y + baseHeight, y + height, y + baseHeight}; if (paintBackground) { - g.setColor(myEditor.getBackroundColor()); + g.setColor(myEditor.getBackgroundColor()); g.fillPolygon(xPoints, yPoints, 5); } @@ -850,7 +850,7 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse boolean active, boolean paintBackground) { if (paintBackground) { - g.setColor(myEditor.getBackroundColor()); + g.setColor(myEditor.getBackgroundColor()); g.fillRect(anchorX, y, width, width); } else { diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java index b34d3e458373..356202ea343a 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java @@ -959,13 +959,15 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi } public LogicalPosition offsetToLogicalPosition(int offset, boolean softWrapAware) { - if (softWrapAware) { - return mySoftWrapModel.offsetToLogicalPosition(offset); - } - int line = calcLogicalLineNumber(offset, false); int column = calcColumnNumber(offset, line, false); - return new LogicalPosition(line, column); + LogicalPosition position = new LogicalPosition(line, column); + if (softWrapAware) { + return mySoftWrapModel.adjustLogicalPosition(position, offset); + } + else { + return position; + } } @NotNull @@ -1284,8 +1286,8 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi return; } - Rectangle viewRect = getScrollingModel().getVisibleArea(); - if (viewRect == null) { + Rectangle visibleArea = getScrollingModel().getVisibleArea(); + if (visibleArea == null) { return; } @@ -1374,7 +1376,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi return myScheme.getDefaultForeground(); } - public Color getBackroundColor() { + public Color getBackgroundColor() { if (myForcedBackground != null) return myForcedBackground; return getBackgroundIgnoreForced(); @@ -1382,7 +1384,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi private Color getBackgroundColor(final TextAttributes attributes) { final Color attrColor = attributes.getBackgroundColor(); - return attrColor == myScheme.getDefaultBackground() ? getBackroundColor() : attrColor; + return attrColor == myScheme.getDefaultBackground() ? getBackgroundColor() : attrColor; } private Color getBackgroundIgnoreForced() { @@ -1501,7 +1503,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi @SuppressWarnings({"StatementWithEmptyBody"}) private void paintBackgrounds(Graphics g, Rectangle clip) { - Color defaultBackground = getBackroundColor(); + Color defaultBackground = getBackgroundColor(); g.setColor(defaultBackground); g.fillRect(clip.x, clip.y, clip.width, clip.height); @@ -2140,7 +2142,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi g.setColor(savedColor); } else if (effectType == EffectType.BOLD_DOTTED_LINE) { - final Color bgColor = getBackroundColor(); + final Color bgColor = getBackgroundColor(); final int dottedAt = SystemInfo.isMac ? y : y + 1; UIUtil.drawBoldDottedLine((Graphics2D)g, xStart, xEnd, dottedAt, bgColor, effectColor, false); } @@ -2598,12 +2600,14 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi throw new IndexOutOfBoundsException("Wrong offset: " + offset + " textLength: " + textLength); } + int lineIndex = myDocument.getLineNumber(offset); + LOG.assertTrue(lineIndex >= 0 && lineIndex < myDocument.getLineCount()); + if (softWrapAware) { - return mySoftWrapModel.offsetToLogicalPosition(offset).line; + int column = calcColumnNumber(offset, lineIndex, false); + return mySoftWrapModel.adjustLogicalPosition(new LogicalPosition(lineIndex, column), offset).line; } else { - int lineIndex = myDocument.getLineNumber(offset); - LOG.assertTrue(lineIndex >= 0 && lineIndex < myDocument.getLineCount()); return lineIndex; } } @@ -2615,14 +2619,17 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi public int calcColumnNumber(int offset, int lineIndex, boolean softWrapAware) { if (myDocument.getTextLength() == 0) return 0; + CharSequence text = myDocument.getCharsSequence(); + int start = myDocument.getLineStartOffset(lineIndex); + if (start == offset) return 0; + int column = EditorUtil.calcColumnNumber(this, text, start, offset, EditorUtil.getTabSize(this)); + if (softWrapAware) { - return mySoftWrapModel.offsetToLogicalPosition(offset).column; + int line = calcLogicalLineNumber(offset, false); + return mySoftWrapModel.adjustLogicalPosition(new LogicalPosition(line, column), offset).column; } else { - CharSequence text = myDocument.getCharsSequence(); - int start = myDocument.getLineStartOffset(lineIndex); - if (start == offset) return 0; - return EditorUtil.calcColumnNumber(this, text, start, offset, EditorUtil.getTabSize(this)); + return column; } } @@ -3148,7 +3155,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi } else { Color background = myScheme.getColor(EditorColors.CARET_ROW_COLOR); - if (background == null) background = getBackroundColor(); + if (background == null) background = getBackgroundColor(); g.setXORMode(background); g.fillRect(x, y, myWidth, lineHeight - 1); @@ -3668,6 +3675,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi return r[0]; } + @Nullable public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) { return null; } @@ -3707,6 +3715,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi return r; } + @Nullable public TextHitInfo getLocationOffset(int x, int y) { if (composedText != null) { Point p = getContentComponent().getLocationOnScreen(); diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/SoftWrapModelImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/SoftWrapModelImpl.java index 8af6f3f0600d..ef66bff68aa5 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/SoftWrapModelImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/SoftWrapModelImpl.java @@ -116,6 +116,32 @@ public class SoftWrapModelImpl implements SoftWrapModelEx { return i >= 0 ? myWraps.get(i) : null; } + @Override + @NotNull + public List getSoftWrapsForLine(int documentLine) { + Document document = myEditor.getDocument(); + int start = document.getLineStartOffset(documentLine); + int end = document.getLineEndOffset(documentLine); + + int i = getSoftWrapIndex(start); + if (i < 0) { + i = -i - 1; + } + + List result = null; + for (; i < myWraps.size(); i++) { + TextChange softWrap = myWraps.get(i); + if (softWrap.getStart() >= end) { + break; + } + if (result == null) { + result = new ArrayList(); + } + result.add(softWrap); + } + return result == null ? Collections.emptyList() : result; + } + /** * Tries to find index of the target soft wrap stored at {@link #myWraps} collection. 'Target' soft wrap is the one * that starts at the given offset. @@ -453,21 +479,21 @@ public class SoftWrapModelImpl implements SoftWrapModelEx { } @NotNull - public LogicalPosition offsetToLogicalPosition(int offset) { + public LogicalPosition adjustLogicalPosition(LogicalPosition defaultLogical, int offset) { if (myActive > 0 || !isSoftWrappingEnabled()) { - return myEditor.offsetToLogicalPosition(offset, false); + return defaultLogical; } myActive++; try { - return doOffsetToLogicalPosition(offset); + return offsetToLogicalPosition(offset); } finally { myActive--; } } @SuppressWarnings({"AssignmentToForLoopParameter"}) - private LogicalPosition doOffsetToLogicalPosition(int offset) { + private LogicalPosition offsetToLogicalPosition(int offset) { DocumentImpl document = (DocumentImpl)myEditor.getDocument(); CharSequence chars = document.getCharsNoThreadCheck(); @@ -759,7 +785,7 @@ public class SoftWrapModelImpl implements SoftWrapModelEx { } private int toVisualColumnSymbolsNumber(CharSequence text, int start, int end) { - return EditorUtil.calcColumnNumber(myEditor, text, start, end, EditorUtil.getTabSize(myEditor)); + return EditorUtil.calcColumnNumber(myEditor, text, start, end); } private class Context { diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/textarea/TextComponentSoftWrapModel.java b/platform/platform-impl/src/com/intellij/openapi/editor/textarea/TextComponentSoftWrapModel.java index 39e0fa40ed8c..682b7a75a17c 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/textarea/TextComponentSoftWrapModel.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/textarea/TextComponentSoftWrapModel.java @@ -19,6 +19,10 @@ import com.intellij.openapi.editor.SoftWrapModel; import com.intellij.openapi.editor.TextChange; import com.intellij.openapi.editor.VisualPosition; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collections; +import java.util.List; /** * @author Denis Zhdanov @@ -26,14 +30,24 @@ import org.jetbrains.annotations.NotNull; */ public class TextComponentSoftWrapModel implements SoftWrapModel { + @Override public boolean isSoftWrappingEnabled() { return false; } + @Nullable + @Override public TextChange getSoftWrap(int offset) { return null; } + @NotNull + @Override + public List getSoftWrapsForLine(int documentLine) { + return Collections.emptyList(); + } + + @Override public void beforeDocumentChange(@NotNull VisualPosition position) { } } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/ex/LineStatusTracker.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/ex/LineStatusTracker.java index 36ef084e473d..ee988600c8d7 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/ex/LineStatusTracker.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/ex/LineStatusTracker.java @@ -784,7 +784,7 @@ public class LineStatusTracker { JComponent toolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.FILEHISTORY_VIEW_TOOLBAR, group, true).getComponent(); - final Color background = ((EditorEx)editor).getBackroundColor(); + final Color background = ((EditorEx)editor).getBackgroundColor(); final Color foreground = editor.getColorsScheme().getColor(EditorColors.CARET_COLOR); toolbar.setBackground(background);