diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/PostHighlightingPass.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/PostHighlightingPass.java index 90a25a5703b9..506bd4f5c3a2 100644 --- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/PostHighlightingPass.java +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/PostHighlightingPass.java @@ -359,9 +359,8 @@ public class PostHighlightingPass extends TextEditorHighlightingPass { @Nullable private HighlightInfo processField(final PsiField field, final PsiIdentifier identifier, ProgressIndicator progress) { - if (isImplicitUsage(field, progress)) return null; if (field.hasModifierProperty(PsiModifier.PRIVATE)) { - if (!myRefCountHolder.isReferenced(field)) { + if (!myRefCountHolder.isReferenced(field) && !isImplicitUsage(field, progress)) { if (HighlightUtil.isSerializationImplicitlyUsedField(field)) { return null; } @@ -397,6 +396,9 @@ public class PostHighlightingPass extends TextEditorHighlightingPass { return info; } } + else if (isImplicitUsage(field, progress)) { + return null; + } else if (!myRefCountHolder.isReferenced(field) && weAreSureThereAreNoUsages(field, progress)) { return formatUnusedSymbolHighlightInfo("field.is.not.used", field, "fields", myDeadCodeKey, myDeadCodeInfoType); } diff --git a/java/java-impl/src/com/intellij/refactoring/inline/InlineLocalHandler.java b/java/java-impl/src/com/intellij/refactoring/inline/InlineLocalHandler.java index 09c9382485c7..3915c8a696ae 100644 --- a/java/java-impl/src/com/intellij/refactoring/inline/InlineLocalHandler.java +++ b/java/java-impl/src/com/intellij/refactoring/inline/InlineLocalHandler.java @@ -32,6 +32,7 @@ import com.intellij.psi.*; import com.intellij.psi.controlFlow.DefUseUtil; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.searches.ReferencesSearch; +import com.intellij.psi.tree.IElementType; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiUtil; import com.intellij.refactoring.HelpID; @@ -247,8 +248,8 @@ public class InlineLocalHandler extends JavaInlineActionHandler { parent = parent.getParent(); } - if (parent instanceof PsiAssignmentExpression && element == ((PsiAssignmentExpression)parent).getLExpression() || - parent instanceof PsiPrefixExpression || parent instanceof PsiPostfixExpression ) { + if (parent instanceof PsiAssignmentExpression && element == ((PsiAssignmentExpression)parent).getLExpression() + || isUnaryWriteExpression(parent)) { EditorColorsManager manager = EditorColorsManager.getInstance(); final TextAttributes writeAttributes = manager.getGlobalScheme().getAttributes(EditorColors.WRITE_SEARCH_RESULT_ATTRIBUTES); @@ -262,6 +263,17 @@ public class InlineLocalHandler extends JavaInlineActionHandler { return false; } + private static boolean isUnaryWriteExpression(PsiElement parent) { + IElementType tokenType = null; + if (parent instanceof PsiPrefixExpression) { + tokenType = ((PsiPrefixExpression)parent).getOperationTokenType(); + } + if (parent instanceof PsiPostfixExpression) { + tokenType = ((PsiPostfixExpression)parent).getOperationTokenType(); + } + return tokenType == JavaTokenType.PLUSPLUS || tokenType == JavaTokenType.MINUSMINUS; + } + private static boolean isSameDefinition(final PsiElement def, final PsiExpression defToInline) { if (def instanceof PsiLocalVariable) return defToInline.equals(((PsiLocalVariable)def).getInitializer()); final PsiElement parent = def.getParent(); diff --git a/java/java-tests/testData/refactoring/inlineLocal/NonWriteUnaryExpression.java b/java/java-tests/testData/refactoring/inlineLocal/NonWriteUnaryExpression.java new file mode 100644 index 000000000000..017afbea98db --- /dev/null +++ b/java/java-tests/testData/refactoring/inlineLocal/NonWriteUnaryExpression.java @@ -0,0 +1,7 @@ +class Test { + + void get(){ + boolean toInline = false; + if (!toInline) return; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/refactoring/inlineLocal/NonWriteUnaryExpression.java.after b/java/java-tests/testData/refactoring/inlineLocal/NonWriteUnaryExpression.java.after new file mode 100644 index 000000000000..74b5a396279c --- /dev/null +++ b/java/java-tests/testData/refactoring/inlineLocal/NonWriteUnaryExpression.java.after @@ -0,0 +1,6 @@ +class Test { + + void get(){ + if (!false) return; + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/refactoring/inline/InlineLocalTest.java b/java/java-tests/testSrc/com/intellij/refactoring/inline/InlineLocalTest.java index 5e4f8f12b8c1..54e15ee65e52 100644 --- a/java/java-tests/testSrc/com/intellij/refactoring/inline/InlineLocalTest.java +++ b/java/java-tests/testSrc/com/intellij/refactoring/inline/InlineLocalTest.java @@ -62,6 +62,10 @@ public class InlineLocalTest extends LightCodeInsightTestCase { doTest(false); } + public void testNonWriteUnaryExpression() throws Exception { + doTest(true); + } + public void testNewExpression() throws Exception { doTest(false); } diff --git a/platform/lang-impl/src/com/intellij/application/options/editor/EditorOptionsPanel.form b/platform/lang-impl/src/com/intellij/application/options/editor/EditorOptionsPanel.form index df44f0ec4488..a4b285923591 100644 --- a/platform/lang-impl/src/com/intellij/application/options/editor/EditorOptionsPanel.form +++ b/platform/lang-impl/src/com/intellij/application/options/editor/EditorOptionsPanel.form @@ -2,7 +2,7 @@
- + @@ -64,7 +64,7 @@ - + @@ -76,7 +76,7 @@ - + @@ -84,7 +84,7 @@ - + @@ -92,13 +92,22 @@ - + + + + + + + + + + diff --git a/platform/lang-impl/src/com/intellij/application/options/editor/EditorOptionsPanel.java b/platform/lang-impl/src/com/intellij/application/options/editor/EditorOptionsPanel.java index 0aca55584554..a7e782dfd520 100644 --- a/platform/lang-impl/src/com/intellij/application/options/editor/EditorOptionsPanel.java +++ b/platform/lang-impl/src/com/intellij/application/options/editor/EditorOptionsPanel.java @@ -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()); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/generation/SelfManagingCommenter.java b/platform/lang-impl/src/com/intellij/codeInsight/generation/SelfManagingCommenter.java index dd3cb376b879..23b51e1137a6 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/generation/SelfManagingCommenter.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/generation/SelfManagingCommenter.java @@ -26,8 +26,6 @@ import org.jetbrains.annotations.Nullable; * @author Maxim.Mossienko */ public interface SelfManagingCommenter { - ExtensionPointName EP_NAME = ExtensionPointName.create("com.intellij.selfManagingCommenter"); - @Nullable T createLineCommentingState(int startLine, int endLine, @NotNull Document document, @NotNull PsiFile file); @Nullable T createBlockCommentingState(int selectionStart, int selectionEnd, @NotNull Document document, @NotNull PsiFile file); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/config/LazyEditor.java b/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/config/LazyEditor.java index 5102e1aceb86..bf949f547111 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/config/LazyEditor.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/config/LazyEditor.java @@ -87,6 +87,11 @@ class LazyEditor extends UserDataHolderBase implements Editor { return getEditor().getCaretModel(); } + @NotNull + public SoftWrapModel getSoftWrapModel() { + return getEditor().getSoftWrapModel(); + } + @NotNull public EditorSettings getSettings() { return getEditor().getSettings(); diff --git a/platform/lang-impl/src/com/intellij/ide/actions/NewElementAction.java b/platform/lang-impl/src/com/intellij/ide/actions/NewElementAction.java index 53ae26c04ce1..04014024f4ae 100644 --- a/platform/lang-impl/src/com/intellij/ide/actions/NewElementAction.java +++ b/platform/lang-impl/src/com/intellij/ide/actions/NewElementAction.java @@ -19,50 +19,87 @@ package com.intellij.ide.actions; import com.intellij.ide.IdeBundle; import com.intellij.ide.IdeView; import com.intellij.openapi.actionSystem.*; -import com.intellij.openapi.project.Project; import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.popup.JBPopupFactory; import com.intellij.openapi.ui.popup.ListPopup; +import com.intellij.openapi.util.Condition; +import org.jetbrains.annotations.Nullable; -public class NewElementAction extends AnAction implements DumbAware { - - public void actionPerformed(final AnActionEvent e) { - showPopup(e.getDataContext()); +/** + * @author Konstantin Bulenkov + */ +@SuppressWarnings({"MethodMayBeStatic"}) +public class NewElementAction extends AnAction implements DumbAware { + public void actionPerformed(final AnActionEvent event) { + showPopup(event.getDataContext()); } - protected void showPopup(DataContext dataContext) { - final ListPopup popup = JBPopupFactory.getInstance() - .createActionGroupPopup(getPopupTitle(), - getGroup(), - dataContext, - false, false, false, - null, -1, LangDataKeys.PRESELECT_NEW_ACTION_CONDITION.getData(dataContext)); + protected void showPopup(DataContext context) { + createPopup(context).showInBestPositionFor(context); + } - popup.showInBestPositionFor(dataContext); + protected ListPopup createPopup(DataContext dataContext) { + return JBPopupFactory.getInstance() + .createActionGroupPopup(getPopupTitle(), + getGroup(dataContext), + dataContext, + isShowNumbers(), + isShowDisabledActions(), + isHonorActionMnemonics(), + getDisposeCallback(), + getMaxRowCount(), + getPreselectActionCondition(dataContext)); + } + + protected int getMaxRowCount() { + return -1; + } + + @Nullable + protected Condition getPreselectActionCondition(DataContext dataContext) { + return LangDataKeys.PRESELECT_NEW_ACTION_CONDITION.getData(dataContext); + } + + @Nullable + protected Runnable getDisposeCallback() { + return null; + } + + protected boolean isHonorActionMnemonics() { + return false; + } + + protected boolean isShowDisabledActions() { + return false; + } + + protected boolean isShowNumbers() { + return false; } protected String getPopupTitle() { return IdeBundle.message("title.popup.new.element"); } - public void update(AnActionEvent event){ - Presentation presentation = event.getPresentation(); - DataContext dataContext = event.getDataContext(); - Project project = PlatformDataKeys.PROJECT.getData(dataContext); + public void update(AnActionEvent e){ + final Presentation presentation = e.getPresentation(); + final DataContext context = e.getDataContext(); + final Project project = PlatformDataKeys.PROJECT.getData(context); if (project == null) { presentation.setEnabled(false); return; } - IdeView ideView = LangDataKeys.IDE_VIEW.getData(dataContext); + final IdeView ideView = LangDataKeys.IDE_VIEW.getData(context); if (ideView == null) { presentation.setEnabled(false); return; } - presentation.setEnabled(!ActionGroupUtil.isGroupEmpty(getGroup(), event)); + presentation.setEnabled(!ActionGroupUtil.isGroupEmpty(getGroup(context), e)); } - private static ActionGroup getGroup() { + protected ActionGroup getGroup(DataContext dataContext) { return (ActionGroup)ActionManager.getInstance().getAction(IdeActions.GROUP_WEIGHING_NEW); } } 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 f567ca62d3f7..c47b95fb2640 100644 --- a/platform/lang-impl/src/com/intellij/injected/editor/EditorWindow.java +++ b/platform/lang-impl/src/com/intellij/injected/editor/EditorWindow.java @@ -203,6 +203,11 @@ public class EditorWindow implements EditorEx, UserDataHolderEx { return myDelegate.getScrollingModel(); } + @NotNull + public SoftWrapModel getSoftWrapModel() { + return myDelegate.getSoftWrapModel(); + } + @NotNull public EditorSettings getSettings() { return myDelegate.getSettings(); diff --git a/platform/lang-impl/src/com/intellij/refactoring/copy/CopyFilesOrDirectoriesHandler.java b/platform/lang-impl/src/com/intellij/refactoring/copy/CopyFilesOrDirectoriesHandler.java index a50e339a5767..81a5e1243c36 100644 --- a/platform/lang-impl/src/com/intellij/refactoring/copy/CopyFilesOrDirectoriesHandler.java +++ b/platform/lang-impl/src/com/intellij/refactoring/copy/CopyFilesOrDirectoriesHandler.java @@ -78,17 +78,26 @@ public class CopyFilesOrDirectoriesHandler implements CopyHandlerDelegate { CopyFilesOrDirectoriesDialog dialog = new CopyFilesOrDirectoriesDialog(elements, defaultTargetDirectory, project, false); dialog.show(); if (dialog.isOK()) { + final String newName = elements.length == 1 ? dialog.getNewName() : null; final PsiManager psiManager = PsiManager.getInstance(project); try { for (PsiElement element : elements) { - psiManager.checkMove(element, dialog.getTargetDirectory()); + PsiFileSystemItem psiElement = (PsiFileSystemItem)element; + if (newName != null) { + if (!psiElement.isDirectory()) { + psiElement = (PsiFileSystemItem)psiElement.copy(); + psiElement.setName(newName); + } else { + dialog.getTargetDirectory().checkCreateSubdirectory(newName); + } + } + psiManager.checkMove(psiElement, dialog.getTargetDirectory()); } } catch (IncorrectOperationException e) { CommonRefactoringUtil.showErrorHint(project, null, e.getMessage(), CommonBundle.getErrorTitle(), null); return; } - String newName = elements.length == 1 ? dialog.getNewName() : null; copyImpl(elements, newName, dialog.getTargetDirectory(), false); } } diff --git a/platform/platform-api/src/com/intellij/execution/process/OSProcessHandler.java b/platform/platform-api/src/com/intellij/execution/process/OSProcessHandler.java index f0c855c30a02..a242024e8aaf 100644 --- a/platform/platform-api/src/com/intellij/execution/process/OSProcessHandler.java +++ b/platform/platform-api/src/com/intellij/execution/process/OSProcessHandler.java @@ -275,18 +275,21 @@ public class OSProcessHandler extends ProcessHandler { } public void readAvailable(char[] buffer) throws IOException { + int fairCount = 0; while (myReader.ready()) { int n = myReader.read(buffer); - if (n > 0) { - int start = 0; - int end = 0; - while (start < n) { - while (end < n && buffer[end++] != '\n'); + if (n <= 0) break; - myNotificationQueue.offer(new String(buffer, start, end - start)); - start = end; - } + int start = 0; + int end = 0; + while (start < n) { + while (end < n && buffer[end++] != '\n'); + + myNotificationQueue.offer(new String(buffer, start, end - start)); + start = end; } + + if (++fairCount > 10) return; } if (isProcessTerminated()) { diff --git a/platform/platform-api/src/com/intellij/openapi/actionSystem/ActionGroup.java b/platform/platform-api/src/com/intellij/openapi/actionSystem/ActionGroup.java index 50f815e465d3..930e75f04e10 100644 --- a/platform/platform-api/src/com/intellij/openapi/actionSystem/ActionGroup.java +++ b/platform/platform-api/src/com/intellij/openapi/actionSystem/ActionGroup.java @@ -32,6 +32,13 @@ import java.util.HashSet; public abstract class ActionGroup extends AnAction implements DumbAware { private boolean myPopup; private final PropertyChangeSupport myChangeSupport = new PropertyChangeSupport(this); + public static final ActionGroup EMPTY_GROUP = new ActionGroup() { + @NotNull + @Override + public AnAction[] getChildren(@Nullable AnActionEvent e) { + return EMPTY_ARRAY; + } + }; private Set mySecondaryActions; @@ -145,4 +152,4 @@ public abstract class ActionGroup extends AnAction implements DumbAware { } } } -} \ No newline at end of file +} diff --git a/platform/platform-api/src/com/intellij/openapi/editor/CaretModel.java b/platform/platform-api/src/com/intellij/openapi/editor/CaretModel.java index 2376cf3c1588..ab9d0f8b5fce 100644 --- a/platform/platform-api/src/com/intellij/openapi/editor/CaretModel.java +++ b/platform/platform-api/src/com/intellij/openapi/editor/CaretModel.java @@ -96,8 +96,14 @@ public interface CaretModel { */ void removeCaretListener(CaretListener listener); + /** + * @return document offset for the start of the logical line where caret is located + */ int getVisualLineStart(); + /** + * @return document offset for the end of the logical line where caret is located + */ int getVisualLineEnd(); TextAttributes getTextAttributes(); diff --git a/platform/platform-api/src/com/intellij/openapi/editor/Editor.java b/platform/platform-api/src/com/intellij/openapi/editor/Editor.java index 510f587821ce..dde1ec98456f 100644 --- a/platform/platform-api/src/com/intellij/openapi/editor/Editor.java +++ b/platform/platform-api/src/com/intellij/openapi/editor/Editor.java @@ -111,6 +111,14 @@ public interface Editor extends UserDataHolder { */ @NotNull CaretModel getCaretModel(); + /** + * Returns the soft wrap model for the document, which can be used to get information about soft wraps registered + * for the editor document at the moment and provides basic management functions for them. + * + * @return the soft wrap model instance + */ + @NotNull SoftWrapModel getSoftWrapModel(); + /** * Returns the editor settings for this editor instance. Changes to these settings affect * only the current editor instance. diff --git a/platform/platform-api/src/com/intellij/openapi/editor/EditorModificationUtil.java b/platform/platform-api/src/com/intellij/openapi/editor/EditorModificationUtil.java index db72cbb5091e..c90a8ffb1d6c 100644 --- a/platform/platform-api/src/com/intellij/openapi/editor/EditorModificationUtil.java +++ b/platform/platform-api/src/com/intellij/openapi/editor/EditorModificationUtil.java @@ -83,9 +83,12 @@ public class EditorModificationUtil { editor.getCaretModel().moveToOffset(selectionModel.getSelectionStart()); } + // There is a possible case that particular soft wraps become hard wraps if the caret is located at soft wrap-introduced virtual + // space, hence, we need to give editor a chance to react accordingly. + editor.getSoftWrapModel().beforeDocumentChange(editor.getCaretModel().getVisualPosition()); int oldOffset = editor.getCaretModel().getOffset(); - String filler = calcStringToFillVitualSpace(editor); + String filler = calcStringToFillVirtualSpace(editor); if (filler.length() > 0) { s = filler + s; } @@ -231,7 +234,7 @@ public class EditorModificationUtil { return columnNumber - lineEndColumnNumber; } - public static String calcStringToFillVitualSpace(Editor editor) { + public static String calcStringToFillVirtualSpace(Editor editor) { int afterLineEnd = calcAfterLineEnd(editor); if (afterLineEnd > 0) { final Project project = editor.getProject(); diff --git a/platform/platform-api/src/com/intellij/openapi/editor/EditorSettings.java b/platform/platform-api/src/com/intellij/openapi/editor/EditorSettings.java index 452f49646df9..b3403b120c61 100644 --- a/platform/platform-api/src/com/intellij/openapi/editor/EditorSettings.java +++ b/platform/platform-api/src/com/intellij/openapi/editor/EditorSettings.java @@ -96,6 +96,8 @@ public interface EditorSettings { void setRefrainFromScrolling(boolean b); boolean isIndentGuidesShown(); - void setIndentGuidesShown(boolean val); + + boolean isUseSoftWraps(); + void setUseSoftWraps(boolean use); } diff --git a/platform/platform-api/src/com/intellij/openapi/editor/LogicalPosition.java b/platform/platform-api/src/com/intellij/openapi/editor/LogicalPosition.java index 611673124e10..ff62e540b587 100644 --- a/platform/platform-api/src/com/intellij/openapi/editor/LogicalPosition.java +++ b/platform/platform-api/src/com/intellij/openapi/editor/LogicalPosition.java @@ -21,6 +21,11 @@ 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. + *

+ * Logical position may store additional parameters that define its mapping to {@link VisualPosition}. Rationale is that + * single logical (line; column) pair matches soft wrap-introduced virtual space, i.e. different visual positions + * correspond to the same logical position. It's convenient to store exact visual location details within the logical + * position in order to relief further {@code 'logical position' -> 'visual position'} mapping. * * @see Editor#offsetToLogicalPosition(int) * @see Editor#logicalPositionToOffset(LogicalPosition) @@ -34,29 +39,126 @@ public class LogicalPosition implements Comparable { public final int line; public final int column; + /** + * Identifies if current logical position may be correctly mapped to visual position. E.g. we can define properties like + * {@link #softWrapLines}, {@link #softWrapColumnDiff} etc during {@code 'visual position' -> 'logical position'} conversion + * in order to be able to easy match it back to visual position. + */ + public final boolean visualPositionAware; + + /** + * Number of virtual soft wrap-introduced lines before the visual position that corresponds to the current logical position. + * + * @see #visualPositionAware + */ + public final int softWrapLines; + + /** + * Number of virtual soft lines introduced by the current soft wrap before the visual position that corresponds + * to the current logical position. + *

+ * This value is assumed to be not greater than {@link #softWrapLines} all the time. + * + * @see #visualPositionAware + */ + public final int linesFromActiveSoftWrap; + + /** + * Number to add to the {@link #column logical column} in order to get soft wrap-introduced visual column offset. + * + * @see #visualPositionAware + */ + public final int softWrapColumnDiff; + + /** + * Number of folded line feeds before the current position. + * + * @see #visualPositionAware + */ + public final int foldedLines; + + /** + * Number to add to the {@link #column logical column} in order to get folding-introduced visual column offset. + * + * @see #visualPositionAware + */ + public final int foldingColumnDiff; + public LogicalPosition(int line, int column) { + this(line, column, 0, 0, 0, 0, 0, false); + } + + public LogicalPosition(int line, int column, int softWrapLines, int linesFromActiveSoftWrap, int softWrapColumnDiff, + int foldedLines, int foldingColumnDiff) + { + this(line, column, softWrapLines, linesFromActiveSoftWrap, softWrapColumnDiff, foldedLines, foldingColumnDiff, true); + } + + private LogicalPosition(int line, int column, int softWrapLines, int linesFromActiveSoftWrap, int softWrapColumnDiff, int foldedLines, + int foldingColumnDiff, boolean visualPositionAware) + { + assert linesFromActiveSoftWrap <= softWrapLines; + this.line = line; this.column = column; + this.softWrapLines = softWrapLines; + this.linesFromActiveSoftWrap = linesFromActiveSoftWrap; + this.softWrapColumnDiff = softWrapColumnDiff; + this.foldedLines = foldedLines; + this.foldingColumnDiff = foldingColumnDiff; + this.visualPositionAware = visualPositionAware; + } + + /** + * Allows to answer if current position points to soft wrap-introduced visual line. + * + * @return true if current position points to soft wrap-introduced visual line; false otherwise + */ + public boolean isOnSoftWrappedLine() { + return softWrapColumnDiff != 0; + } + + /** + * Builds visual position based on a state of the current logical position. + *

+ * Such visual position is considered to make sense only if current logical position + * is {@link #visualPositionAware visual position aware}. + * + * @return visual position based on a state of the current logical position + */ + public VisualPosition toVisualPosition() { + return new VisualPosition(line + softWrapLines - foldedLines, column + softWrapColumnDiff + foldingColumnDiff); } 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 + && linesFromActiveSoftWrap == logicalPosition.linesFromActiveSoftWrap + && softWrapColumnDiff == logicalPosition.softWrapColumnDiff && foldedLines == logicalPosition.foldedLines + && foldingColumnDiff == logicalPosition.foldingColumnDiff; } public int hashCode() { - return 29 * line + column; + int result = 29 * line + column; + result = result * 29 + softWrapLines; + result = 29 * result + softWrapColumnDiff; + result = 29 * result + foldedLines; + return 29 * result + foldingColumnDiff; } @NonNls public String toString() { - return "LogicalPosition: line = " + line + " column = " + column; + return "LogicalPosition: line=" + line + " column=" + column + "; visual position aware=" + visualPositionAware + + " soft wrap: lines=" + softWrapLines + " (active=" + linesFromActiveSoftWrap + ") columns diff=" + softWrapColumnDiff + + "; folding: lines = " + foldedLines + " columns diff=" + foldingColumnDiff; } 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 softWrapColumnDiff - position.softWrapColumnDiff; } } diff --git a/platform/platform-api/src/com/intellij/openapi/editor/SoftWrapModel.java b/platform/platform-api/src/com/intellij/openapi/editor/SoftWrapModel.java new file mode 100644 index 000000000000..67bd7828d84c --- /dev/null +++ b/platform/platform-api/src/com/intellij/openapi/editor/SoftWrapModel.java @@ -0,0 +1,97 @@ +/* + * 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 org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Defines contract for the services that bring 'soft wrapping' to the editor. + *

+ * 'Soft wrap' 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 'view' facility. + *

+ * Example: + *

+ * Raw text (as stored at file system) + *

+ *

+ *     public class Test {                                         | <- right margin
+ *                                                                 |
+ *         public void baz() {                                     |
+ *             foo("test1", "test2", "test3", "test4", "test5", "test6");
+ *         }                                                       |
+ *                                                                 |
+ *         public void foo(Object ... args) {                      |
+ *         }                                                       |
+ *     }                                                           |
+ *      
+ *

+ * The same text with soft wraps as shown to end-user at editor + *

+ *

+ *     public class Test {                                         | <- right margin
+ *                                                                 |
+ *         public void baz() {                                     |
+ *             foo("test1", "test2", "test3", "test4", "test5",    |
+ *                 "test6");                                       |
+ *         }                                                       |
+ *                                                                 |
+ *         public void foo(Object ... args) {                      |
+ *         }                                                       |
+ *     }                                                           |
+ *      
+ *

+ * 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 "test6" at example above), that soft wraps becomes + * 'hard wrap', i.e. virtual changes introduced by it are flushed to the underlying document. + *

+ * Note: soft wrap is assumed to provide as user-friendly indentation for those wrapped line as possible + * (note that "test6" at example below is aligned to the parameters start). + *

+ * 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 'soft wrap' feature is enabled. + * + * @return true if 'soft wraps' are enabled; false otherwise + */ + boolean isSoftWrappingEnabled(); + + /** + * 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; null otherwise + */ + @Nullable + TextChange getSoftWrap(int offset); + + /** + * Notifies current model that target document is about to be changed at location identified by the given visual position. + *

+ * Primary purpose of this method is to perform {@code 'soft wrap' -> 'hard wrap'} conversion if the user types in virtual + * soft wraps-introduced space. + * + * @param position position where the document is to be modified + */ + void beforeDocumentChange(@NotNull VisualPosition position); +} diff --git a/platform/platform-api/src/com/intellij/openapi/editor/TextChange.java b/platform/platform-api/src/com/intellij/openapi/editor/TextChange.java new file mode 100644 index 000000000000..062275c6aab8 --- /dev/null +++ b/platform/platform-api/src/com/intellij/openapi/editor/TextChange.java @@ -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. + *

+ * Thread-safe. + * + * @author Denis Zhdanov + * @since May 31, 2010 12:26:51 PM + */ +public class TextChange { + + private final AtomicReference myChars = new AtomicReference(); + private final CharSequence myText; + private final int myStart; + private final int myEnd; + + /** + * Shorthand for creating {@link TextChange} with the given arguments where 'end index' has the same value as + * 'start index'. + * + * @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 TextChange 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 [start; end) + * 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. + *

+ * 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. + *

+ * Note: returned number may be either positive or not. For example it may be negative for 'remove' + * or 'replace' 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); + } +} diff --git a/platform/platform-api/src/com/intellij/openapi/ui/popup/PopupChooserBuilder.java b/platform/platform-api/src/com/intellij/openapi/ui/popup/PopupChooserBuilder.java index 79f95d34bb93..30b5d03577c9 100644 --- a/platform/platform-api/src/com/intellij/openapi/ui/popup/PopupChooserBuilder.java +++ b/platform/platform-api/src/com/intellij/openapi/ui/popup/PopupChooserBuilder.java @@ -25,6 +25,7 @@ import com.intellij.ui.ListScrollingUtil; import com.intellij.ui.speedSearch.ListWithFilter; import com.intellij.ui.treeStructure.treetable.TreeTable; import com.intellij.util.Function; +import com.intellij.util.ui.ButtonlessScrollBarUI; import com.intellij.util.ui.UIUtil; import com.intellij.util.ui.tree.TreeUtil; import org.jetbrains.annotations.Nls; @@ -185,7 +186,6 @@ public class PopupChooserBuilder { } } - myChooserComponent.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); (list != null ? list : myChooserComponent).addMouseListener(new MouseAdapter() { @Override @@ -217,6 +217,9 @@ public class PopupChooserBuilder { } scrollPane.getViewport().setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + ((JComponent)scrollPane.getViewport().getView()).setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); + + scrollPane.getVerticalScrollBar().setUI(ButtonlessScrollBarUI.createMini()); if (myChooserComponent instanceof ListWithFilter) { contentPane.add(myChooserComponent, BorderLayout.CENTER); diff --git a/platform/platform-api/src/com/intellij/util/ui/ButtonlessScrollBarUI.java b/platform/platform-api/src/com/intellij/util/ui/ButtonlessScrollBarUI.java new file mode 100644 index 000000000000..cebffce7158f --- /dev/null +++ b/platform/platform-api/src/com/intellij/util/ui/ButtonlessScrollBarUI.java @@ -0,0 +1,210 @@ +/* + * 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. + */ + +/* + * @author max + */ +package com.intellij.util.ui; + +import com.intellij.openapi.util.Disposer; +import com.intellij.ui.LightColors; + +import javax.swing.*; +import javax.swing.plaf.basic.BasicScrollBarUI; +import java.awt.*; +import java.awt.event.AdjustmentEvent; +import java.awt.event.AdjustmentListener; + +public class ButtonlessScrollBarUI extends BasicScrollBarUI { + + private static final Color GRADIENT_LIGHT = new SameColor(0xfb); + private static final Color GRADIENT_DARK = new SameColor(0xd7); + private static final Color GRADIENT_THUMB_BORDER = new SameColor(0xc9); + private static final Color PLAIN_THUMB_FILL = new SameColor(0xe5); + private static final Color PLAIN_THUMB_BORDER = new SameColor(0xd0); + private static final Color TRACK_BACKGROUND = LightColors.SLIGHTLY_GRAY; + private static final Color TRACK_BORDER = new SameColor(230); + + private final boolean myIsMini; + private final AdjustmentListener myAdjustmentListener; + private final Animator myAnimator; + + private int myAnimationColorShift = 0; + + private ButtonlessScrollBarUI(boolean isMini) { + myIsMini = isMini; + myAdjustmentListener = new AdjustmentListener() { + @Override + public void adjustmentValueChanged(AdjustmentEvent e) { + myAnimator.reset(); + myAnimator.resume(); + } + }; + + myAnimator = new Animator("Adjustment fadeout", myIsMini ? 5 : 10, myIsMini ? 300 : 500, false, 1, 0) { + @Override + public void paintNow(float frame, float totalFrames, float cycle) { + myAnimationColorShift = (int)((totalFrames - frame) * 3); + scrollbar.repaint(); + } + }; + } + + public static BasicScrollBarUI createNormal() { + return new ButtonlessScrollBarUI(false); + } + + public static BasicScrollBarUI createMini() { + return new ButtonlessScrollBarUI(true); + } + + @Override + public void installUI(JComponent c) { + if (myIsMini) { + c.putClientProperty("JComponent.sizeVariant", "mini"); + } + + super.installUI(c); + } + + @Override + protected void installListeners() { + super.installListeners(); + scrollbar.addAdjustmentListener(myAdjustmentListener); + } + + @Override + protected void uninstallListeners() { + super.uninstallListeners(); + scrollbar.removeAdjustmentListener(myAdjustmentListener); + Disposer.dispose(myAnimator); + } + + @Override + protected void paintTrack(Graphics g, JComponent c, Rectangle trackBounds) { + g.setColor(TRACK_BACKGROUND); + g.fillRect(trackBounds.x, trackBounds.y, trackBounds.width, trackBounds.height); + + g.setColor(TRACK_BORDER); + if (isVertical()) { + g.drawLine(trackBounds.x, trackBounds.y, trackBounds.x, trackBounds.y + trackBounds.height); + } + else { + g.drawLine(trackBounds.x, trackBounds.y, trackBounds.x + trackBounds.width, trackBounds.y); + } + } + + @Override + protected Dimension getMinimumThumbSize() { + final int thickness = myIsMini ? 10 : 18; + return isVertical() ? new Dimension(thickness, thickness * 2) : new Dimension(thickness * 2, thickness); + } + + + @Override + protected void paintThumb(Graphics g, JComponent c, Rectangle thumbBounds) { + if (thumbBounds.isEmpty() || !scrollbar.isEnabled()) { + return; + } + + + g.translate(thumbBounds.x, thumbBounds.y); + + if (myIsMini) { + paintMiniThumb(g, thumbBounds); + } + else { + paintMaxiThumb(g, thumbBounds); + } + + g.translate(-thumbBounds.x, -thumbBounds.y); + + } + + private void paintMaxiThumb(Graphics g, Rectangle thumbBounds) { + final int gap = 3; + + int w = thumbBounds.width - gap * 2; + int h = thumbBounds.height - gap * 2; + + final GradientPaint paint; + final Color start = adjustColor(GRADIENT_LIGHT); + final Color end = adjustColor(GRADIENT_DARK); + + if (isVertical()) { + paint = new GradientPaint(1, 0, start, w + 1, 0, end); + } + else { + paint = new GradientPaint(0, 1, start, 0, h + 1, end); + } + + ((Graphics2D)g).setPaint(paint); + g.fillRoundRect(gap, gap, w, h, 4, 4); + + g.setColor(GRADIENT_THUMB_BORDER); + g.drawRoundRect(gap, gap, w, h, 4, 4); + } + + private Color adjustColor(Color c) { + if (myAnimationColorShift == 0) return c; + return new SameColor(c.getRed() - myAnimationColorShift); + } + + private void paintMiniThumb(Graphics g, Rectangle thumbBounds) { + final int gap = 2; + + int w = thumbBounds.width - gap * 2; + int h = thumbBounds.height - gap * 2; + + g.setColor(adjustColor(PLAIN_THUMB_FILL)); + g.fillRect(gap, gap, w, h); + + g.setColor(PLAIN_THUMB_BORDER); + g.drawRect(gap, gap, w, h); + } + + + private boolean isVertical() { + return scrollbar.getOrientation() == JScrollBar.VERTICAL; + } + + @Override + protected JButton createIncreaseButton(int orientation) { + return new EmptyButton(); + } + + @Override + protected JButton createDecreaseButton(int orientation) { + return new EmptyButton(); + } + + private static class EmptyButton extends JButton { + @Override + public Dimension getMaximumSize() { + return new Dimension(0, 0); + } + + @Override + public Dimension getPreferredSize() { + return getMaximumSize(); + } + + @Override + public Dimension getMinimumSize() { + return getMaximumSize(); + } + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/ex/EditorSettingsExternalizable.java b/platform/platform-impl/src/com/intellij/openapi/editor/ex/EditorSettingsExternalizable.java index 88773955bd3a..0ab3aeffccc1 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/ex/EditorSettingsExternalizable.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/ex/EditorSettingsExternalizable.java @@ -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 = false; 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; } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/ex/MarkupModelEx.java b/platform/platform-impl/src/com/intellij/openapi/editor/ex/MarkupModelEx.java index c0d30ecbd186..7cf8d2c819b7 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/ex/MarkupModelEx.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/ex/MarkupModelEx.java @@ -30,7 +30,8 @@ public interface MarkupModelEx extends MarkupModel { @Nullable HighlighterList getHighlighterList(); - + + @Nullable RangeHighlighter addPersistentLineHighlighter(int lineNumber, int layer, TextAttributes textAttributes); boolean containsHighlighter(RangeHighlighter highlighter); 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 new file mode 100644 index 000000000000..a819f812971e --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/editor/ex/SoftWrapModelEx.java @@ -0,0 +1,71 @@ +/* + * 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.ex; + +import com.intellij.openapi.editor.LogicalPosition; +import com.intellij.openapi.editor.SoftWrapModel; +import com.intellij.openapi.editor.TextChange; +import com.intellij.openapi.editor.VisualPosition; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +/** + * Extends {@link SoftWrapModel} in order to define implementation-specific contract. + * + * @author Denis Zhdanov + * @since Jun 16, 2010 10:53:59 AM + */ +public interface SoftWrapModelEx extends SoftWrapModel { + + /** + * Asks current model to adjust logical position for the given visual position if necessary. + *

+ * Given logical position is allowed to be non-soft wrap aware, i.e. the one calculated as there are no soft wraps at the moment. + * + * @param defaultLogical default logical position that corresponds to the given visual position + * @param visual target visual position for which logical position should be adjusted if necessary + * @return logical position that corresponds to the given visual position + */ + @NotNull + LogicalPosition adjustLogicalPosition(@NotNull LogicalPosition defaultLogical, @NotNull VisualPosition visual); + + /** + * Asks current model to adjust visual position that corresponds to the given logical position if necessary. + *

+ * Given visual position is assumed to be the one that is obtained during soft wraps unaware processing. + * + * @param logical target logical position for {@code 'logical' -> visual} conversion + * @param defaultVisual visual position of {@code 'logical' -> visual} conversion that is unaware about soft wraps + * @return resulting visual position for the given logical position + */ + @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 + */ + List getRegisteredSoftWraps(); +} 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 ab44109454f7..4106dfa79e74 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 @@ -26,7 +26,6 @@ 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.project.Project; import java.awt.*; @@ -77,7 +76,7 @@ public class EditorUtil { public static void fillVirtualSpaceUntil(final Editor editor, int columnNumber, int lineNumber) { final int offset = editor.logicalPositionToOffset(new LogicalPosition(lineNumber, columnNumber)); - final String filler = EditorModificationUtil.calcStringToFillVitualSpace(editor); + final String filler = EditorModificationUtil.calcStringToFillVirtualSpace(editor); if (filler.length() > 0) { new WriteAction(){ protected void run(final Result result) throws Throwable { diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/ex/util/SegmentArray.java b/platform/platform-impl/src/com/intellij/openapi/editor/ex/util/SegmentArray.java index ea9aad16d686..848a74bb8318 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/ex/util/SegmentArray.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/ex/util/SegmentArray.java @@ -19,6 +19,13 @@ import com.intellij.openapi.diagnostic.Logger; import java.lang.reflect.Array; +/** + * This class is a data structure specialized for working with the indexed segments, i.e. it holds numerous mappings like + * {@code 'index <-> (start; end)'} and provides convenient way for working with them, e.g. find index by particular offset that + * belongs to target (start; end) segment etc. + *

+ * Not thread-safe. + */ public class SegmentArray { private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.ex.util.SegmentArray"); @@ -101,7 +108,7 @@ public class SegmentArray { public final int findSegmentIndex(int offset) { if (mySegmentCount <= 0) { if (offset == 0) return 0; - throw new IllegalStateException("no segments avaliable"); + throw new IllegalStateException("no segments available"); } final int lastValidOffset = getLastValidOffset(); diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/ex/util/SegmentArrayWithData.java b/platform/platform-impl/src/com/intellij/openapi/editor/ex/util/SegmentArrayWithData.java index 9d67b04e4d9f..3062a0fe35c9 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/ex/util/SegmentArrayWithData.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/ex/util/SegmentArrayWithData.java @@ -15,6 +15,12 @@ */ package com.intellij.openapi.editor.ex.util; +/** + * Expands {@link SegmentArray} contract in providing ability to attach additional 'short' variable to target segment, + * i.e. holds mappings like {@code 'index <-> (data, (start; end))'}. + *

+ * Not thread-safe. + */ public class SegmentArrayWithData extends SegmentArray { private short[] myData; diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/CaretModelImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/CaretModelImpl.java index c89912f99f1c..56a94026f1ac 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/CaretModelImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/CaretModelImpl.java @@ -47,6 +47,7 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener { private final EditorImpl myEditor; private final CopyOnWriteArrayList myCaretListeners = ContainerUtil.createEmptyCOWList(); private LogicalPosition myLogicalCaret; + private VerticalInfo myCaretInfo; private VisualPosition myVisibleCaret; private int myOffset; private int myVisualLineStart; @@ -58,6 +59,7 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener { myEditor = editor; myLogicalCaret = new LogicalPosition(0, 0); myVisibleCaret = new VisualPosition(0, 0); + myCaretInfo = new VerticalInfo(0, 0); myOffset = 0; myVisualLineStart = 0; Document doc = editor.getDocument(); @@ -97,26 +99,23 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener { } } - int oldY = myEditor.visibleLineNumberToYPosition(myVisibleCaret.line); - myVisibleCaret = new VisualPosition(line, column); + VerticalInfo oldInfo = myCaretInfo; LogicalPosition oldPosition = myLogicalCaret; - myLogicalCaret = myEditor.visualToLogicalPosition(myVisibleCaret); + setCurrentLogicalCaret(myEditor.visualToLogicalPosition(myVisibleCaret)); myOffset = myEditor.logicalPositionToOffset(myLogicalCaret); LOG.assertTrue(myOffset >= 0 && myOffset <= myEditor.getDocument().getTextLength()); - myVisualLineStart = - myEditor.logicalPositionToOffset(myEditor.visualToLogicalPosition(new VisualPosition(myVisibleCaret.line, 0))); - myVisualLineEnd = - myEditor.logicalPositionToOffset(myEditor.visualToLogicalPosition(new VisualPosition(myVisibleCaret.line + 1, 0))); + myVisualLineStart = myEditor.logicalPositionToOffset(myEditor.visualToLogicalPosition(new VisualPosition(myVisibleCaret.line, 0))); + myVisualLineEnd = myEditor.logicalPositionToOffset(myEditor.visualToLogicalPosition(new VisualPosition(myVisibleCaret.line + 1, 0))); ((FoldingModelImpl)myEditor.getFoldingModel()).flushCaretPosition(); myEditor.setLastColumnNumber(myVisibleCaret.column); myEditor.updateCaretCursor(); - requestRepaint(oldY); + requestRepaint(oldInfo); if (oldPosition.column != myLogicalCaret.column || oldPosition.line != myLogicalCaret.line) { CaretEvent event = new CaretEvent(myEditor, oldPosition, myLogicalCaret); @@ -144,7 +143,8 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener { int lineShift, boolean withSelection, boolean blockSelection, - boolean scrollToCaret) { + boolean scrollToCaret) + { assertIsDispatchThread(); SelectionModel selectionModel = myEditor.getSelectionModel(); int selectionStart = selectionModel.getLeadSelectionOffset(); @@ -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.softWrapColumnDiff; 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,16 +260,20 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener { int lineEndColumnNumber = myEditor.offsetToLogicalPosition(lineEndOffset).column; if (column > lineEndColumnNumber) { column = lineEndColumnNumber; + if (softWrapColumns != 0) { + softWrapColumns -= column - lineEndColumnNumber; + } } } ((FoldingModelImpl)myEditor.getFoldingModel()).flushCaretPosition(); - int oldY = myEditor.visibleLineNumberToYPosition(myVisibleCaret.line); - + VerticalInfo oldInfo = myCaretInfo; LogicalPosition oldCaretPosition = myLogicalCaret; - myLogicalCaret = new LogicalPosition(line, column); + setCurrentLogicalCaret(new LogicalPosition( + line, column, softWrapLines, linesFromCurrentSoftWrap, softWrapColumns, pos.foldedLines, pos.foldingColumnDiff + )); final int offset = myEditor.logicalPositionToOffset(myLogicalCaret); @@ -282,13 +298,17 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener { myOffset = myEditor.logicalPositionToOffset(myLogicalCaret); LOG.assertTrue(myOffset >= 0 && myOffset <= myEditor.getDocument().getTextLength()); - myVisualLineStart = myEditor.logicalPositionToOffset(myEditor.visualToLogicalPosition(new VisualPosition(myVisibleCaret.line, 0))); - myVisualLineEnd = myEditor.logicalPositionToOffset(myEditor.visualToLogicalPosition(new VisualPosition(myVisibleCaret.line + 1, 0))); + int caretOffset = myEditor.logicalPositionToOffset(myEditor.visualToLogicalPosition(new VisualPosition(myVisibleCaret.line, 0))); + int caretLine = doc.getLineNumber(caretOffset); + myVisualLineStart = doc.getLineStartOffset(caretLine); + myVisualLineEnd = doc.getLineEndOffset(caretLine) + 1; myEditor.updateCaretCursor(); - requestRepaint(oldY); + requestRepaint(oldInfo); - if (oldCaretPosition.column != myLogicalCaret.column || oldCaretPosition.line != myLogicalCaret.line) { + if (oldCaretPosition.column + oldCaretPosition.softWrapColumnDiff != myLogicalCaret.column + myLogicalCaret.softWrapColumnDiff + || oldCaretPosition.line + oldCaretPosition.softWrapLines != myLogicalCaret.line + myLogicalCaret.softWrapLines) + { CaretEvent event = new CaretEvent(myEditor, oldCaretPosition, myLogicalCaret); for (CaretListener listener : myCaretListeners) { listener.caretPositionChanged(event); @@ -296,27 +316,25 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener { } } - private void requestRepaint(int oldY) { - int newY = myEditor.visibleLineNumberToYPosition(myVisibleCaret.line); + private void requestRepaint(VerticalInfo oldCaretInfo) { int lineHeight = myEditor.getLineHeight(); - Rectangle visibleRect = myEditor.getScrollingModel().getVisibleArea(); + Rectangle visibleArea = myEditor.getScrollingModel().getVisibleArea(); final EditorGutterComponentEx gutter = myEditor.getGutterComponentEx(); final EditorComponentImpl content = (EditorComponentImpl)myEditor.getContentComponent(); - int updateWidth = myEditor.getScrollPane().getHorizontalScrollBar().getValue() + visibleRect.width; - if (Math.abs(newY - oldY) <= 2 * lineHeight) { - int minY = Math.min(oldY, newY); - int maxY = Math.max(oldY + lineHeight, newY + lineHeight); + int updateWidth = myEditor.getScrollPane().getHorizontalScrollBar().getValue() + visibleArea.width; + if (Math.abs(myCaretInfo.y - oldCaretInfo.y) <= 2 * lineHeight) { + int minY = Math.min(oldCaretInfo.y, myCaretInfo.y); + int maxY = Math.max(oldCaretInfo.y + oldCaretInfo.height, myCaretInfo.y + myCaretInfo.height); content.repaintEditorComponent(0, minY, updateWidth, maxY - minY); gutter.repaint(0, minY, gutter.getWidth(), maxY - minY); } else { - content.repaintEditorComponent(0, oldY, updateWidth, 2 * lineHeight); - gutter.repaint(0, oldY, updateWidth, 2 * lineHeight); - content.repaintEditorComponent(0, newY, updateWidth, 2 * lineHeight); - gutter.repaint(0, newY, updateWidth, 2 * lineHeight); + content.repaintEditorComponent(0, oldCaretInfo.y, updateWidth, oldCaretInfo.height + lineHeight); + gutter.repaint(0, oldCaretInfo.y, updateWidth, oldCaretInfo.height + lineHeight); + content.repaintEditorComponent(0, myCaretInfo.y, updateWidth, myCaretInfo.height + lineHeight); + gutter.repaint(0, myCaretInfo.y, updateWidth, myCaretInfo.height + lineHeight); } - } public LogicalPosition getLogicalPosition() { @@ -338,6 +356,18 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener { return myOffset; } + /** + * There is a possible case that single logical line is spread to more than one visual lines because of soft wraps. This method + * allows to receive information about vertical range occupied by the active logical line, i.e. it identifies + * 'y' coordinate of the first visual line that corresponds to the logical line and total height + * of all visual lines that correspond to the active logical line. + * + * @return object that encapsulates information about visual vertical range occupied by the current logical line on a screen + */ + public VerticalInfo getVisualCaretInfo() { + return myCaretInfo; + } + public int getVisualLineStart() { return myVisualLineStart; } @@ -391,7 +421,7 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener { int newOffset = myOffset; - if (myOffset > oldEndOffset || myOffset == oldEndOffset && needToShiftWhitespaces(e)) { + if (myOffset > oldEndOffset || myOffset == oldEndOffset && needToShiftWhiteSpaces(e)) { newOffset += e.getNewLength() - e.getOldLength(); } else if (myOffset >= startOffset && myOffset <= oldEndOffset) { @@ -413,7 +443,7 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener { myVisualLineEnd = myEditor.logicalPositionToOffset(myEditor.visualToLogicalPosition(new VisualPosition(myVisibleCaret.line + 1, 0))); } - private boolean needToShiftWhitespaces(final DocumentEvent e) { + private boolean needToShiftWhiteSpaces(final DocumentEvent e) { if(!CharArrayUtil.containsOnlyWhiteSpaces(e.getNewFragment()) || CharArrayUtil.containLineBreaks(e.getNewFragment())) return e.getOldLength() > 0; if(e.getOffset() == 0) return false; @@ -429,4 +459,45 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener { public int getPriority() { return 3; } + + private void setCurrentLogicalCaret(LogicalPosition position) { + myLogicalCaret = position; + myCaretInfo = createVerticalInfo(position); + } + + private VerticalInfo createVerticalInfo(LogicalPosition position) { + Document document = myEditor.getDocument(); + int line = position.line; + + // There is a possible case that active logical line is represented on multiple lines due to soft wraps processing. + // We want to highlight those visual lines as 'active' then, so, we calculate 'y' position for the logical line start + // and height in accordance with the number of occupied visual lines. + int y = myEditor.logicalPositionToXY(myEditor.offsetToLogicalPosition(document.getLineStartOffset(line))).y; + int height = myEditor.getLineHeight(); + if (line < document.getLineCount() - 1) { + int nextLineY = myEditor.logicalPositionToXY(myEditor.offsetToLogicalPosition(document.getLineStartOffset(line + 1))).y; + int heightCandidate = nextLineY - y; + + // There is a possible case that active line is the one that ends with folding, so, 'y' position + // of its next logical line is the same as the previous. We explicitly check that in order to use non-standard + // line height only in case of visible soft-wrapped line. + if (heightCandidate > height) { + height = heightCandidate; + } + } + return new VerticalInfo(y, height); + } + + /** + * Encapsulates information about target vertical range info - its 'y' coordinate and height in pixels. + */ + public static class VerticalInfo { + public final int y; + public final int height; + + private VerticalInfo(int y, int height) { + this.y = y; + this.height = height; + } + } } 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 2eafb38d65a9..8160cff3ef07 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 @@ -85,7 +85,7 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse private static final int GAP_BETWEEN_ANNOTATIONS = 6; private Color myBackgroundColor = null; private GutterDraggableObject myGutterDraggableObject; - private String myLastGutterTooltip = null; + private String myLastGutterToolTip = null; public EditorGutterComponentImpl(EditorImpl editor) { @@ -121,7 +121,7 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse myBackgroundColor = null; repaint(); } - + public void paint(Graphics g) { ((ApplicationImpl)ApplicationManager.getApplication()).editorPaintStart(); @@ -143,7 +143,7 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse paintLineNumbers(g, clip); paintAnnotations(g, clip); - Object antialiasing = g2.getRenderingHint(RenderingHints.KEY_ANTIALIASING); + Object hint = g2.getRenderingHint(RenderingHints.KEY_ANTIALIASING); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); try { @@ -152,7 +152,7 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse paintFoldingTree(g, clip); } finally { - g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, antialiasing); + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, hint); } g2.setTransform(old); @@ -258,12 +258,11 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse } private void paintCaretRowBackground(final Graphics g, final int x, final int width) { - final VisualPosition visCaret = myEditor.getCaretModel().getVisualPosition(); + CaretModelImpl.VerticalInfo caretInfo = myEditor.getCaretModel().getVisualCaretInfo(); Color caretRowColor = myEditor.getColorsScheme().getColor(EditorColors.CARET_ROW_COLOR); if (caretRowColor != null) { g.setColor(caretRowColor); - final Point caretPoint = myEditor.visualPositionToXY(visCaret); - g.fillRect(x, caretPoint.y, width, myEditor.getLineHeight()); + g.fillRect(x, caretInfo.y, width, caretInfo.height); } } @@ -327,7 +326,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() - @@ -504,7 +507,7 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse Graphics2D g2 = (Graphics2D)g; - Object antialiasing = g2.getRenderingHint(RenderingHints.KEY_ANTIALIASING); + Object hint = g2.getRenderingHint(RenderingHints.KEY_ANTIALIASING); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); try { processRangeHighlighters(new RangeHighlighterProcessor() { @@ -514,7 +517,7 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse }, firstVisibleOffset, lastVisibleOffset); } finally { - g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, antialiasing); + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, hint); } int firstVisibleLine = myEditor.getDocument().getLineNumber(firstVisibleOffset); @@ -549,16 +552,17 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse } private void paintLineMarkerRenderer(RangeHighlighter highlighter, Graphics g) { - Rectangle rect = getLineRendererRect(highlighter); + Rectangle rectangle = getLineRendererRectangle(highlighter); - if (rect != null) { + if (rectangle != null) { final LineMarkerRenderer lineMarkerRenderer = highlighter.getLineMarkerRenderer(); assert lineMarkerRenderer != null; - lineMarkerRenderer.paint(myEditor, g, rect); + lineMarkerRenderer.paint(myEditor, g, rectangle); } } - private Rectangle getLineRendererRect(RangeHighlighter highlighter) { + @Nullable + private Rectangle getLineRendererRectangle(RangeHighlighter highlighter) { LineMarkerRenderer renderer = highlighter.getLineMarkerRenderer(); if (renderer == null) return null; @@ -694,10 +698,10 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse paintCaretRowBackground(g, lineX, getFoldingAnchorWidth()); - paintFoldingBoxBacgrounds((Graphics2D)g); + paintFoldingBoxBackground((Graphics2D)g); } - private void paintFoldingBoxBacgrounds(Graphics2D g) { + private void paintFoldingBoxBackground(Graphics2D g) { if (!isFoldingOutlineShown()) return; Rectangle clip = g.getClipBounds(); @@ -965,14 +969,14 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse continue; } - if (rectByFoldOffset(foldStart, anchorWidth, anchorX).contains(x, y)) return foldRange; - if ((group == null || foldRange.isExpanded()) && rectByFoldOffset(foldEnd, anchorWidth, anchorX).contains(x, y)) return foldRange; + if (rectangleByFoldOffset(foldStart, anchorWidth, anchorX).contains(x, y)) return foldRange; + if ((group == null || foldRange.isExpanded()) && rectangleByFoldOffset(foldEnd, anchorWidth, anchorX).contains(x, y)) return foldRange; } return null; } - private Rectangle rectByFoldOffset(VisualPosition foldStart, int anchorWidth, int anchorX) { + private Rectangle rectangleByFoldOffset(VisualPosition foldStart, int anchorWidth, int anchorX) { int anchorY = myEditor.visibleLineNumberToYPosition(foldStart.line) + myEditor.getLineHeight() - myEditor.getDescent() - anchorWidth; return new Rectangle(anchorX, anchorY, anchorWidth, anchorWidth); @@ -983,11 +987,11 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse } public void mouseMoved(final MouseEvent e) { - String tooltip = null; + String toolTip = null; GutterIconRenderer renderer = getGutterRenderer(e); TooltipController controller = TooltipController.getInstance(); if (renderer != null) { - tooltip = renderer.getTooltipText(); + toolTip = renderer.getTooltipText(); if (renderer.isNavigateAction()) { setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); } @@ -1002,10 +1006,10 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse TextAnnotationGutterProvider provider = getProviderAtPoint(e.getPoint()); if (provider != null) { final int line = getLineNumAtPoint(e.getPoint()); - tooltip = provider.getToolTip(line, myEditor); - if (!Comparing.equal(tooltip, myLastGutterTooltip)) { + toolTip = provider.getToolTip(line, myEditor); + if (!Comparing.equal(toolTip, myLastGutterToolTip)) { controller.cancelTooltip(GUTTER_TOOLTIP_GROUP); - myLastGutterTooltip = tooltip; + myLastGutterToolTip = toolTip; } if (myProviderToListener.containsKey(provider)) { final EditorGutterAction action = myProviderToListener.get(provider); @@ -1017,8 +1021,8 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse } } - if (tooltip != null && tooltip.length() != 0) { - controller.showTooltipByMouseMove(myEditor, e, ((EditorMarkupModel)myEditor.getMarkupModel()).getErrorStripTooltipRendererProvider().calcTooltipRenderer(tooltip), false, GUTTER_TOOLTIP_GROUP); + if (toolTip != null && toolTip.length() != 0) { + controller.showTooltipByMouseMove(myEditor, e, ((EditorMarkupModel)myEditor.getMarkupModel()).getErrorStripTooltipRendererProvider().calcTooltipRenderer(toolTip), false, GUTTER_TOOLTIP_GROUP); } else { controller.cancelTooltip(GUTTER_TOOLTIP_GROUP); @@ -1056,6 +1060,7 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse return myEditor.xyToLogicalPosition(new Point(0, clickPoint.y)).line; } + @Nullable private TextAnnotationGutterProvider getProviderAtPoint(final Point clickPoint) { int current = getAnnotationsAreaOffset(); if (clickPoint.x < current) return null; @@ -1126,11 +1131,11 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse processRangeHighlighters(new RangeHighlighterProcessor() { public void process(RangeHighlighter highlighter) { if (gutterRenderer[0] != null) return; - Rectangle rect = getLineRendererRect(highlighter); - if (rect == null) return; + Rectangle rectangle = getLineRendererRectangle(highlighter); + if (rectangle == null) return; - int startY = rect.y; - int endY = startY + rect.height; + int startY = rectangle.y; + int endY = startY + rectangle.height; if (startY == endY) { endY += myEditor.getLineHeight(); } @@ -1315,7 +1320,7 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse } }, dragSourceListener); } - catch (InvalidDnDOperationException idoe) { + catch (InvalidDnDOperationException e) { // OK, can't dnd } } @@ -1325,21 +1330,21 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse } private class MyDragSourceListener extends DragSourceAdapter { - public void dragEnter(DragSourceDragEvent dsde) { - updateCursor(dsde); + public void dragEnter(DragSourceDragEvent e) { + updateCursor(e); } - public void dragOver(DragSourceDragEvent dsde) { - updateCursor(dsde); + public void dragOver(DragSourceDragEvent e) { + updateCursor(e); } - public void dropActionChanged(DragSourceDragEvent dsde) { - dsde.getDragSourceContext().setCursor(null);//setCursor (dsde.getDragSourceContext()); + public void dropActionChanged(DragSourceDragEvent e) { + e.getDragSourceContext().setCursor(null);//setCursor (e.getDragSourceContext()); } - private void updateCursor(final DragSourceDragEvent dsde) { - final DragSourceContext context = dsde.getDragSourceContext(); - final Point screenPoint = dsde.getLocation(); + private void updateCursor(final DragSourceDragEvent e) { + final DragSourceContext context = e.getDragSourceContext(); + final Point screenPoint = e.getLocation(); if (screenPoint != null) { final Point gutterPoint = new Point(screenPoint); SwingUtilities.convertPointFromScreen(gutterPoint, EditorGutterComponentImpl.this); @@ -1355,26 +1360,26 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse context.setCursor(null); } - public void dragDropEnd(DragSourceDropEvent dsde) { - if (!dsde.getDropSuccess()) return; + public void dragDropEnd(DragSourceDropEvent e) { + if (!e.getDropSuccess()) return; - if (dsde.getDropAction() == DnDConstants.ACTION_MOVE) { + if (e.getDropAction() == DnDConstants.ACTION_MOVE) { myGutterDraggableObject.removeSelf(); } } } private class MyDropTargetListener extends DropTargetAdapter { - public void drop(DropTargetDropEvent dtde) { + public void drop(DropTargetDropEvent e) { if (myGutterDraggableObject != null) { - int dropAction = dtde.getDropAction(); + int dropAction = e.getDropAction(); if ((dropAction & DnDConstants.ACTION_MOVE) != 0) { - int line = myEditor.xyToLogicalPosition(new Point(0, (int)dtde.getLocation().getY())).line; - dtde.dropComplete(myGutterDraggableObject.copy(line)); + int line = myEditor.xyToLogicalPosition(new Point(0, (int)e.getLocation().getY())).line; + e.dropComplete(myGutterDraggableObject.copy(line)); return; } } - dtde.rejectDrop(); + e.rejectDrop(); } } } 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 083208f8984c..1ecbe3e9e901 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 @@ -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; @@ -64,10 +65,11 @@ import com.intellij.util.Alarm; import com.intellij.util.IJSwingUtilities; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.HashMap; +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; @@ -97,6 +99,7 @@ import java.text.AttributedCharacterIterator; import java.text.AttributedString; import java.text.CharacterIterator; import java.util.*; +import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; @@ -156,6 +159,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; @@ -169,10 +173,12 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi private static final int MOUSE_SELECTION_STATE_LINE_SELECTED = 2; private final MarkupModelListener myMarkupModelListener; + private final List mySoftWrapsOnLastRepaint = new ArrayList(); + private boolean myUnderRepainting; private EditorHighlighter myHighlighter; - private int myScrollbarOrientation; + private int myScrollBarOrientation; private boolean myMousePressedInsideSelection; private FontMetrics myPlainFontMetrics; private FontMetrics myBoldFontMetrics; @@ -242,6 +248,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(); @@ -315,7 +322,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); @@ -420,7 +427,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi } @NotNull - public CaretModel getCaretModel() { + public CaretModelImpl getCaretModel() { return myCaretModel; } @@ -429,6 +436,11 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi return myScrollingModel; } + @NotNull + public SoftWrapModelImpl getSoftWrapModel() { + return mySoftWrapModel; + } + @NotNull public EditorSettings getSettings() { assertReadAccess(); @@ -496,7 +508,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi myScrollingModel.dispose(); myGutterComponent.dispose(); clearCaretThread(); - //myFoldingModel.dispose(); TODO rangemarker tree + //myFoldingModel.dispose(); TODO range marker tree } private void clearCaretThread() { @@ -610,11 +622,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); @@ -781,11 +793,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(); @@ -798,7 +818,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(); @@ -811,32 +833,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++; } @@ -867,6 +913,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 'x' position on a line where given character should be displayed + * @return width required to represent given char with the given settings on a screen; + * '0' 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 'x' coordinate of the line where given char is represented that indicates char end location + * @param prevX 'x' coordinate of the line where given char is represented that indicates char start location + * @param spaceSize 'space' 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)); @@ -874,8 +956,16 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi @NotNull public LogicalPosition offsetToLogicalPosition(int offset) { - int line = calcLogicalLineNumber(offset); - int column = calcColumnNumber(offset, line); + return offsetToLogicalPosition(offset, true); + } + + 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); } @@ -1030,31 +1120,31 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi return myGutterComponent != null && myGutterComponent.isShowing(); } - private void repaintToScreenBotton(int startLine) { - Rectangle visibleRect = getScrollingModel().getVisibleArea(); + private void repaintToScreenBottom(int startLine) { + Rectangle visibleArea = getScrollingModel().getVisibleArea(); int yStartLine = logicalLineToY(startLine); - int yEndLine = visibleRect.y + visibleRect.height; + int yEndLine = visibleArea.y + visibleArea.height; - myEditorComponent.repaintEditorComponent(visibleRect.x, yStartLine, visibleRect.x + visibleRect.width, yEndLine - yStartLine); + myEditorComponent.repaintEditorComponent(visibleArea.x, yStartLine, visibleArea.x + visibleArea.width, yEndLine - yStartLine); myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), yEndLine - yStartLine); } public void repaintLines(int startLine, int endLine) { if (!isShowing()) return; - Rectangle visibleRect = getScrollingModel().getVisibleArea(); + Rectangle visibleArea = getScrollingModel().getVisibleArea(); int yStartLine = logicalLineToY(startLine); int yEndLine = logicalLineToY(endLine) + getLineHeight() + WAVE_HEIGHT; - myEditorComponent.repaintEditorComponent(visibleRect.x, yStartLine, visibleRect.x + visibleRect.width, yEndLine - yStartLine); + myEditorComponent.repaintEditorComponent(visibleArea.x, yStartLine, visibleArea.x + visibleArea.width, yEndLine - yStartLine); myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), yEndLine - yStartLine); } private void beforeChangedUpdate(DocumentEvent e) { if (!myDocument.isInBulkUpdate()) { - Rectangle viewRect = getScrollingModel().getVisibleArea(); + Rectangle visibleArea = getScrollingModel().getVisibleArea(); Point pos = visualPositionToXY(getCaretModel().getVisualPosition()); - myCaretUpdateVShift = pos.y - viewRect.y; + myCaretUpdateVShift = pos.y - visibleArea.y; } mySizeContainer.beforeChange(e); } @@ -1081,7 +1171,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; } } @@ -1139,7 +1229,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi } } - void recalcSizeAndRepaint() { + void recalculateSizeAndRepaint() { mySizeContainer.reset(); validateSize(); myEditorComponent.repaintEditorComponent(); @@ -1177,6 +1267,10 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi return isReleased; } + public boolean isUnderRepainting() { + return myUnderRepainting; + } + void paint(Graphics g) { startOptimizedScrolling(); @@ -1203,22 +1297,28 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi return; } - paintBackgrounds(g, clip); - paintRectangularSelection(g); - paintRightMargin(g, clip); - paintCustomRenderers((Graphics2D)g, clip); - final MarkupModel docMarkup = myDocument.getMarkupModel(myProject); - paintLineMarkersSeparators(g, clip, docMarkup); - paintLineMarkersSeparators(g, clip, myMarkupModel); - paintText(g, clip); - paintSegmentHighlightersBorderAndAfterEndOfLine(g, clip); - BorderEffect borderEffect = new BorderEffect(this, g); - borderEffect.paintHighlighters(getHighlighter()); - borderEffect.paintHighlighters(docMarkup.getAllHighlighters()); - borderEffect.paintHighlighters(getMarkupModel().getAllHighlighters()); - paintCaretCursor(g); + myUnderRepainting = true; + try { + paintBackgrounds(g, clip); + paintRectangularSelection(g); + paintRightMargin(g, clip); + paintCustomRenderers((Graphics2D)g, clip); + final MarkupModel docMarkup = myDocument.getMarkupModel(myProject); + paintLineMarkersSeparators(g, clip, docMarkup); + paintLineMarkersSeparators(g, clip, myMarkupModel); + paintText(g, clip); + paintSegmentHighlightersBorderAndAfterEndOfLine(g, clip); + BorderEffect borderEffect = new BorderEffect(this, g); + borderEffect.paintHighlighters(getHighlighter()); + borderEffect.paintHighlighters(docMarkup.getAllHighlighters()); + borderEffect.paintHighlighters(getMarkupModel().getAllHighlighters()); + paintCaretCursor(g); - paintComposedTextDecoration((Graphics2D)g); + paintComposedTextDecoration((Graphics2D)g); + } + finally { + myUnderRepainting = false; + } } private void paintCustomRenderers(Graphics2D g, Rectangle clip) { @@ -1400,6 +1500,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi return width; } + @SuppressWarnings({"StatementWithEmptyBody"}) private void paintBackgrounds(Graphics g, Rectangle clip) { Color defaultBackground = getBackroundColor(); g.setColor(defaultBackground); @@ -1440,6 +1541,8 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi int fontType = attributes.getFontType(); CharSequence text = myDocument.getCharsNoThreadCheck(); int lastLineIndex = Math.max(0, myDocument.getLineCount() - 1); + + outer: while (!iterationState.atEnd() && !lIterator.atEnd()) { int hEnd = iterationState.getEndOffset(); int lEnd = lIterator.getEnd(); @@ -1478,6 +1581,37 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi position.x = drawBackground(g, backColor, collapsedFolderAt.getPlaceholderText(), position, fontType, defaultBackground, clip); } else { + TextChange softWrap = getSoftWrapModel().getSoftWrap(start); + if (softWrap == null && getSoftWrapModel().shouldWrap(myDocument.getRawChars(), start, hEnd, position)) { + softWrap = getSoftWrapModel().wrap(myDocument.getRawChars(), start, hEnd); + } + if (softWrap != null) { + // Draw soft wrap text background if any. + CharSequence softWrapText = softWrap.getText(); + int softWrapStart = 0; + int softWrapEnd = 0; + while (softWrapEnd < softWrapText.length()) { + for (; softWrapEnd < softWrapText.length() && softWrapText.charAt(softWrapEnd) != '\n'; softWrapEnd++) ; + if (softWrapStart >= softWrapText.length() || softWrapEnd >= softWrapText.length()) { + position.x = drawBackground( + g, backColor, softWrapText.subSequence(softWrapStart, softWrapText.length()), position, fontType, defaultBackground, clip + ); + } + else { + if (softWrapEnd > softWrapStart + 1) { + drawBackground( + g, backColor, softWrapText.subSequence(softWrapStart, softWrapEnd - 1), position, fontType, defaultBackground, clip + ); + } + if (position.y > clip.y + clip.height) break outer; + position.x = 0; + position.y += lineHeight; + softWrapStart = softWrapEnd + 1; + softWrapEnd = softWrapStart; + } + } + } + if (hEnd > lEnd - lIterator.getSeparatorLength()) { position.x = drawBackground(g, backColor, text.subSequence(start, lEnd - lIterator.getSeparatorLength()), position, fontType, defaultBackground, clip); @@ -1500,6 +1634,24 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi if (lIterator.getLineNumber() >= lastLineIndex && position.y <= clip.y + clip.height) { paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight, defaultBackground); } + + // Perform additional activity if soft wrap is added or removed during repainting. + // Note: this code lives in this method in assumption that background repainting is the very first activity performed + // during whole editor component repaint. + List softWrapsAfterRepaint = mySoftWrapModel.getRegisteredSoftWraps(); + if (!mySoftWrapsOnLastRepaint.equals(softWrapsAfterRepaint)) { + // Repaint editor to the bottom in order to ensure that its content is shown correctly after new soft wrap introduction. + repaintToScreenBottom(xyToLogicalPosition(position).line); + + // Repaint gutter at all space that is located after active clip in order to ensure that line numbers are correctly redrawn + // in accordance with the newly introduced soft wrap(s). + myGutterComponent.repaint(0, clip.y, myGutterComponent.getWidth(), myGutterComponent.getHeight() - clip.y); + + // Ask caret model to update visual caret position. + getCaretModel().moveToOffset(getCaretModel().getOffset()); + } + mySoftWrapsOnLastRepaint.clear(); + mySoftWrapsOnLastRepaint.addAll(softWrapsAfterRepaint); } private void paintRectangularSelection(Graphics g) { @@ -1597,7 +1749,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) { @@ -1609,7 +1762,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()); @@ -1639,7 +1801,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; } @@ -1662,6 +1826,62 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi fontType, currentColor); } else { + boolean breakWholeIteration = false; + TextChange softWrap = getSoftWrapModel().getSoftWrap(start); + if (softWrap != null) { + // 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); } } @@ -1773,6 +1993,9 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi } private void paintCaretCursor(Graphics g) { + // There is a possible case that visual caret position is changed because of newly added or removed soft wraps. + // We check if that's the case and ask caret model to recalculate visual position if necessary. + myCaretCursor.paint(g); } @@ -2201,8 +2424,14 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi @NotNull public VisualPosition logicalToVisualPosition(@NotNull LogicalPosition logicalPos) { + return logicalToVisualPosition(logicalPos, true); + } + + private VisualPosition logicalToVisualPosition(@NotNull LogicalPosition logicalPos, boolean softWrapAware) { 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); @@ -2223,16 +2452,17 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi line -= myFoldingModel.getFoldedLinesCountBefore(offset); - FoldRegion[] toplevel = myFoldingModel.fetchTopLevel(); + FoldRegion[] topLevel = myFoldingModel.fetchTopLevel(); + LogicalPosition anchorFoldingPosition = logicalPos; 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) { + if (region.getDocument().getLineNumber(region.getEndOffset()) == anchorFoldingPosition.line && region.getEndOffset() <= offset) { LogicalPosition foldStart = offsetToLogicalPosition(region.getStartOffset()); LogicalPosition foldEnd = offsetToLogicalPosition(region.getEndOffset()); column += foldStart.column + region.getPlaceholderText().length() - foldEnd.column; offset = region.getStartOffset(); - logicalPos = foldStart; + anchorFoldingPosition = foldStart; } else { break; @@ -2242,7 +2472,13 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi LOG.assertTrue(line >= 0); - return new VisualPosition(line, Math.max(0, column)); + VisualPosition softWrapUnawarePosition = new VisualPosition(line, Math.max(0, column)); + if (softWrapAware) { + return mySoftWrapModel.adjustVisualPosition(logicalPos, softWrapUnawarePosition); + } + else { + return softWrapUnawarePosition; + } } @Nullable @@ -2311,7 +2547,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; @@ -2319,26 +2557,41 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi FoldRegion lastCollapsedBefore = getLastCollapsedBeforePosition(visiblePos); if (lastCollapsedBefore != null) { - LogicalPosition logFoldEnd = offsetToLogicalPosition(lastCollapsedBefore.getEndOffset()); - VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd); - - line = logFoldEnd.line + (visiblePos.line - visFoldEnd.line); - if (visFoldEnd.line == visiblePos.line) { - if (visiblePos.column >= visFoldEnd.column) { - column = logFoldEnd.column + (visiblePos.column - visFoldEnd.column); + LogicalPosition softWrapAwareLogFoldEnd = offsetToLogicalPosition(lastCollapsedBefore.getEndOffset()); + VisualPosition softWrapAwareVisFoldEnd = logicalToVisualPosition(softWrapAwareLogFoldEnd); + if (softWrapAwareVisFoldEnd.line == visiblePos.line) { + if (visiblePos.column == softWrapAwareVisFoldEnd.column) { + return softWrapAwareLogFoldEnd; + } + else if (visiblePos.column > softWrapAwareVisFoldEnd.column) { + int columnToUse = softWrapAwareLogFoldEnd.column + visiblePos.column - softWrapAwareVisFoldEnd.column; + return new LogicalPosition( + softWrapAwareLogFoldEnd.line, columnToUse, softWrapAwareLogFoldEnd.softWrapLines, + softWrapAwareLogFoldEnd.linesFromActiveSoftWrap, visiblePos.column - columnToUse - softWrapAwareLogFoldEnd.foldingColumnDiff, + softWrapAwareLogFoldEnd.foldedLines, softWrapAwareLogFoldEnd.foldingColumnDiff + ); } else { return offsetToLogicalPosition(lastCollapsedBefore.getStartOffset()); } } + + LogicalPosition softWrapUnawareLogFoldEnd = offsetToLogicalPosition(lastCollapsedBefore.getEndOffset(), false); + VisualPosition softWrapUnawareVisFoldEnd = logicalToVisualPosition(softWrapUnawareLogFoldEnd, false); + line = softWrapUnawareLogFoldEnd.line + (visiblePos.line - softWrapUnawareVisFoldEnd.line); } if (column < 0) column = 0; - return new LogicalPosition(line, column); + LogicalPosition softWrapUnawareResult = new LogicalPosition(line, column); + return mySoftWrapModel.adjustLogicalPosition(softWrapUnawareResult, visiblePos); } private int calcLogicalLineNumber(int offset) { + return calcLogicalLineNumber(offset, true); + } + + private int calcLogicalLineNumber(int offset, boolean softWrapAware) { int textLength = myDocument.getTextLength(); if (textLength == 0) return 0; @@ -2346,20 +2599,32 @@ 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()); - - return lineIndex; + if (softWrapAware) { + return mySoftWrapModel.offsetToLogicalPosition(offset).line; + } + else { + int lineIndex = myDocument.getLineNumber(offset); + LOG.assertTrue(lineIndex >= 0 && lineIndex < myDocument.getLineCount()); + return lineIndex; + } } public int calcColumnNumber(int offset, int lineIndex) { + return calcColumnNumber(offset, lineIndex, true); + } + + 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; - return EditorUtil.calcColumnNumber(this, text, start, offset, EditorUtil.getTabSize(this)); + if (softWrapAware) { + return mySoftWrapModel.offsetToLogicalPosition(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)); + } } private void moveCaretToScreenPos(int x, int y) { @@ -2371,10 +2636,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.softWrapColumnDiff; if (lineNumber < 0) { lineNumber = 0; columnNumber = 0; + softWrapLines = 0; + linesFromCurrentSoftWrap = 0; + softWrapColumns = 0; } final int totalLines = myDocument.getLineCount(); @@ -2393,6 +2664,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; + } } } @@ -2405,7 +2679,9 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi } } } - LogicalPosition pos1 = new LogicalPosition(lineNumber, columnNumber); + LogicalPosition pos1 = new LogicalPosition( + lineNumber, columnNumber, softWrapLines, linesFromCurrentSoftWrap, softWrapColumns, pos.foldedLines, pos.foldingColumnDiff + ); getCaretModel().moveToLogicalPosition(pos1); } @@ -2547,7 +2823,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi if (SwingUtilities.isRightMouseButton(e)) { return; } - Rectangle rect = getScrollingModel().getVisibleArea(); + Rectangle visibleArea = getScrollingModel().getVisibleArea(); int x = e.getX(); @@ -2556,23 +2832,23 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi } int dx = 0; - if (x < rect.x && rect.x > 0) { - dx = x - rect.x; + if (x < visibleArea.x && visibleArea.x > 0) { + dx = x - visibleArea.x; } else { - if (x > rect.x + rect.width) { - dx = x - rect.x - rect.width; + if (x > visibleArea.x + visibleArea.width) { + dx = x - visibleArea.x - visibleArea.width; } } int dy = 0; int y = e.getY(); - if (y < rect.y && rect.y > 0) { - dy = y - rect.y; + if (y < visibleArea.y && visibleArea.y > 0) { + dy = y - visibleArea.y; } else { - if (y > rect.y + rect.height) { - dy = y - rect.y - rect.height; + if (y > visibleArea.y + visibleArea.height) { + dy = y - visibleArea.y - visibleArea.height; } } if (dx == 0 && dy == 0) { @@ -2858,8 +3134,8 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi int lineHeight = getLineHeight(); int y = myLocation.y; - Rectangle viewRect = getScrollingModel().getVisibleArea(); - if (x - viewRect.x < 0) { + Rectangle viewRectangle = getScrollingModel().getVisibleArea(); + if (x - viewRectangle.x < 0) { return; } @@ -3013,8 +3289,8 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi } class MyScrollBar extends JScrollBar { - @NonNls private static final String DECR_BUTTON_FIELD = "decrButton"; - @NonNls private static final String INCR_BUTTON_FIELD = "incrButton"; + @NonNls private static final String DECREASE_BUTTON_FIELD = "decrButton"; + @NonNls private static final String INCREASE_BUTTON_FIELD = "incrButton"; @NonNls private static final String APPLE_LAF_AQUA_SCROLL_BAR_UI_CLASS = "apple.laf.AquaScrollBarUI"; private MyScrollBar(int orientation) { @@ -3023,9 +3299,14 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi putClientProperty("JScrollBar.fastWheelScrolling", Boolean.TRUE); // fast scrolling for JDK 6 } + @Override + public void updateUI() { + setUI(ButtonlessScrollBarUI.createNormal()); + } + /** - * This is helper method. It returns height of the top (descrease) scrollbar - * button. Please note, that it's possible to return real height only if scrollbar + * This is helper method. It returns height of the top (decrease) scroll bar + * button. Please note, that it's possible to return real height only if scroll bar * is instance of BasicScrollBarUI. Otherwise it returns fake (but good enough :) ) * value. */ @@ -3034,11 +3315,11 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi Insets insets = getInsets(); if (barUI instanceof BasicScrollBarUI) { try { - Field decrButtonField = BasicScrollBarUI.class.getDeclaredField(DECR_BUTTON_FIELD); - decrButtonField.setAccessible(true); - JButton decrButtonValue = (JButton)decrButtonField.get(barUI); - LOG.assertTrue(decrButtonValue != null); - return insets.top + decrButtonValue.getHeight(); + Field decreaseButtonField = BasicScrollBarUI.class.getDeclaredField(DECREASE_BUTTON_FIELD); + decreaseButtonField.setAccessible(true); + JButton decreaseButtonValue = (JButton)decreaseButtonField.get(barUI); + LOG.assertTrue(decreaseButtonValue != null); + return insets.top + decreaseButtonValue.getHeight(); } catch (Exception exc) { throw new IllegalStateException(exc.getMessage()); @@ -3050,8 +3331,8 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi } /** - * This is helper method. It returns height of the bottom (increase) scrollbar - * button. Please note, that it's possible to return real height only if scrollbar + * This is helper method. It returns height of the bottom (increase) scroll bar + * button. Please note, that it's possible to return real height only if scroll bar * is instance of BasicScrollBarUI. Otherwise it returns fake (but good enough :) ) * value. */ @@ -3060,11 +3341,11 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi Insets insets = getInsets(); if (barUI instanceof BasicScrollBarUI) { try { - Field incrButtonField = BasicScrollBarUI.class.getDeclaredField(INCR_BUTTON_FIELD); - incrButtonField.setAccessible(true); - JButton incrButtonValue = (JButton)incrButtonField.get(barUI); - LOG.assertTrue(incrButtonValue != null); - return insets.bottom + incrButtonValue.getHeight(); + Field increaseButtonField = BasicScrollBarUI.class.getDeclaredField(INCREASE_BUTTON_FIELD); + increaseButtonField.setAccessible(true); + JButton increaseButtonValue = (JButton)increaseButtonField.get(barUI); + LOG.assertTrue(increaseButtonValue != null); + return insets.bottom + increaseButtonValue.getHeight(); } catch (Exception exc) { throw new IllegalStateException(exc.getMessage()); @@ -3191,7 +3472,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()); } @@ -3220,7 +3501,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi } int getVerticalScrollbarOrientation() { - return myScrollbarOrientation; + return myScrollBarOrientation; } MyScrollBar getVerticalScrollBar() { @@ -3505,10 +3786,12 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi return length; } + @Nullable public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) { return null; } + @Nullable public AttributedCharacterIterator getSelectedText(AttributedCharacterIterator.Attribute[] attributes) { String text = getSelectionModel().getSelectedText(); return text == null ? null : new AttributedString(text).getIterator(); @@ -4128,6 +4411,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi return false; } + @Nullable protected Transferable createTransferable(JComponent c) { Editor editor = getEditor(c); String s = editor.getSelectionModel().getSelectedText(); diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorMarkupModelImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorMarkupModelImpl.java index ac754787dc95..d6350bd05de6 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorMarkupModelImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorMarkupModelImpl.java @@ -48,6 +48,7 @@ import com.intellij.util.containers.ContainerUtil; import com.intellij.util.ui.UIUtil; import gnu.trove.THashSet; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; @@ -334,6 +335,9 @@ public class EditorMarkupModelImpl extends MarkupModelImpl implements EditorMark g.setColor(color); g.fillRect(x + 1, yStart, paintWidth - 2, yEnd - yStart); + if (color == null) { + return; + } Color brighter = color.brighter(); Color darker = color.darker(); @@ -388,6 +392,7 @@ public class EditorMarkupModelImpl extends MarkupModelImpl implements EditorMark fireErrorMarkerClicked(marker, e); } + @Nullable private RangeHighlighter getNearestRangeHighlighter(final MouseEvent e, final int width) { List nearestSpots = getNearestMarkSpots(e, width); RangeHighlighter nearestMarker = null; diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/FoldingModelImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/FoldingModelImpl.java index ee981287f074..e1ee5da5922c 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/FoldingModelImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/FoldingModelImpl.java @@ -301,7 +301,7 @@ public class FoldingModelImpl implements FoldingModelEx, PrioritizedDocumentList myFoldTree.rebuild(); myEditor.updateCaretCursor(); - myEditor.recalcSizeAndRepaint(); + myEditor.recalculateSizeAndRepaint(); if (myEditor.getGutterComponentEx().isFoldingOutlineShown()) { myEditor.getGutterComponentEx().repaint(); } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/IterationState.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/IterationState.java index f1338b78b5b1..31f6217e893a 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/IterationState.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/IterationState.java @@ -38,7 +38,7 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.List; -@SuppressWarnings({"ForLoopReplaceableByForEach"}) // Way too many garbage in AbrstractList.iterator() produced otherwise. +@SuppressWarnings({"ForLoopReplaceableByForEach"}) // Way too many garbage in AbstractList.iterator() produced otherwise. public final class IterationState { private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.IterationState"); private final TextAttributes myMergedAttributes = new TextAttributes(); diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/LineSet.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/LineSet.java index e2ccbea22de2..9dc4468cc906 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/LineSet.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/LineSet.java @@ -24,7 +24,10 @@ import com.intellij.openapi.util.text.LineTokenizer; import com.intellij.util.text.MergingCharSequence; /** - * + * Data structure specialized for working with document text lines, i.e. stores information about line mapping to document + * offsets and provides convenient ways to work with that information like retrieving target line by document offset etc. + *

+ * Not thread-safe. */ public class LineSet{ private SegmentArrayWithData mySegments = new SegmentArrayWithData(); diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/SettingsImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/SettingsImpl.java index b7a82b17ec26..17085851d78e 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/SettingsImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/SettingsImpl.java @@ -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(); 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 new file mode 100644 index 000000000000..8af6f3f0600d --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/SoftWrapModelImpl.java @@ -0,0 +1,882 @@ +/* + * 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.DocumentEvent; +import com.intellij.openapi.editor.event.DocumentListener; +import com.intellij.openapi.editor.event.VisibleAreaEvent; +import com.intellij.openapi.editor.event.VisibleAreaListener; +import com.intellij.openapi.editor.ex.SoftWrapModelEx; +import com.intellij.openapi.editor.ex.util.EditorUtil; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.text.CharArrayUtil; +import gnu.trove.TIntHashSet; +import gnu.trove.TIntIterator; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.awt.*; +import java.nio.CharBuffer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * Default {@link SoftWrapModelEx} implementation. + *

+ * Design principles: + *

    + *
  • + * remembers width of the visible area used last time; drops all of registered soft wraps on offset/position + * recalculation/adjustment request (e.g. {@link #adjustLogicalPosition(LogicalPosition, VisualPosition)}) if current + * visible area width differs from the one used last time; + *
  • + *
  • + * performs {@code 'soft wrap' -> 'hard wrap'} conversion if the document is change in a soft wrap-introduced + * virtual space (e.g. user start typing inside soft wrap-introduced indent). There is a dedicated method that + * does that if necessary - {@link #beforeDocumentChange(VisualPosition)}; + *
  • + *
+ *

+ * Not thread-safe. + * + * @author Denis Zhdanov + * @since Jun 8, 2010 12:47:32 PM + */ +public class SoftWrapModelImpl implements SoftWrapModelEx { + + private static final Comparator SOFT_WRAPS_BY_OFFSET_COMPARATOR = new Comparator() { + public int compare(TextChange c1, TextChange c2) { + return c1.getStart() - c2.getEnd(); + } + }; + + private final CharBuffer myCharBuffer = CharBuffer.allocate(1); + + /** + * Holds lines where soft wraps should be removed. + *

+ * The general idea is to do the following: + *

    + *
  • listen for document changes, mark all soft wraps that belong to modified logical line as 'dirty';
  • + *
  • remove soft wraps marked as 'dirty' on repaint;
  • + *
+ */ + private final TIntHashSet myDirtyLines = new TIntHashSet(); + + /** Holds registered soft wraps sorted by offsets in ascending order. */ + private final List myWraps = new ArrayList(); + private final List myWrapsView = Collections.unmodifiableList(myWraps); + + private final EditorImpl myEditor; + 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). + *

+ * We don't drop previously registered soft wraps during visible area change then but mark them as 'dirty' 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(); + } + + @Nullable + public TextChange getSoftWrap(int offset) { + if (myActive <= 0) { + dropDataIfNecessary(); + } + int i = getSoftWrapIndex(offset); + return i >= 0 ? myWraps.get(i) : null; + } + + /** + * 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. + * + * @param offset target offset + * @return index that conforms to {@link Collections#binarySearch(List, Object)} contract, i.e. non-negative returned + * index points to soft wrap that starts at the given offset; '-(negative value) - 1' points + * to position at {@link #myWraps} collection where soft wrap for the given index should be inserted + */ + private int getSoftWrapIndex(int offset) { + TextChange searchKey = new TextChange("", offset); + return Collections.binarySearch(myWraps, searchKey, SOFT_WRAPS_BY_OFFSET_COMPARATOR); + } + + private boolean hasSoftWrapAt(int offset) { + return getSoftWrapIndex(offset) >= 0; + } + + /** + * Inserts given soft wrap to {@link #myWraps} collection at the given index. + * + * @param softWrap soft wrap to store + * @return previous soft wrap object stored for the same offset if any; null otherwise + */ + @Nullable + private TextChange storeSoftWrap(TextChange softWrap) { + int i = Collections.binarySearch(myWraps, softWrap, SOFT_WRAPS_BY_OFFSET_COMPARATOR); + if (i >= 0) { + return myWraps.set(i, softWrap); + } + + i = -i - 1; + myWraps.add(i, softWrap); + return null; + } + + /** + * Allows to answer if symbols of the given char array located at [start; end) interval should be soft wrapped, + * i.e. represented on a next line. + * + * @param chars symbols holder + * @param start target symbols sub-sequence start within the given char array (inclusive) + * @param end target symbols sub-sequence end within the given char array (exclusive) + * @param position current drawing position + * @return true if target symbols sub-sequence should be soft-wrapped; false otherwise + */ + public boolean shouldWrap(char[] chars, int start, int end, Point position) { + if (!isSoftWrappingEnabled()) { + return false; + } + initIfNecessary(); + dropDataIfNecessary(); + + if (hasSoftWrapAt(start)) { + return true; + } + + if (myRightEdgeLocation < 0) { + assert false; + return false; + } + + //TODO den implement + return position.x + (end - start) * 7 > myRightEdgeLocation; + } + + private static boolean containsOnlyWhiteSpaces(char[] chars, int start, int end) { + int i = CharArrayUtil.shiftForward(chars, start, "\n \t"); + return i >= end; + } + + /** + * Asks current model to perform soft wrapping for the characters sub-sequent identified by the given parameters. + * + * @param chars symbols holder + * @param start target symbols sub-sequence start within the given char array (inclusive) + * @param end target symbols sub-sequence end within the given char array (exclusive) + * @return soft wrap registered for the given offset + */ + public TextChange wrap(char[] chars, int start, int end) { + TextChange result = getSoftWrap(start); + if (result != null) { + return result; + } + + dropDataIfNecessary(); + //TODO den implement indent calculation on formatting options + result = containsOnlyWhiteSpaces(chars, start, end) ? new TextChange("\n", start) : new TextChange("\n ", start); + storeSoftWrap(result); + return result; + } + + public List getRegisteredSoftWraps() { + return myWrapsView; + } + + /** + * Drops information about registered soft wraps if they are marked as 'dirty'. + * //TODO den add doc about editor repainting + * + * @see #myDataIsDirty + */ + private void dropDataIfNecessary() { + if (!myEditor.isUnderRepainting()) { + return; + } + Document document = myEditor.getDocument(); + for (TIntIterator it = myDirtyLines.iterator(); it.hasNext();) { + int line = it.next(); + int start = document.getLineStartOffset(line); + int end = document.getLineEndOffset(line); + + int startIndex = getSoftWrapIndex(start); + if (startIndex < 0) { + startIndex = -startIndex - 1; + } + int endIndex = startIndex; + for (; endIndex < myWraps.size(); endIndex++) { + TextChange softWrap = myWraps.get(endIndex); + if (softWrap.getStart() >= end) { + break; + } + } + myWraps.subList(startIndex, endIndex).clear(); + } + myDirtyLines.clear(); + + if (!myDataIsDirty) { + return; + + } + myDataIsDirty = false; + myWraps.clear(); + } + + @NotNull + public LogicalPosition adjustLogicalPosition(@NotNull LogicalPosition defaultLogical, @NotNull VisualPosition visual) { + if (myActive > 0 || !isSoftWrappingEnabled() || myWraps.isEmpty()) { + return defaultLogical; + } + + //TODO den check + //if (defaultLogical.softWrapAware) { + // return defaultLogical; + //} + + updateVisibleAreaIfNecessary(); + myActive++; + try { + return doAdjustLogicalPosition(defaultLogical, visual); + } + finally { + myActive--; + } + } + + private LogicalPosition doAdjustLogicalPosition(LogicalPosition defaultLogical, VisualPosition visual) { + DocumentImpl document = (DocumentImpl)myEditor.getDocument(); + int maxOffset = document.getLineEndOffset(Math.min(defaultLogical.line, document.getLineCount() - 1)); + + // This index points to registered soft wrap that is guaranteed to be located after the target visual line. + int endIndex = getSoftWrapIndex(maxOffset + 1); + if (endIndex < 0) { + endIndex = -endIndex - 1; + } + + int softWrapIntroducedLineFeeds = 0; + + FoldingModel foldingModel = myEditor.getFoldingModel(); + int i = 0; + int max = Math.min(myWraps.size(), endIndex); + for (; i < max; i++) { + TextChange softWrap = myWraps.get(i); + if (foldingModel.isOffsetCollapsed(softWrap.getStart())) { + continue; + } + + int currentSoftWrapLineFeeds = StringUtil.countNewLines(softWrap.getText()); + int softWrapLine = document.getLineNumber(softWrap.getStart()); + int visualLineBeforeSoftWrapAppliance + = myEditor.logicalToVisualPosition(new LogicalPosition(softWrapLine, 0)).line + softWrapIntroducedLineFeeds; + if (visualLineBeforeSoftWrapAppliance > visual.line) { + int logicalLine = defaultLogical.line - softWrapIntroducedLineFeeds; + LogicalPosition foldingUnawarePosition + = new LogicalPosition(logicalLine, defaultLogical.column, softWrapIntroducedLineFeeds, 0, 0, 0, 0); + return adjustFoldingData(foldingModel, foldingUnawarePosition); + } + + int visualLineAfterSoftWrapAppliance = visualLineBeforeSoftWrapAppliance + currentSoftWrapLineFeeds; + if (visualLineAfterSoftWrapAppliance < visual.line) { + softWrapIntroducedLineFeeds += currentSoftWrapLineFeeds; + continue; + } + + // If we're here that means that current soft wrap affects logical line that is matched to the given visual line. + // We iterate from the logical line start then in order to calculate resulting logical position. + Context context = new Context(defaultLogical, visual, softWrapIntroducedLineFeeds, visualLineBeforeSoftWrapAppliance, foldingModel); + int startLineOffset = document.getLineStartOffset(softWrapLine); + int endLineOffset = document.getLineEndOffset(softWrapLine); + CharSequence documentText = document.getCharsNoThreadCheck(); + for (int j = startLineOffset; j < endLineOffset; j++) { + + // Process soft wrap at the current offset if any. + if (j == softWrap.getStart()) { + CharSequence softWrapText = softWrap.getText(); + for (int k = 0; k < softWrapText.length(); k++) { + LogicalPosition result = context.onSoftWrapSymbol(softWrapText.charAt(k)); + if (result != null) { + return result; + } + } + } + + // Process document symbol. + LogicalPosition result = context.onNonSoftWrapSymbol(documentText.charAt(j)); + if (result != null) { + return result; + } + } + + // If we are here that means that target visual position is located at virtual space after the line end. + int logicalLine = defaultLogical.line - softWrapIntroducedLineFeeds - context.lineFeedsFromCurrentSoftWrap; + int logicalColumn = context.symbolsOnCurrentLogicalLine + visual.column - context.symbolsOnCurrentVisualLine; + int softWrapColumnDiff = visual.column - logicalColumn; + LogicalPosition foldingUnawarePosition = new LogicalPosition( + logicalLine, logicalColumn, softWrapIntroducedLineFeeds + context.lineFeedsFromCurrentSoftWrap, + context.lineFeedsFromCurrentSoftWrap, softWrapColumnDiff, 0, 0 + ); + return adjustFoldingData(foldingModel, foldingUnawarePosition); + } + + // If we are here that means that there is no soft wrap on a logical line that corresponds to the target visual line. + int logicalLine = defaultLogical.line - softWrapIntroducedLineFeeds; + LogicalPosition foldingUnaware = new LogicalPosition(logicalLine, defaultLogical.column, softWrapIntroducedLineFeeds, 0, 0, 0, 0); + return adjustFoldingData(foldingModel, foldingUnaware); + } + + /** + * Builds folding-aware logical position on the basis of the given folding-unaware position and folding model + * + * @param foldingModel folding model to use for retrieving information about folding + * @param position folding-unaware logical position + * @return folding-aware logical position + */ + private LogicalPosition adjustFoldingData(FoldingModel foldingModel, LogicalPosition position) { + int offset = myEditor.logicalPositionToOffset(position); + int foldedLines = 0; + int foldColumnDiff = 0; + int softWrapColumnDiff = position.softWrapColumnDiff; + DocumentImpl document = (DocumentImpl)myEditor.getDocument(); + CharSequence text = document.getCharsNoThreadCheck(); + for (FoldRegion foldRegion : foldingModel.getAllFoldRegions()) { + if (foldRegion.getStartOffset() >= offset) { + break; + } + + if (foldRegion.isExpanded() || !foldRegion.isValid()) { + continue; + } + + int foldingStartLine = document.getLineNumber(foldRegion.getStartOffset()); + int foldingEndLine = document.getLineNumber(foldRegion.getEndOffset()); + foldedLines += foldingEndLine - foldingStartLine; + + // Process situation when target offset is located inside the folded region. + if (offset >= foldRegion.getStartOffset() && offset < foldRegion.getEndOffset()) { + // Our purpose is to define folding data in order to point to the visual folding start. + int visualFoldingStartColumn = calculateVisualFoldingStartColumn(foldRegion); + foldColumnDiff = visualFoldingStartColumn - position.column - softWrapColumnDiff; + break; + } + + if (foldingEndLine != position.line) { + continue; + } + + // We know here that offset is at the same line where folding ends and is located after it. Hence, we process that as follows: + // 1. Check if the folding is single-line; + // 2.1. Process as follows if the folding is single-line: + // 3.1. Calculate column difference introduced by the folding; + // 2.2. Process as follows if the folding is multi-line: + // 3.2. Calculate visual column of folding start; + // 4.2. Calculate number of columns between target offset and folding end; + // 5.1. Calculate folding placeholder width in columns; + // 6.1. Calculate resulting offset visual column; + // 7.1. Calculate resulting folding column diff; + + if (foldingStartLine == foldingEndLine) { + foldColumnDiff = toVisualColumnSymbolsNumber(foldRegion.getPlaceholderText()) + - toVisualColumnSymbolsNumber(text, foldRegion.getStartOffset(), foldRegion.getEndOffset()); + } + else { + int endOffsetOfLineWithFoldingEnd = document.getLineEndOffset(foldingEndLine); + int columnsBetweenFoldingEndAndOffset = toVisualColumnSymbolsNumber(text, foldRegion.getEndOffset(), endOffsetOfLineWithFoldingEnd); + if (position.column > endOffsetOfLineWithFoldingEnd) { + columnsBetweenFoldingEndAndOffset += position.column - endOffsetOfLineWithFoldingEnd; + } + int visualFoldingStartColumn = calculateVisualFoldingStartColumn(foldRegion); + int foldingPlaceholderWidth = toVisualColumnSymbolsNumber(foldRegion.getPlaceholderText()); + int visual = columnsBetweenFoldingEndAndOffset + visualFoldingStartColumn + foldingPlaceholderWidth; + foldColumnDiff = visual - position.column; + break; + } + } + + return new LogicalPosition( + position.line, position.column, position.softWrapLines, position.linesFromActiveSoftWrap, + softWrapColumnDiff, foldedLines, foldColumnDiff + ); + } + + private int calculateVisualFoldingStartColumn(FoldRegion region) { + DocumentImpl document = (DocumentImpl)myEditor.getDocument(); + int foldingStartOffset = region.getStartOffset(); + int logicalLine = document.getLineNumber(foldingStartOffset); + int logicalLineStartOffset = document.getLineStartOffset(logicalLine); + + int softWrapIndex = getSoftWrapIndex(logicalLineStartOffset); + if (softWrapIndex < 0) { + softWrapIndex = -softWrapIndex - 1; + } + + int startOffsetOfVisualLineWithFoldingStart = logicalLineStartOffset; + for (; softWrapIndex < myWraps.size(); softWrapIndex++) { + TextChange softWrap = myWraps.get(softWrapIndex); + if (softWrap.getStart() >= foldingStartOffset) { + break; + } + + startOffsetOfVisualLineWithFoldingStart = softWrap.getStart(); + } + + assert startOffsetOfVisualLineWithFoldingStart <= foldingStartOffset; + return toVisualColumnSymbolsNumber(document.getCharsNoThreadCheck(), startOffsetOfVisualLineWithFoldingStart, foldingStartOffset); + } + + @NotNull + public LogicalPosition offsetToLogicalPosition(int offset) { + if (myActive > 0 || !isSoftWrappingEnabled()) { + return myEditor.offsetToLogicalPosition(offset, false); + } + + myActive++; + try { + return doOffsetToLogicalPosition(offset); + } finally { + myActive--; + } + } + + @SuppressWarnings({"AssignmentToForLoopParameter"}) + private LogicalPosition doOffsetToLogicalPosition(int offset) { + DocumentImpl document = (DocumentImpl)myEditor.getDocument(); + CharSequence chars = document.getCharsNoThreadCheck(); + + int targetLine = document.getLineNumber(offset); + int targetLineStartOffset = document.getLineStartOffset(targetLine); + + updateVisibleAreaIfNecessary(); + + int softWrapIntroducedLines = 0; + int linesFromCurrentSoftWrap = 0; + int symbolsOnCurrentLogicalLine = 0; + int symbolsOnCurrentVisibleLine = 0; + + // Retrieve information about logical position that is soft-wraps unaware. + LogicalPosition rawLineStartLogicalPosition = myEditor.offsetToLogicalPosition(targetLineStartOffset, false); + + // Calculate number of soft wrap-introduced lines before the line that holds target offset. + int index = getSoftWrapIndex(targetLineStartOffset); + if (index < 0) { + index = -index - 1; + } + int max = Math.min(index, myWraps.size()); + for (int j = 0; j < max; j++) { + softWrapIntroducedLines += StringUtil.countNewLines(myWraps.get(j).getText()); + } + + FoldingModel foldingModel = myEditor.getFoldingModel(); + + // Return eagerly if there are no soft wraps before the target offset on a line that contains it. + if (max >= myWraps.size() || myWraps.get(max).getStart() > offset) { + LogicalPosition foldingUnawarePosition = new LogicalPosition( + rawLineStartLogicalPosition.line, offset - targetLineStartOffset, softWrapIntroducedLines, 0, 0, 0, 0 + ); + return adjustFoldingData(foldingModel, foldingUnawarePosition); + } + + // Calculate number of lines and columns introduced by soft wrap located at the line that holds target offset if any. + + + // We add '1' here in order to correctly process situation when there is soft wrap at target offset (it impacts resulting logical + // position but document symbol at that offset should not be count). + max = Math.min(chars.length(), offset + 1); + + for (int i = targetLineStartOffset; i < max; i++) { + FoldRegion region = foldingModel.getCollapsedRegionAtOffset(i); + if (region != null) { + // Assuming that folded region placeholder doesn't contain line feed symbols. + i = region.getEndOffset(); + symbolsOnCurrentVisibleLine += region.getPlaceholderText().length(); + continue; + } + + TextChange softWrap = getSoftWrap(i); + if (softWrap != null) { + CharSequence softWrapText = softWrap.getText(); + for (int j = 0; j < softWrapText.length(); j++) { + if (softWrapText.charAt(j) == '\n') { + softWrapIntroducedLines++; + linesFromCurrentSoftWrap++; + symbolsOnCurrentVisibleLine = 0; + } + else { + symbolsOnCurrentVisibleLine++; + } + } + } + + // We don't want to count symbol at target offset. + if (i == offset) { + break; + } + + // Assuming that no line feed is contained before target offset on a line that holds it. + symbolsOnCurrentLogicalLine++; + symbolsOnCurrentVisibleLine++; + } + + LogicalPosition foldingUnawarePosition = new LogicalPosition( + rawLineStartLogicalPosition.line, symbolsOnCurrentLogicalLine, softWrapIntroducedLines, linesFromCurrentSoftWrap, + symbolsOnCurrentVisibleLine - symbolsOnCurrentLogicalLine, 0, 0 + ); + return adjustFoldingData(foldingModel, foldingUnawarePosition); + } + + @NotNull + public VisualPosition adjustVisualPosition(@NotNull LogicalPosition logical, @NotNull VisualPosition defaultVisual) { + if (myActive > 0 || !isSoftWrappingEnabled() || myWraps.isEmpty()) { + return defaultVisual; + } + + if (logical.visualPositionAware) { + // We don't need to recalculate logical position adjustments because given object already has them. + return logical.toVisualPosition(); + } + + updateVisibleAreaIfNecessary(); + myActive++; + try { + return doAdjustVisualPosition(logical, defaultVisual); + } + finally { + myActive--; + } + } + + @NotNull + private VisualPosition doAdjustVisualPosition(LogicalPosition logical, VisualPosition visual) { + // Check if there are registered soft wraps before the target logical position. + int maxOffset = myEditor.logicalPositionToOffset(logical); + int endIndex = getSoftWrapIndex(maxOffset); + if (endIndex < 0) { + endIndex = -endIndex - 2; // We subtract '2' instead of '1' here in order to point to offset of the first soft wrap the + // is located before the given logical position. + } + + // Return eagerly if no soft wraps are registered before the target offset. + if (endIndex < 0 || endIndex >= myWraps.size()) { + return visual; + } + + int lineDiff = 0; + int column = -1; + + FoldingModel foldingModel = myEditor.getFoldingModel(); + int targetLogicalLineStartOffset = myEditor.logicalPositionToOffset(new LogicalPosition(logical.line, 0)); + for (int i = endIndex; i >= 0; i--) { + TextChange softWrap = myWraps.get(i); + if (softWrap == null) { + assert false; + continue; + } + + if (foldingModel.isOffsetCollapsed(softWrap.getStart())) { + continue; + } + + CharSequence softWrapText = softWrap.getText(); + int softWrapLines = StringUtil.countNewLines(softWrapText); + + // Count lines introduced by the current soft wrap. We assume that the soft wrap is located before target offset, + // hence, we're free to count all of its line feeds. + lineDiff += softWrapLines; + + // Count soft wrap column offset only if it's located at the same line as the target offset. + if (softWrapLines > 0 && softWrap.getStart() >= targetLogicalLineStartOffset) { + for (int j = softWrapText.length() - 1; j >= 0; j--) { + if (softWrapText.charAt(j) == '\n') { + column = maxOffset - softWrap.getStart() - j + 1; + break; + } + } + } + } + + int columnToUse = column >= 0 ? column : visual.column; + return new VisualPosition(visual.line + lineDiff, columnToUse); + } + + public void beforeDocumentChange(@NotNull VisualPosition visualPosition) { + LogicalPosition logicalPosition = myEditor.visualToLogicalPosition(visualPosition); + int offset = myEditor.logicalPositionToOffset(logicalPosition); + int i = getSoftWrapIndex(offset); + if (i < 0 || i >= myWraps.size()) { + return; + } + + TextChange softWrap = myWraps.get(i); + + VisualPosition visualCaretPosition = myEditor.getCaretModel().getVisualPosition(); + + // Consider given visual position to belong to soft wrap-introduced virtual space if visual position for the target offset + // differs from the given. + if (!visualPosition.equals(myEditor.offsetToVisualPosition(offset))) { + myEditor.getDocument().replaceString(softWrap.getStart(), softWrap.getEnd(), softWrap.getText()); + } + + // Restore caret position. + myEditor.getCaretModel().moveToVisualPosition(visualCaretPosition); + myWraps.remove(i); + } + + /** + * //TODO den add doc + * + * @param change change introduced to the document + */ + private void updateRegisteredSoftWraps(TextChange change) { + int softWrapIndex = getSoftWrapIndex(change.getStart()); + if (softWrapIndex < 0) { + softWrapIndex = -softWrapIndex - 1; + } + + if (softWrapIndex >= myWraps.size()) { + return; + } + + Document document = myEditor.getDocument(); + int firstChangedLine = document.getLineNumber(change.getStart()); + int lastChangedLine = Math.max(document.getLineNumber(change.getEnd()), firstChangedLine + StringUtil.countNewLines(change.getText())); + for (int i = firstChangedLine; i <= lastChangedLine; i++) { + myDirtyLines.add(i); + } + + // Collect soft wraps which offsets should be modified. + List modified = myWraps.subList(softWrapIndex, myWraps.size()); + List toModify = new ArrayList(modified); + modified.clear(); + + // Add modified soft wraps. + for (TextChange softWrap : toModify) { + myWraps.add(softWrap.advance(change.getDiff())); + } + } + + private void initIfNecessary() { + if (myInitialized) { + return; + } + myInitialized = true; + + // Subscribe for visible area changes notifications. + ScrollingModel scrollingModel = myEditor.getScrollingModel(); + scrollingModel.addVisibleAreaListener(new VisibleAreaListener() { + public void visibleAreaChanged(VisibleAreaEvent e) { + updateVisibleAreaIfNecessary(e.getNewRectangle()); + } + }); + updateVisibleAreaIfNecessary(scrollingModel.getVisibleArea()); + + // Subscribe for document change events. + myEditor.getDocument().addDocumentListener(new DocumentListener() { + public void beforeDocumentChange(DocumentEvent event) { + } + + public void documentChanged(DocumentEvent event) { + myActive++; + try { + updateRegisteredSoftWraps(new TextChange(event.getNewFragment(), event.getOffset(), event.getOffset() + event.getOldLength())); + } + finally { + myActive--; + } + } + }); + } + + private void updateVisibleAreaIfNecessary() { + updateVisibleAreaIfNecessary(myEditor.getScrollingModel().getVisibleArea()); + } + + private void updateVisibleAreaIfNecessary(@Nullable Rectangle visibleArea) { + if (visibleArea == null || myActive > 0 || !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. + *

+ * 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 right edge. + int currentRightEdgeLocation = visibleArea.x + visibleArea.width; + if (myRightEdgeLocation != currentRightEdgeLocation) { + myDataIsDirty = true; + myRightEdgeLocation = currentRightEdgeLocation; + } + } + + private int toVisualColumnSymbolsNumber(char c) { + myCharBuffer.clear(); + myCharBuffer.put(c); + myCharBuffer.flip(); + return toVisualColumnSymbolsNumber(myCharBuffer, 0, 1); + } + + private int toVisualColumnSymbolsNumber(CharSequence text) { + return toVisualColumnSymbolsNumber(text, 0, text.length()); + } + + private int toVisualColumnSymbolsNumber(CharSequence text, int start, int end) { + return EditorUtil.calcColumnNumber(myEditor, text, start, end, EditorUtil.getTabSize(myEditor)); + } + + private class Context { + + public final FoldingModel foldingModel; + public final LogicalPosition softWrapUnawareLogicalPosition; + public final VisualPosition targetVisualPosition; + public final int softWrapIntroducedLines; + public final int visualLineBeforeSoftWrapAppliance; + public int lineFeedsFromCurrentSoftWrap; + public int symbolsOnCurrentLogicalLine; + public int symbolsOnCurrentVisualLine; + + Context(LogicalPosition softWrapUnawareLogicalPosition, VisualPosition targetVisualPosition, int softWrapIntroducedLines, + int visualLineBeforeSoftWrapAppliance, FoldingModel foldingModel) + { + this.softWrapUnawareLogicalPosition = softWrapUnawareLogicalPosition; + this.targetVisualPosition = targetVisualPosition; + this.softWrapIntroducedLines = softWrapIntroducedLines; + this.visualLineBeforeSoftWrapAppliance = visualLineBeforeSoftWrapAppliance; + this.foldingModel = foldingModel; + } + + /** + * Updates current context within the soft wrap symbol. + * + * @param c soft wrap symbol to process + * @return logical position that matches target visual position if given symbol processing makes it possible to calculate it; + * null otherwise + */ + @Nullable + public LogicalPosition onSoftWrapSymbol(char c) { + // Process line feed inside soft wrap. + if (c == '\n') { + if (targetVisualPosition.line == visualLineBeforeSoftWrapAppliance + lineFeedsFromCurrentSoftWrap) { + return build(targetVisualPosition.column - symbolsOnCurrentLogicalLine); + } + else { + lineFeedsFromCurrentSoftWrap++; + symbolsOnCurrentVisualLine = 0; + return null; + } + } + + // Just update information about tracked symbols number if current visual line is too low. + if (targetVisualPosition.line > visualLineBeforeSoftWrapAppliance + lineFeedsFromCurrentSoftWrap) { + symbolsOnCurrentVisualLine += toVisualColumnSymbolsNumber(c); + } + + // There is a possible case that, for example, target visual column is zero and it points to the soft-wrapped line, + // i.e. soft wrap are. We shouldn't count symbols then. Hence, we perform this preliminary examination with eager + // return if necessary. + if (targetVisualPosition.column <= symbolsOnCurrentVisualLine) { + return build(); + } + + // Process non-line feed inside soft wrap. + symbolsOnCurrentVisualLine += toVisualColumnSymbolsNumber(c); + if (targetVisualPosition.column <= symbolsOnCurrentVisualLine) { + return build(); + } + else { + return null; + } + } + + /** + * Updates current context within the non-soft wrap symbol. + * + * @param c soft wrap symbol to process + * @return logical position that matches target visual position if given symbol processing makes it possible to calculate it; + * null otherwise + */ + @Nullable + public LogicalPosition onNonSoftWrapSymbol(char c) { + // Don't expect line feed symbol to be delivered to this method in assumption that we process only one logical line here. + if (c == '\n') { + assert false; + return null; + } + + // Just update information about tracked symbols number if current visual line is too low. + if (targetVisualPosition.line > visualLineBeforeSoftWrapAppliance + lineFeedsFromCurrentSoftWrap) { + symbolsOnCurrentVisualLine += toVisualColumnSymbolsNumber(c); + symbolsOnCurrentLogicalLine++; + return null; + } + + // There is a possible case that, for example, target visual column is zero. We shouldn't count symbols then. + // Hence, we perform this preliminary examination with eager return if necessary. + if (targetVisualPosition.column <= symbolsOnCurrentVisualLine) { + return build(); + } + + symbolsOnCurrentVisualLine += toVisualColumnSymbolsNumber(c); + symbolsOnCurrentLogicalLine++; + + + if (targetVisualPosition.column <= symbolsOnCurrentVisualLine) { + return build(); + } + else { + return null; + } + } + + private LogicalPosition build() { + return build(symbolsOnCurrentVisualLine - symbolsOnCurrentLogicalLine); + } + + private LogicalPosition build(int softWrapColumnDiff) { + int logicalLine = softWrapUnawareLogicalPosition.line - softWrapIntroducedLines - lineFeedsFromCurrentSoftWrap; + LogicalPosition foldingUnawareResult = new LogicalPosition( + logicalLine, symbolsOnCurrentLogicalLine, softWrapIntroducedLines + lineFeedsFromCurrentSoftWrap, lineFeedsFromCurrentSoftWrap, + softWrapColumnDiff, 0, 0 + ); + return adjustFoldingData(foldingModel, foldingUnawareResult); + } + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/textarea/TextComponentEditor.java b/platform/platform-impl/src/com/intellij/openapi/editor/textarea/TextComponentEditor.java index 30b25e655a12..b0e9465fa2af 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/textarea/TextComponentEditor.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/textarea/TextComponentEditor.java @@ -43,6 +43,7 @@ public class TextComponentEditor extends UserDataHolderBase implements Editor { private final TextComponentCaretModel myCaretModel; private final TextComponentSelectionModel mySelectionModel; private final TextComponentScrollingModel myScrollingModel; + private final TextComponentSoftWrapModel mySoftWrapModel; private EditorSettings mySettings; public TextComponentEditor(final Project project, final JTextComponent textComponent) { @@ -57,6 +58,7 @@ public class TextComponentEditor extends UserDataHolderBase implements Editor { myCaretModel = new TextComponentCaretModel(textComponent, this); mySelectionModel = new TextComponentSelectionModel(textComponent, this); myScrollingModel = new TextComponentScrollingModel(textComponent); + mySoftWrapModel = new TextComponentSoftWrapModel(); } @NotNull @@ -103,6 +105,11 @@ public class TextComponentEditor extends UserDataHolderBase implements Editor { return myCaretModel; } + @NotNull + public SoftWrapModel getSoftWrapModel() { + return mySoftWrapModel; + } + @NotNull public EditorSettings getSettings() { if (mySettings == null) { diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/colors/impl/MacOSDefaultColorSchemeExtension.java b/platform/platform-impl/src/com/intellij/openapi/editor/textarea/TextComponentSoftWrapModel.java similarity index 53% rename from platform/platform-impl/src/com/intellij/openapi/editor/colors/impl/MacOSDefaultColorSchemeExtension.java rename to platform/platform-impl/src/com/intellij/openapi/editor/textarea/TextComponentSoftWrapModel.java index 860c8dbbe190..39e0fa40ed8c 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/colors/impl/MacOSDefaultColorSchemeExtension.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/textarea/TextComponentSoftWrapModel.java @@ -13,29 +13,27 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.intellij.openapi.editor.colors.impl; +package com.intellij.openapi.editor.textarea; -import com.intellij.openapi.util.SystemInfo; -import org.jetbrains.annotations.Nullable; +import com.intellij.openapi.editor.SoftWrapModel; +import com.intellij.openapi.editor.TextChange; +import com.intellij.openapi.editor.VisualPosition; +import org.jetbrains.annotations.NotNull; /** - * @author Roman.Chernyatchik + * @author Denis Zhdanov + * @since Jun 22, 2010 5:56:23 PM */ -public class MacOSDefaultColorSchemeExtension implements BundledColorSchemesProvider{ - @Override - @Nullable - public String[] getBundledSchemesRelativePaths() { +public class TextComponentSoftWrapModel implements SoftWrapModel { + + public boolean isSoftWrappingEnabled() { + return false; + } + + public TextChange getSoftWrap(int offset) { return null; } - @Override - @Nullable - public String getDefaultSchemaExtensionPath() { - - // Extends default color scheme settings on MacOS - if (!SystemInfo.isMac) { - return null; - } - return "colorSchemes/MacOSDefaultExt"; + public void beforeDocumentChange(@NotNull VisualPosition position) { } } diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/status/PositionPanel.java b/platform/platform-impl/src/com/intellij/openapi/wm/impl/status/PositionPanel.java index 805d7ea23557..0deea4849100 100644 --- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/status/PositionPanel.java +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/status/PositionPanel.java @@ -27,10 +27,10 @@ import com.intellij.openapi.editor.event.CaretEvent; import com.intellij.openapi.editor.event.CaretListener; import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.fileEditor.FileEditorManagerAdapter; +import com.intellij.openapi.fileEditor.FileEditorManagerEvent; import com.intellij.openapi.fileEditor.FileEditorManagerListener; import com.intellij.openapi.fileEditor.ex.IdeDocumentHistory; import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.wm.StatusBar; import com.intellij.openapi.wm.StatusBarWidget; import com.intellij.ui.UIBundle; @@ -49,8 +49,8 @@ public class PositionPanel implements StatusBarWidget, StatusBarWidget.TextPrese public PositionPanel(@NotNull final Project project) { project.getMessageBus().connect().subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, new FileEditorManagerAdapter() { @Override - public void fileOpened(@NotNull final FileEditorManager source, @NotNull final VirtualFile file) { - final Editor editor = source.getSelectedTextEditor(); + public void selectionChanged(final FileEditorManagerEvent event) { + final Editor editor = getEditor(); if (editor != null) updatePosition(editor); } }); @@ -140,7 +140,11 @@ public class PositionPanel implements StatusBarWidget, StatusBarWidget.TextPrese } public void caretPositionChanged(final CaretEvent e) { - updatePosition(e.getEditor()); + final Editor editor = e.getEditor(); + Project project = editor.getProject(); + if (project != null && FileEditorManager.getInstance(project).getSelectedTextEditor() == e.getEditor()) { + updatePosition(editor); + } } private void updatePosition(final Editor editor) { diff --git a/platform/platform-resources-en/src/messages/ApplicationBundle.properties b/platform/platform-resources-en/src/messages/ApplicationBundle.properties index 3d53c996f90d..8676e25d3ca4 100644 --- a/platform/platform-resources-en/src/messages/ApplicationBundle.properties +++ b/platform/platform-resources-en/src/messages/ApplicationBundle.properties @@ -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=When number of opened editors exceeds tab limit: 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 diff --git a/platform/platform-resources/src/META-INF/LangExtensionPoints.xml b/platform/platform-resources/src/META-INF/LangExtensionPoints.xml index 2dbbf4bd0f05..90f4aceaf5ed 100644 --- a/platform/platform-resources/src/META-INF/LangExtensionPoints.xml +++ b/platform/platform-resources/src/META-INF/LangExtensionPoints.xml @@ -121,8 +121,6 @@ - diff --git a/platform/platform-resources/src/META-INF/PlatformExtensions.xml b/platform/platform-resources/src/META-INF/PlatformExtensions.xml index 9cdb1680ac02..4f9d065bdca2 100644 --- a/platform/platform-resources/src/META-INF/PlatformExtensions.xml +++ b/platform/platform-resources/src/META-INF/PlatformExtensions.xml @@ -197,6 +197,5 @@ - diff --git a/platform/platform-resources/src/colorSchemes/MacOSDefaultExt.xml b/platform/platform-resources/src/colorSchemes/MacOSDefaultExt.xml deleted file mode 100644 index c999ef2cc6a5..000000000000 --- a/platform/platform-resources/src/colorSchemes/MacOSDefaultExt.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/platform/util/src/com/intellij/util/text/CharArrayUtil.java b/platform/util/src/com/intellij/util/text/CharArrayUtil.java index 9b27acfb3e0f..de477ee80d80 100644 --- a/platform/util/src/com/intellij/util/text/CharArrayUtil.java +++ b/platform/util/src/com/intellij/util/text/CharArrayUtil.java @@ -16,6 +16,7 @@ package com.intellij.util.text; import com.intellij.openapi.util.TextRange; +import org.jetbrains.annotations.Nullable; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -27,6 +28,9 @@ import java.util.List; public class CharArrayUtil { private static final int GET_CHARS_THRESHOLD = 10; + private CharArrayUtil() { + } + public static void getChars(CharSequence src, char[] dst, int dstOffset) { getChars(src, dst, dstOffset, src.length()); } @@ -66,11 +70,12 @@ public class CharArrayUtil { public static char[] fromSequenceStrict(CharSequence seq) { char[] chars = fromSequence(seq); if (seq.length() == chars.length) return chars; - char[] strictchars = new char[seq.length()]; - System.arraycopy(chars, 0, strictchars, 0, seq.length()); - return strictchars; + char[] strictChars = new char[seq.length()]; + System.arraycopy(chars, 0, strictChars, 0, seq.length()); + return strictChars; } + @Nullable public static char[] fromSequenceWithoutCopying(CharSequence seq) { if (seq instanceof CharSequenceBackedByArray) { return ((CharSequenceBackedByArray)seq).getChars(); @@ -94,10 +99,11 @@ public class CharArrayUtil { if (seq instanceof CharBuffer) { final CharBuffer buffer = (CharBuffer)seq; if (buffer.hasArray() && !buffer.isReadOnly() && buffer.arrayOffset() == 0) { - final char[] bufArray = buffer.array(); - /* return larger array. Clients may use seq.length() to calculate correct processing range. - if (bufArray.length == seq.length()) - */ return bufArray; + return buffer.array(); + // final char[] bufArray = buffer.array(); + // return larger array. Clients may use seq.length() to calculate correct processing range. + // if (bufArray.length == seq.length()) + // return bufArray; } char[] chars = new char[seq.length()]; @@ -180,9 +186,11 @@ public class CharArrayUtil { return shiftBackward(new CharArrayCharSequence(buffer), offset, chars); } - public static int shiftForwardUntil(char[] buffer, int offset, String chars) { - return shiftForwardUntil(new CharArrayCharSequence(buffer), offset, chars); - } + //Commented in order to apply to green code policy as the method is unused. + // + //public static int shiftForwardUntil(char[] buffer, int offset, String chars) { + // return shiftForwardUntil(new CharArrayCharSequence(buffer), offset, chars); + //} public static int shiftForwardUntil(CharSequence buffer, int offset, String chars) { while (true) { @@ -198,10 +206,37 @@ public class CharArrayUtil { return offset; } - public static int shiftBackwardUntil(char[] buffer, int offset, String chars) { - return shiftBackwardUntil(new CharArrayCharSequence(buffer), offset, chars); - } + //Commented in order to apply to the green code policy as the method is unused. + // + //public static int shiftBackwardUntil(char[] buffer, int offset, String chars) { + // return shiftBackwardUntil(new CharArrayCharSequence(buffer), offset, chars); + //} + /** + * Calculates offset that points to the given buffer and has the following characteristics: + *

+ *

    + *
  • is less than or equal to the given offset;
  • + *
  • + * it's guaranteed that all symbols of the given buffer that are located at (returned offset; given offset] + * interval differ from the given symbols; + *
  • + *
+ *

+ * Example: suppose that this method is called with buffer that holds 'test data' symbols, offset that points + * to the last symbols and 'sf' as a chars to exclude. Offset that points to 's' symbol + * is returned then, i.e. all symbols of the given buffer that are located after it and not after given offset + * ('t data') are guaranteed to not contain given chars ('sf'). + * + * @param buffer symbols buffer to check + * @param offset initial symbols buffer offset to use + * @param chars chars to exclude + * @return offset of the given buffer that guarantees that all symbols at (returned offset; given offset] + * interval of the given buffer differ from symbols of given 'chars' arguments; + * given offset is returned if it is outside of given buffer bounds; + * '-1' is returned if all document symbols that precede given offset differ from symbols + * of the given 'chars to exclude' + */ public static int shiftBackwardUntil(CharSequence buffer, int offset, String chars) { if (offset >= buffer.length()) return offset; while (true) { @@ -341,8 +376,12 @@ public class CharArrayUtil { public static byte[] toByteArray(char[] chars, int size) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); OutputStreamWriter writer = new OutputStreamWriter(out); - writer.write(chars, 0, size); - writer.close(); + try { + writer.write(chars, 0, size); + } + finally { + writer.close(); + } return out.toByteArray(); } @@ -356,15 +395,17 @@ public class CharArrayUtil { return true; } - public static boolean subArraysEqual(char[] ca1, int startOffset1, int endOffset1,char[] ca2, int startOffset2, int endOffset2) { - if (endOffset1 - startOffset1 != endOffset2 - startOffset2) return false; - for (int i = startOffset1; i < endOffset1; i++) { - char c1 = ca1[i]; - char c2 = ca2[i - startOffset1 + startOffset2]; - if (c1 != c2) return false; - } - return true; - } + //Commented in order to apply to green code policy as the method is unused. + // + //public static boolean subArraysEqual(char[] ca1, int startOffset1, int endOffset1,char[] ca2, int startOffset2, int endOffset2) { + // if (endOffset1 - startOffset1 != endOffset2 - startOffset2) return false; + // for (int i = startOffset1; i < endOffset1; i++) { + // char c1 = ca1[i]; + // char c2 = ca2[i - startOffset1 + startOffset2]; + // if (c1 != c2) return false; + // } + // return true; + //} public static TextRange[] getIndents(CharSequence charsSequence, int shift) { List result = new ArrayList(); diff --git a/plugins/git4idea/src/META-INF/plugin.xml b/plugins/git4idea/src/META-INF/plugin.xml index e6d0dee20e1d..e6b24f8a977b 100644 --- a/plugins/git4idea/src/META-INF/plugin.xml +++ b/plugins/git4idea/src/META-INF/plugin.xml @@ -98,6 +98,9 @@ + @@ -115,8 +118,5 @@ git4idea.history.browser.GitProjectLogManager - - git4idea.history.GitUsersComponent - diff --git a/plugins/git4idea/src/git4idea/checkin/GitCheckinEnvironment.java b/plugins/git4idea/src/git4idea/checkin/GitCheckinEnvironment.java index d1433436fcc5..c622bd070992 100644 --- a/plugins/git4idea/src/git4idea/checkin/GitCheckinEnvironment.java +++ b/plugins/git4idea/src/git4idea/checkin/GitCheckinEnvironment.java @@ -638,7 +638,9 @@ public class GitCheckinEnvironment implements CheckinEnvironment { final GitUsersComponent component = GitUsersComponent.getInstance(project); final Set result = new HashSet(); for (VirtualFile root : roots) { - result.addAll(component.getUsersList(root)); + final List list = component.getUsersList(root); + if (list == null) continue; + result.addAll(list); } return result; } diff --git a/plugins/testng/src/com/theoryinpractice/testng/inspection/JUnitConvertTool.java b/plugins/testng/src/com/theoryinpractice/testng/inspection/JUnitConvertTool.java index bf0c2636550b..8717dd343e04 100644 --- a/plugins/testng/src/com/theoryinpractice/testng/inspection/JUnitConvertTool.java +++ b/plugins/testng/src/com/theoryinpractice/testng/inspection/JUnitConvertTool.java @@ -16,7 +16,6 @@ package com.theoryinpractice.testng.inspection; import com.intellij.codeInsight.CodeInsightUtilBase; -import com.intellij.codeInsight.actions.OptimizeImportsProcessor; import com.intellij.codeInspection.*; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; @@ -196,7 +195,7 @@ public class JUnitConvertTool extends BaseJavaLocalInspectionTool { element.delete(); } } - new OptimizeImportsProcessor(project, javaFile).run(); //delete unused imports + JavaCodeStyleManager.getInstance(project).optimizeImports(javaFile);//delete unused imports } catch (IncorrectOperationException e) { LOG.error("Error converting testcase", e);