From 95619766f3ee3ddd60cc9a95cff4a8ba30ff04d3 Mon Sep 17 00:00:00 2001 From: irengrig Date: Thu, 24 Jun 2010 13:21:35 +0400 Subject: [PATCH 01/20] IDEA-55997 NPE at git4idea.checkin.GitCheckinEnvironment$GitCheckinOptions.getUsersList --- plugins/git4idea/src/META-INF/plugin.xml | 6 +++--- .../src/git4idea/checkin/GitCheckinEnvironment.java | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) 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; } From 456401833f4eb8639c141f90b3b0d07917103960 Mon Sep 17 00:00:00 2001 From: Roman Chernyatchik Date: Thu, 24 Jun 2010 13:15:55 +0400 Subject: [PATCH 02/20] red spelling highlighting was disabled --- .../MacOSDefaultColorSchemeExtension.java | 41 ------------------- .../src/META-INF/PlatformExtensions.xml | 1 - .../src/colorSchemes/MacOSDefaultExt.xml | 15 ------- 3 files changed, 57 deletions(-) delete mode 100644 platform/platform-impl/src/com/intellij/openapi/editor/colors/impl/MacOSDefaultColorSchemeExtension.java delete mode 100644 platform/platform-resources/src/colorSchemes/MacOSDefaultExt.xml diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/colors/impl/MacOSDefaultColorSchemeExtension.java b/platform/platform-impl/src/com/intellij/openapi/editor/colors/impl/MacOSDefaultColorSchemeExtension.java deleted file mode 100644 index 860c8dbbe190..000000000000 --- a/platform/platform-impl/src/com/intellij/openapi/editor/colors/impl/MacOSDefaultColorSchemeExtension.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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.colors.impl; - -import com.intellij.openapi.util.SystemInfo; -import org.jetbrains.annotations.Nullable; - -/** - * @author Roman.Chernyatchik - */ -public class MacOSDefaultColorSchemeExtension implements BundledColorSchemesProvider{ - @Override - @Nullable - public String[] getBundledSchemesRelativePaths() { - return null; - } - - @Override - @Nullable - public String getDefaultSchemaExtensionPath() { - - // Extends default color scheme settings on MacOS - if (!SystemInfo.isMac) { - return null; - } - return "colorSchemes/MacOSDefaultExt"; - } -} 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 From d2576e4aea2aa86210a8c31820d8e5ec1d9b02b2 Mon Sep 17 00:00:00 2001 From: anna Date: Thu, 24 Jun 2010 13:40:22 +0400 Subject: [PATCH 03/20] inline: do not warn about non existing write usages --- .../refactoring/inline/InlineLocalHandler.java | 16 ++++++++++++++-- .../inlineLocal/NonWriteUnaryExpression.java | 7 +++++++ .../NonWriteUnaryExpression.java.after | 6 ++++++ .../refactoring/inline/InlineLocalTest.java | 4 ++++ 4 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 java/java-tests/testData/refactoring/inlineLocal/NonWriteUnaryExpression.java create mode 100644 java/java-tests/testData/refactoring/inlineLocal/NonWriteUnaryExpression.java.after 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); } From ea094049da79e73a833704608e42e539cc94c501 Mon Sep 17 00:00:00 2001 From: anna Date: Thu, 24 Jun 2010 13:44:22 +0400 Subject: [PATCH 04/20] copy: check copy with new name if provided --- .../refactoring/copy/CopyFilesOrDirectoriesHandler.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) 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..9d21bf736bc9 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,21 @@ public class CopyFilesOrDirectoriesHandler implements CopyHandlerDelegate { CopyFilesOrDirectoriesDialog dialog = new CopyFilesOrDirectoriesDialog(elements, defaultTargetDirectory, project, false); dialog.show(); if (dialog.isOK()) { + String newName = elements.length == 1 ? dialog.getNewName() : null; final PsiManager psiManager = PsiManager.getInstance(project); try { for (PsiElement element : elements) { - psiManager.checkMove(element, dialog.getTargetDirectory()); + final PsiFileSystemItem psiElement = (PsiFileSystemItem)element.copy(); + if (newName != null) { + psiElement.setName(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); } } From 7aef38ff7c25c3cd02a1a59c0b76ad849b350ab1 Mon Sep 17 00:00:00 2001 From: Konstantin Bulenkov Date: Thu, 24 Jun 2010 13:58:31 +0400 Subject: [PATCH 05/20] make it flexible for overriding --- .../ide/actions/NewElementAction.java | 77 ++++++++++++++----- 1 file changed, 57 insertions(+), 20 deletions(-) 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); } } From 49bbd94d9c3d5a425512c5fd6e0bdfd377e026bc Mon Sep 17 00:00:00 2001 From: Alexey Pegov Date: Thu, 24 Jun 2010 14:00:51 +0400 Subject: [PATCH 06/20] more accurate position panel updates --- .../openapi/wm/impl/status/PositionPanel.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) 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..797dbc916eef 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,10 @@ public class PositionPanel implements StatusBarWidget, StatusBarWidget.TextPrese } public void caretPositionChanged(final CaretEvent e) { - updatePosition(e.getEditor()); + final Editor editor = e.getEditor(); + if (FileEditorManager.getInstance(editor.getProject()).getSelectedTextEditor() == e.getEditor()) { + updatePosition(editor); + } } private void updatePosition(final Editor editor) { From 5d62d5cd9b1117194e550b2ccca6440a356c7491 Mon Sep 17 00:00:00 2001 From: Konstantin Bulenkov Date: Thu, 24 Jun 2010 14:01:57 +0400 Subject: [PATCH 07/20] Empty group --- .../com/intellij/openapi/actionSystem/ActionGroup.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) 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 +} From 7b3e09ed8aea949a96cef40fe2478a9a4cd49d1e Mon Sep 17 00:00:00 2001 From: anna Date: Thu, 24 Jun 2010 14:10:29 +0400 Subject: [PATCH 08/20] copy: check copy with new name if possible --- .../copy/CopyFilesOrDirectoriesHandler.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) 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 9d21bf736bc9..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,13 +78,18 @@ public class CopyFilesOrDirectoriesHandler implements CopyHandlerDelegate { CopyFilesOrDirectoriesDialog dialog = new CopyFilesOrDirectoriesDialog(elements, defaultTargetDirectory, project, false); dialog.show(); if (dialog.isOK()) { - String newName = elements.length == 1 ? dialog.getNewName() : null; + final String newName = elements.length == 1 ? dialog.getNewName() : null; final PsiManager psiManager = PsiManager.getInstance(project); try { for (PsiElement element : elements) { - final PsiFileSystemItem psiElement = (PsiFileSystemItem)element.copy(); + PsiFileSystemItem psiElement = (PsiFileSystemItem)element; if (newName != null) { - psiElement.setName(newName); + if (!psiElement.isDirectory()) { + psiElement = (PsiFileSystemItem)psiElement.copy(); + psiElement.setName(newName); + } else { + dialog.getTargetDirectory().checkCreateSubdirectory(newName); + } } psiManager.checkMove(psiElement, dialog.getTargetDirectory()); } From 653b03ce87706f0875f11398d7cefdb389664324 Mon Sep 17 00:00:00 2001 From: Maxim Shafirov Date: Wed, 23 Jun 2010 12:42:07 +0400 Subject: [PATCH 09/20] Making sure all read requests are processed in fair manner. --- .../execution/process/OSProcessHandler.java | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) 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()) { From bbae9c1449c4c5058c8422a7c5aeb84c70b44654 Mon Sep 17 00:00:00 2001 From: Maxim Shafirov Date: Thu, 24 Jun 2010 14:17:05 +0400 Subject: [PATCH 10/20] Alternative scrollbar ui in a few places. Still work in progress though. --- .../openapi/ui/popup/PopupChooserBuilder.java | 5 +- .../util/ui/ButtonlessScrollBarUI.java | 210 ++++++++++++++++++ .../openapi/editor/impl/EditorImpl.java | 6 + 3 files changed, 220 insertions(+), 1 deletion(-) create mode 100644 platform/platform-api/src/com/intellij/util/ui/ButtonlessScrollBarUI.java 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/impl/EditorImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java index 083208f8984c..2c72189e1f16 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 @@ -64,6 +64,7 @@ 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; @@ -3023,6 +3024,11 @@ 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 From cce6b7a7b929fc91602a5be8509eff3d0b63e417 Mon Sep 17 00:00:00 2001 From: Denis Zhdanov Date: Mon, 7 Jun 2010 18:05:21 +0400 Subject: [PATCH 11/20] IDEA-53596 Soft wrap for editors 1. Javadocs are added; 2. Green code policy is applied; 3. Typos are corrected; --- .../openapi/editor/ex/MarkupModelEx.java | 3 +- .../openapi/editor/ex/util/SegmentArray.java | 9 +- .../editor/ex/util/SegmentArrayWithData.java | 6 ++ .../editor/impl/EditorMarkupModelImpl.java | 5 ++ .../openapi/editor/impl/IterationState.java | 2 +- .../intellij/openapi/editor/impl/LineSet.java | 5 +- .../com/intellij/util/text/CharArrayUtil.java | 89 ++++++++++++++----- 7 files changed, 91 insertions(+), 28 deletions(-) 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/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/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/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/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(); From dbfead834d9ea04a2bc6b332218faddbad0d05c1 Mon Sep 17 00:00:00 2001 From: Denis Zhdanov Date: Fri, 11 Jun 2010 17:40:06 +0400 Subject: [PATCH 12/20] IDEA-53596 Soft wrap for editors First soft wraps implementation iteration. Current state: * soft wraps are represented at the editor during its repainting; * line numbers representations is consistent with soft-wrapped lines presence; * logical position representation is consistent with soft-wrapped lines presence; --- .../options/editor/EditorOptionsPanel.form | 19 +- .../options/editor/EditorOptionsPanel.java | 4 + .../openapi/editor/EditorSettings.java | 4 +- .../openapi/editor/LogicalPosition.java | 36 +- .../openapi/editor/SoftWrapModel.java | 123 ++++++ .../intellij/openapi/editor/TextChange.java | 175 +++++++++ .../ex/EditorSettingsExternalizable.java | 9 + .../openapi/editor/impl/CaretModelImpl.java | 21 +- .../impl/EditorGutterComponentImpl.java | 6 +- .../openapi/editor/impl/EditorImpl.java | 233 +++++++++-- .../openapi/editor/impl/SettingsImpl.java | 13 + .../editor/impl/SoftWrapModelImpl.java | 365 ++++++++++++++++++ .../src/messages/ApplicationBundle.properties | 1 + 13 files changed, 956 insertions(+), 53 deletions(-) create mode 100644 platform/platform-api/src/com/intellij/openapi/editor/SoftWrapModel.java create mode 100644 platform/platform-api/src/com/intellij/openapi/editor/TextChange.java create mode 100644 platform/platform-impl/src/com/intellij/openapi/editor/impl/SoftWrapModelImpl.java 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/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..aa203871aeb1 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,8 @@ import org.jetbrains.annotations.NonNls; * Represents a logical position in the editor. Logical positions ignore folding - * for example, if the top 10 lines of the document are folded, the 10th line in the document * will have the line number 10 in its logical position. + *

+ * //TODO den add doc equals/compareTo() behavior difference because of soft wraps. * * @see Editor#offsetToLogicalPosition(int) * @see Editor#logicalPositionToOffset(LogicalPosition) @@ -33,30 +35,56 @@ import org.jetbrains.annotations.NonNls; public class LogicalPosition implements Comparable { public final int line; public final int column; + //TODO den add doc + public final int softWrapLines; + //TODO den add doc + public final int linesFromActiveSoftWrap; + //TODO den add doc + public final int softWrapColumns; public LogicalPosition(int line, int column) { + this(line, column, 0, 0, 0); + } + + public LogicalPosition(int line, int column, int softWrapLines, int linesFromActiveSoftWrap, int softWrapColumns) { + assert linesFromActiveSoftWrap <= softWrapLines; + this.line = line; this.column = column; + this.softWrapLines = softWrapLines; + this.linesFromActiveSoftWrap = linesFromActiveSoftWrap; + this.softWrapColumns = softWrapColumns; + } + + //TODO den add doc + public boolean isOnSoftWrappedLine() { + return softWrapColumns != 0; } public boolean equals(Object o) { if (!(o instanceof LogicalPosition)) return false; final LogicalPosition logicalPosition = (LogicalPosition) o; - return column == logicalPosition.column && line == logicalPosition.line; + return column == logicalPosition.column && line == logicalPosition.line && softWrapLines == logicalPosition.softWrapLines + && softWrapColumns == logicalPosition.softWrapColumns; } public int hashCode() { - return 29 * line + column; + int result = 29 * line + column; + result = result * 29 + softWrapLines; + return 29 * result + softWrapColumns; } @NonNls public String toString() { - return "LogicalPosition: line = " + line + " column = " + column; + return "LogicalPosition: line = " + line + " column = " + column + "; soft wrap: lines = " + softWrapLines + + " (active = " + linesFromActiveSoftWrap + ") columns = " + softWrapColumns; } public int compareTo(LogicalPosition position) { if (line != position.line) return line - position.line; - return column - position.column; + if (column != position.column) return column - position.column; + if (softWrapLines != position.softWrapLines) return softWrapLines - position.softWrapLines; + return softWrapColumns - position.softWrapColumns; } } 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..bf391b437bd5 --- /dev/null +++ b/platform/platform-api/src/com/intellij/openapi/editor/SoftWrapModel.java @@ -0,0 +1,123 @@ +/* + * Copyright 2000-2010 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.editor; + +import gnu.trove.TIntObjectHashMap; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; + +/** + * Defines contract for the services that bring '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(); + + /** + * Allows to retrieve number of lines feeds from 'soft wraps' located before the given visual position. + *

+ * There is a possible case that given visual position points to location inside soft wrap - line feeds of that soft wrap + * are not counted then. + *

+ * Example: + *

+ *

+   *     public class Test {                      | <- right margin
+   *                                              |
+   *         public void baz() {                  |
+   *             foo("test1", "test2", "test3", [p1]
+   *  [p2            "test4", "test5", "test6",   |
+   *                 "test7", "test8", "test8"); [p3]
+   *         }                                    |
+   *                                              |
+   *  [p4]   public void foo(Object ... args) {   |
+   *         }                                    |
+   *     }                                        |
+   *      
+ *

+ * Here all 'foo()' call arguments are soft wrapped into three lines. However zero is returned for visual position + * 'p1'-'p3' because they are located inside soft wrap; + * '2' is returned for 'p4'. + * + * @param position target visual position + * @return number of lines feeds from 'soft wraps' located before the given visual position if any; + * 0 otherwise + */ + int getSoftWrapLineFeedsBefore(VisualPosition position); + + /** + * Asks current model for the soft wrap registered for the given document offset if any. + * + * @param offset target document offset + * @return soft wrap registered for the given offset within the current model if any; null otherwise + */ + @Nullable + TextChange getSoftWrap(int offset); + + LogicalPosition adjustLogicalPositionIfNecessary(LogicalPosition logical, VisualPosition visual); +} 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-impl/src/com/intellij/openapi/editor/ex/EditorSettingsExternalizable.java b/platform/platform-impl/src/com/intellij/openapi/editor/ex/EditorSettingsExternalizable.java index 88773955bd3a..473734e1a5cf 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 = true; public boolean IS_VIRTUAL_SPACE = true; public boolean IS_CARET_INSIDE_TABS; @NonNls public String STRIP_TRAILING_SPACES = "Changed"; @@ -211,6 +212,14 @@ public class EditorSettingsExternalizable implements NamedJDOMExternalizable, Ex myOptions.SMART_HOME = val; } + public boolean isUseSoftWraps() { + return myOptions.USE_SOFT_WRAPS; + } + + public void setUseSoftWraps(boolean use) { + myOptions.USE_SOFT_WRAPS = use; + } + public boolean isVirtualSpace() { return myOptions.IS_VIRTUAL_SPACE; } 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..a8fe1d75119b 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 @@ -227,11 +227,21 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener { validateCallContext(); int column = pos.column; int line = pos.line; + int softWrapLines = pos.softWrapLines; + int linesFromCurrentSoftWrap = pos.linesFromActiveSoftWrap; + int softWrapColumns = pos.softWrapColumns; Document doc = myEditor.getDocument(); - if (column < 0) column = 0; - if (line < 0) line = 0; + if (column < 0) { + column = 0; + softWrapColumns = 0; + } + if (line < 0) { + line = 0; + softWrapLines = 0; + linesFromCurrentSoftWrap = 0; + } int lineCount = doc.getLineCount(); if (lineCount == 0) { @@ -239,6 +249,8 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener { } else if (line > lineCount - 1) { line = lineCount - 1; + softWrapLines = 0; + linesFromCurrentSoftWrap = 0; } EditorSettings editorSettings = myEditor.getSettings(); @@ -248,6 +260,9 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener { int lineEndColumnNumber = myEditor.offsetToLogicalPosition(lineEndOffset).column; if (column > lineEndColumnNumber) { column = lineEndColumnNumber; + if (softWrapColumns != 0) { + softWrapColumns -= column - lineEndColumnNumber; + } } } @@ -257,7 +272,7 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener { LogicalPosition oldCaretPosition = myLogicalCaret; - myLogicalCaret = new LogicalPosition(line, column); + myLogicalCaret = new LogicalPosition(line, column, softWrapLines, linesFromCurrentSoftWrap, softWrapColumns); final int offset = myEditor.logicalPositionToOffset(myLogicalCaret); 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..783e66fad3fc 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 @@ -327,7 +327,11 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse } for (int i = startLineNumber; i < endLineNumber; i++) { - int logLine = myEditor.visualToLogicalPosition(new VisualPosition(i, 0)).line; + LogicalPosition logicalPosition = myEditor.visualToLogicalPosition(new VisualPosition(i, 0)); + if (logicalPosition.isOnSoftWrappedLine()) { + continue; + } + int logLine = logicalPosition.line; String s = String.valueOf(logLine + 1); g.drawString(s, getLineNumberAreaOffset() + getLineNumberAreaWidth() - 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 2c72189e1f16..6b0ee8732f63 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; @@ -68,7 +69,7 @@ import com.intellij.util.ui.ButtonlessScrollBarUI; import com.intellij.util.ui.EmptyClipboardOwner; import com.intellij.util.ui.UIUtil; import com.intellij.util.ui.update.UiNotifyConnector; -import gnu.trove.TIntArrayList; +import gnu.trove.*; import org.jdom.Element; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; @@ -157,6 +158,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi private final FoldingModelImpl myFoldingModel; private final ScrollingModelImpl myScrollingModel; private final CaretModelImpl myCaretModel; + private final SoftWrapModelImpl mySoftWrapModel; private static final RepaintCursorCommand ourCaretBlinkingCommand; @@ -173,7 +175,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi private EditorHighlighter myHighlighter; - private int myScrollbarOrientation; + private int myScrollBarOrientation; private boolean myMousePressedInsideSelection; private FontMetrics myPlainFontMetrics; private FontMetrics myBoldFontMetrics; @@ -243,6 +245,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi myMarkupModel = new EditorMarkupModelImpl(this); myFoldingModel = new FoldingModelImpl(this); myCaretModel = new CaretModelImpl(this); + mySoftWrapModel = new SoftWrapModelImpl(this); mySizeContainer.reset(); myCommandProcessor = CommandProcessor.getInstance(); @@ -316,7 +319,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi myCaretCursor = new CaretCursor(); myFoldingModel.flushCaretShift(); - myScrollbarOrientation = VERTICAL_SCROLLBAR_RIGHT; + myScrollBarOrientation = VERTICAL_SCROLLBAR_RIGHT; EditorHighlighter highlighter = new EmptyEditorHighlighter(myScheme.getAttributes(HighlighterColors.TEXT)); setHighlighter(highlighter); @@ -611,11 +614,11 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi final DropTarget dropTarget = myEditorComponent.getDropTarget(); if (dropTarget != null) { // might be null in headless environment dropTarget.addDropTargetListener(new DropTargetAdapter() { - public void drop(DropTargetDropEvent dtde) { + public void drop(DropTargetDropEvent e) { } - public void dragOver(DropTargetDragEvent dtde) { - Point location = dtde.getLocation(); + public void dragOver(DropTargetDragEvent e) { + Point location = e.getLocation(); moveCaretToScreenPos(location.x, location.y); getScrollingModel().scrollToCaret(ScrollType.RELATIVE); @@ -782,11 +785,19 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi } } - int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line, 0))); + LogicalPosition logicalPosition = visualToLogicalPosition(new VisualPosition(line, 0)); + int offset = logicalPositionToOffset(logicalPosition); int textLength = myDocument.getTextLength(); if (offset >= textLength) return new VisualPosition(line, 0); + // There is a possible case that starting logical line is split by soft-wraps and it's part after the split should be drawn. + // We mark that we're under such circumstances then. + boolean activeSoftWrapProcessed = true; + if (logicalPosition.isOnSoftWrappedLine()) { + activeSoftWrapProcessed = false; + } + int column = 0; int prevX = 0; CharSequence text = myDocument.getCharsNoThreadCheck(); @@ -799,7 +810,9 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi int x = 0; outer: while (true) { - if (offset >= textLength) break; + if (offset >= textLength) { + break; + } if (offset >= state.getEndOffset()) { state.advance(); @@ -812,32 +825,56 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi for (char aPlaceholder : placeholder) { c = aPlaceholder; x += EditorUtil.charWidth(c, fontType, this); - if (x >= px) break outer; + if (x >= px) { + break outer; + } column++; } offset = region.getEndOffset(); } else { + TextChange softWrap = mySoftWrapModel.getSoftWrap(offset); + if (softWrap != null) { + // There is a possible case that soft wrap contains more than one line feed inside and we need to start counting not + // from its first line. + int softWrapLinesToSkip = activeSoftWrapProcessed ? 0 : logicalPosition.linesFromActiveSoftWrap; + CharSequence softWrapText = softWrap.getText(); + for (int i = 0; i < softWrapText.length(); i++) { + c = softWrapText.charAt(i); + if (softWrapLinesToSkip > 0) { + if (c == '\n') { + softWrapLinesToSkip--; + } + continue; + } + prevX = x; + int diff = charToVisibleWidth(c, fontType, x); + if (diff == 0) { + break outer; + } + + x += diff; + if (x >= px) { + break outer; + } + column += columnsNumber(c, x, prevX, spaceSize); + } + activeSoftWrapProcessed = true; + } + prevX = x; c = text.charAt(offset); - if (c == '\n') { + int diff = charToVisibleWidth(c, fontType, x); + if (diff == 0) { + break; + } else { + x += diff; + } + + if (x >= px) { break; } - if (c == '\t') { - x = EditorUtil.nextTabStop(x, this); - } - else { - x += EditorUtil.charWidth(c, fontType, this); - } - - if (x >= px) break; - - if (c == '\t') { - column += (x - prevX) / spaceSize; - } - else { - column++; - } + column += columnsNumber(c, x, prevX, spaceSize); offset++; } @@ -868,6 +905,42 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi return new VisualPosition(line, column); } + /** + * Allows to answer how much width requires given char to be represented on a screen. + * + * @param c target character + * @param fontType font type to use for representation of the given character + * @param currentX current '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)); @@ -1031,7 +1104,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi return myGutterComponent != null && myGutterComponent.isShowing(); } - private void repaintToScreenBotton(int startLine) { + private void repaintToScreenBottom(int startLine) { Rectangle visibleRect = getScrollingModel().getVisibleArea(); int yStartLine = logicalLineToY(startLine); int yEndLine = visibleRect.y + visibleRect.height; @@ -1082,7 +1155,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi if (countLineFeeds(e.getOldFragment()) != countLineFeeds(e.getNewFragment())) { // Lines removed. Need to repaint till the end of the screen - repaintToScreenBotton(startLine); + repaintToScreenBottom(startLine); painted = true; } } @@ -1598,7 +1671,8 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi int visibleLineNumber = clip.y / lineHeight; - int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line; + LogicalPosition logicalPosition = xyToLogicalPosition(new Point(0, clip.y)); + int startLineNumber = logicalPosition.line; Point position = new Point(0, visibleLineNumber * lineHeight); if (startLineNumber == 0 && myPrefixText != null) { @@ -1610,7 +1684,16 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi return; } + // There is a possible case that starting logical line is split by soft-wraps and it's part after the split should be drawn. + // We do the following then: + // 1. Mark that we're under such circumstances ('activeSoftWrapProcessed = false'); + // 2. Use active soft wrap offset as a start offset in order to paint its symbols in correct way; + boolean activeSoftWrapProcessed = true; int start = myDocument.getLineStartOffset(startLineNumber); + if (logicalPosition.isOnSoftWrappedLine()) { + start = logicalPositionToOffset(logicalPosition); + activeSoftWrapProcessed = false; + } IterationState iterationState = new IterationState(this, start, paintSelection()); @@ -1640,7 +1723,9 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi drawString(g, chars, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor, effectType, fontType, currentColor); position.x = 0; - if (position.y > clip.y + clip.height) break; + if (position.y > clip.y + clip.height) { + break; + } position.y += lineHeight; start = lEnd; } @@ -1663,6 +1748,63 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi fontType, currentColor); } else { + boolean breakWholeIteration = false; + if (mySoftWrapModel.shouldWrap(chars, start, hEnd, position)) { + TextChange softWrap = mySoftWrapModel.wrap(start); + + // There is a possible case that soft wrap contains more than one line feed inside and we need to start drawing not + // from its first line. + int softWrapLinesToSkip = activeSoftWrapProcessed ? 0 : logicalPosition.linesFromActiveSoftWrap; + char[] softWrapChars = softWrap.getChars(); + + // We don't draw every soft wrap symbol one-by-one but whole visual line. Current variable holds index that points + // to the first soft wrap symbol that is not drawn yet. + int softWrapSegmentStartIndex = 0; + for (int i = 0; i < softWrapChars.length; i++) { + // Delay soft wraps symbols drawing until EOL if found. + if (softWrapChars[i] != '\n') { + continue; + } + + // Skip top soft wrap lines if necessary. + if (softWrapLinesToSkip-- > 0) { + position.x = 0; + softWrapSegmentStartIndex = i + 1; + continue; + } + + // Draw soft wrap symbols on current visual line if any. + if (i - softWrapSegmentStartIndex > 0) { + drawString( + g, softWrapChars, softWrapSegmentStartIndex, i, position, clip, currentColor, effectType, fontType, currentColor + ); + } + + // Reset 'x' coordinate because of new line start. + position.x = 0; + + // Stop the processing if we drew the whole clip. + if (position.y > clip.y + clip.height) { + breakWholeIteration = true; + break; + } + position.y += lineHeight; + softWrapSegmentStartIndex = i + 1; + } + + // Draw remaining soft wrap symbols from its last line if any. + if (softWrapSegmentStartIndex < softWrapChars.length - 1) { + position.x += drawString( + g, softWrapChars, softWrapSegmentStartIndex, softWrapChars.length, position, clip, effectColor, effectType, + fontType, currentColor + ); + } + + activeSoftWrapProcessed = true; + } + if (breakWholeIteration) { + break; + } position.x = drawString(g, chars, start, hEnd, position, clip, effectColor, effectType, fontType, currentColor); } } @@ -2203,7 +2345,9 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi @NotNull public VisualPosition logicalToVisualPosition(@NotNull LogicalPosition logicalPos) { assertReadAccess(); - if (!myFoldingModel.isFoldingEnabled()) return new VisualPosition(logicalPos.line, logicalPos.column); + if (!myFoldingModel.isFoldingEnabled() && !mySoftWrapModel.isSoftWrappingEnabled()) { + return new VisualPosition(logicalPos.line, logicalPos.column); + } int offset = logicalPositionToOffset(logicalPos); @@ -2224,9 +2368,9 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi line -= myFoldingModel.getFoldedLinesCountBefore(offset); - FoldRegion[] toplevel = myFoldingModel.fetchTopLevel(); + FoldRegion[] topLevel = myFoldingModel.fetchTopLevel(); for (int idx = myFoldingModel.getLastTopLevelIndexBefore(offset); idx >= 0; idx--) { - FoldRegion region = toplevel[idx]; + FoldRegion region = topLevel[idx]; if (region.isValid()) { if (region.getDocument().getLineNumber(region.getEndOffset()) == logicalPos.line && region.getEndOffset() <= offset) { LogicalPosition foldStart = offsetToLogicalPosition(region.getStartOffset()); @@ -2243,7 +2387,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi LOG.assertTrue(line >= 0); - return new VisualPosition(line, Math.max(0, column)); + return new VisualPosition(line + logicalPos.softWrapLines, Math.max(0, column + logicalPos.softWrapColumns)); } @Nullable @@ -2312,7 +2456,9 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi @NotNull public LogicalPosition visualToLogicalPosition(@NotNull VisualPosition visiblePos) { assertReadAccess(); - if (!myFoldingModel.isFoldingEnabled()) return new LogicalPosition(visiblePos.line, visiblePos.column); + if (!myFoldingModel.isFoldingEnabled() && !mySoftWrapModel.isSoftWrappingEnabled()) { + return new LogicalPosition(visiblePos.line, visiblePos.column); + } int line = visiblePos.line; int column = visiblePos.column; @@ -2329,14 +2475,14 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi column = logFoldEnd.column + (visiblePos.column - visFoldEnd.column); } else { - return offsetToLogicalPosition(lastCollapsedBefore.getStartOffset()); + return offsetToLogicalPosition(lastCollapsedBefore.getStartOffset()); //TODO den check if we need soft-wrap processing here } } } if (column < 0) column = 0; - return new LogicalPosition(line, column); + return mySoftWrapModel.adjustLogicalPositionIfNecessary(new LogicalPosition(line, column), visiblePos); } private int calcLogicalLineNumber(int offset) { @@ -2372,10 +2518,16 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi int columnNumber = pos.column; int lineNumber = pos.line; + int softWrapLines = pos.softWrapLines; + int linesFromCurrentSoftWrap = pos.linesFromActiveSoftWrap; + int softWrapColumns = pos.softWrapColumns; if (lineNumber < 0) { lineNumber = 0; columnNumber = 0; + softWrapLines = 0; + linesFromCurrentSoftWrap = 0; + softWrapColumns = 0; } final int totalLines = myDocument.getLineCount(); @@ -2394,6 +2546,9 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi int lineEndColumnNumber = calcColumnNumber(lineEndOffset, lineNumber); if (columnNumber > lineEndColumnNumber) { columnNumber = lineEndColumnNumber; + if (softWrapColumns != 0) { + softWrapColumns -= columnNumber - lineEndColumnNumber; + } } } @@ -2406,7 +2561,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi } } } - LogicalPosition pos1 = new LogicalPosition(lineNumber, columnNumber); + LogicalPosition pos1 = new LogicalPosition(lineNumber, columnNumber, softWrapLines, linesFromCurrentSoftWrap, softWrapColumns); getCaretModel().moveToLogicalPosition(pos1); } @@ -3197,7 +3352,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi public void setVerticalScrollbarOrientation(int type) { assertIsDispatchThread(); int currentHorOffset = myScrollingModel.getHorizontalScrollOffset(); - myScrollbarOrientation = type; + myScrollBarOrientation = type; if (type == VERTICAL_SCROLLBAR_LEFT) { myScrollPane.setLayout(new LeftHandScrollbarLayout()); } @@ -3226,7 +3381,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi } int getVerticalScrollbarOrientation() { - return myScrollbarOrientation; + return myScrollBarOrientation; } MyScrollBar getVerticalScrollBar() { 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..20247b9faa53 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/SoftWrapModelImpl.java @@ -0,0 +1,365 @@ +/* + * Copyright 2000-2010 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.editor.impl; + +import com.intellij.openapi.editor.*; +import com.intellij.openapi.editor.event.VisibleAreaEvent; +import com.intellij.openapi.editor.event.VisibleAreaListener; +import com.intellij.openapi.editor.ex.util.EditorUtil; +import com.intellij.openapi.editor.TextChange; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.text.CharArrayUtil; +import gnu.trove.TIntArrayList; +import gnu.trove.TIntObjectHashMap; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.awt.*; + +/** + * //TODO den add doc + *

+ * Not thread-safe. + * + * @author Denis Zhdanov + * @since Jun 8, 2010 12:47:32 PM + */ +public class SoftWrapModelImpl implements SoftWrapModel { + + private static final TIntObjectHashMap EMPTY = new TIntObjectHashMap(); + + /** Holds mappings like {@code 'soft wrap start offset at document' -> 'soft wrap indent spaces'}. */ + private final TIntObjectHashMap myWraps = new TIntObjectHashMap(); + private final EditorImpl myEditor; + private int myFirstVisibleSymbolOffset = -1; + private int myFirstVisibleLine = -1; + private int myRightEdgeLocation = -1; + private boolean myInitialized; + + /** + * There is a possible case that visible area is changed but document representation of shown at viewport is still the same + * (e.g. the document doesn't contain long lines and viewport width is expanded). + *

+ * 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(); + } + + //TODO den add doc + public boolean shouldWrap(char[] chars, int start, int end, Point position) { + if (!isSoftWrappingEnabled() || containsOnlyWhiteSpaces(chars, start, end)) { + return false; + } + initIfNecessary(); + dropDataIfNecessary(); + + if (myWraps.contains(start)) { + return true; + } + + if (myRightEdgeLocation < 0) { + assert false; + return false; + } + + //TODO den implement + boolean b = position.x + (end - start) * 7 > myRightEdgeLocation; + //TODO den remove + if (b) { + int i = 1; + } + return b; + } + + private static boolean containsOnlyWhiteSpaces(char[] chars, int start, int end) { + int i = CharArrayUtil.shiftForward(chars, start, "\n \t"); + return i >= end; + } + + //TODO den add doc + public TextChange wrap(int offset) { + TextChange result = myWraps.get(offset); + if (result != null) { + return result; + } + + dropDataIfNecessary(); + result = new TextChange("\n ", offset); //TODO den implement indent calculation on formatting options basis. + myWraps.put(offset, result); + return result; + } + + /** + * Drops information about registered soft wraps if they are marked as 'dirty'. + * + * @see #myDataIsDirty + */ + private void dropDataIfNecessary() { + if (myDataIsDirty) { + myWraps.clear(); + myDataIsDirty = false; + } + } + + public TextChange getSoftWrap(int offset) { + return myWraps.get(offset); + } + + public LogicalPosition adjustLogicalPositionIfNecessary(LogicalPosition logical, VisualPosition visual) { + if (myActive > 0 || !isSoftWrappingEnabled() || myWraps.isEmpty()) { + return logical; + } + myActive++; + try { + return doAdjustLogicalPositionIfNecessary(logical, visual); + } + finally { + myActive--; + } + } + + @SuppressWarnings({"AssignmentToForLoopParameter"}) + private LogicalPosition doAdjustLogicalPositionIfNecessary(LogicalPosition logical, VisualPosition visual) { + CharSequence chars = ((DocumentImpl)myEditor.getDocument()).getCharsNoThreadCheck(); + int softWrapIntroducedLines = 0; + int linesFromCurrentSoftWrap = 0; + int symbolsOnCurrentLogicalLine = 0; + int symbolsOnCurrentVisibleLine = 0; + int softWrapsSymbolsOnCurrentVisibleLine = 0; + + FoldingModel foldingModel = myEditor.getFoldingModel(); + int currentLine = myFirstVisibleLine; + for (int i = myFirstVisibleSymbolOffset, max = chars.length(); i < max && currentLine <= visual.line; i++) { + if (currentLine == visual.line) { + if (symbolsOnCurrentVisibleLine >= visual.column) { + int softWrapColumns = softWrapsSymbolsOnCurrentVisibleLine > 0 + ? symbolsOnCurrentVisibleLine - symbolsOnCurrentLogicalLine : 0; + return new LogicalPosition( + logical.line - softWrapIntroducedLines, symbolsOnCurrentLogicalLine, softWrapIntroducedLines, + linesFromCurrentSoftWrap, softWrapColumns + ); + } + } + + FoldRegion region = foldingModel.getCollapsedRegionAtOffset(i); + if (region != null && !region.isExpanded()) { + // Assuming that folded region placeholder doesn't contain line feed symbols. + i = region.getEndOffset(); + continue; + } + + TextChange softWrap = myWraps.get(i); + if (softWrap != null) { + CharSequence softWrapText = softWrap.getText(); + for (int j = 0; j < softWrapText.length(); j++) { + if (currentLine == visual.line && symbolsOnCurrentVisibleLine >= visual.column) { + return new LogicalPosition( + logical.line - softWrapIntroducedLines, symbolsOnCurrentLogicalLine, softWrapIntroducedLines, + linesFromCurrentSoftWrap, softWrapsSymbolsOnCurrentVisibleLine - symbolsOnCurrentLogicalLine + ); + } + + if (softWrapText.charAt(j) == '\n') { + if (currentLine == visual.line) { + return new LogicalPosition( + logical.line - softWrapIntroducedLines, symbolsOnCurrentLogicalLine, softWrapIntroducedLines, + linesFromCurrentSoftWrap, visual.column - symbolsOnCurrentLogicalLine + ); + } + else { + softWrapIntroducedLines++; + linesFromCurrentSoftWrap++; + currentLine++; + symbolsOnCurrentVisibleLine = 0; + softWrapsSymbolsOnCurrentVisibleLine = 0; + } + } + else { + symbolsOnCurrentVisibleLine++; + softWrapsSymbolsOnCurrentVisibleLine++; + } + } + } + + if (currentLine == visual.line && symbolsOnCurrentVisibleLine >= visual.column) { + return new LogicalPosition( + logical.line - softWrapIntroducedLines, symbolsOnCurrentLogicalLine, softWrapIntroducedLines, + linesFromCurrentSoftWrap, softWrapsSymbolsOnCurrentVisibleLine - symbolsOnCurrentLogicalLine + ); + } + + char c = chars.charAt(i); + + // Check if there is a line break at the document. + if (c == '\n') { + if (currentLine == visual.line) { + int columnToUse = symbolsOnCurrentLogicalLine + visual.column - symbolsOnCurrentVisibleLine; + return new LogicalPosition( + logical.line - softWrapIntroducedLines, columnToUse, softWrapIntroducedLines, + linesFromCurrentSoftWrap, visual.column - columnToUse + ); + } + else { + currentLine++; + linesFromCurrentSoftWrap = 0; + symbolsOnCurrentVisibleLine = 0; + symbolsOnCurrentLogicalLine = 0; + softWrapsSymbolsOnCurrentVisibleLine = 0; + } + } + else { + symbolsOnCurrentLogicalLine++; + symbolsOnCurrentVisibleLine++; + } + } + return logical; + } + + public int getSoftWrapLineFeedsBefore(VisualPosition position) { + if (myActive > 0 || !isSoftWrappingEnabled() || myWraps.isEmpty()) { + return 0; + } + myActive++; + try { + return doGetSoftWrappedLinesFor(position); + } + finally { + myActive--; + } + } + + @SuppressWarnings({"AssignmentToForLoopParameter"}) + private int doGetSoftWrappedLinesFor(VisualPosition position) { + // It's assumed that there are two possible cases when visual position may differ from logical - folding and soft wraps. + // The main idea is to start from document offset that corresponds to the top left position of visible editor area and count + // number of occurred soft wraps before the line of given position (avoiding to count soft wraps from folded regions) + CharSequence chars = ((DocumentImpl)myEditor.getDocument()).getCharsNoThreadCheck(); + int result = 0; + int processedLinesNumber = 0; + FoldingModel foldingModel = myEditor.getFoldingModel(); + int maxLinesToCheck = position.line - myFirstVisibleLine; // Number of the first line that shouldn't be checked. + int charsInRow = 0; + for (int i = myFirstVisibleSymbolOffset, max = chars.length(); i < max && processedLinesNumber < maxLinesToCheck; i++) { + // There is a possible situation that there is a soft wrap at the end of the row denoted by given position. + // We need to avoid counting it then, hence, we stop processing if current line is a line of visual position + // and we checked necessary number of chars. + if (processedLinesNumber == maxLinesToCheck && charsInRow >= position.column) { + break; + } + + TextChange softWrap = myWraps.get(i); + if (softWrap != null) { + CharSequence softWrapText = softWrap.getText(); + for (int j = 0; j < softWrapText.length(); j++) { + if (softWrapText.charAt(j) == '\n') { + processedLinesNumber++; + charsInRow = -1; // We set this to '-1' assuming that it's incremented in loop's 'update' block + continue; + } + charsInRow += charToVisibleSymbolsNumber(softWrapText.charAt(j)); + } + continue; + } + + char c = chars.charAt(i); + + // Check if there is a line break at the document. + if (c == '\n') { + processedLinesNumber++; + charsInRow = -1; // We set this to '-1' assuming that it's incremented in loop's 'update' block + continue; + } + + FoldRegion region = foldingModel.getCollapsedRegionAtOffset(i); + if (region != null && !region.isExpanded()) { + i = region.getEndOffset(); + // Assuming that folded region is not represented in more than one line. + charsInRow += region.getPlaceholderText().length() - 1; + continue; + } + + charsInRow += charToVisibleSymbolsNumber(c); + } + return result; + } + + private int charToVisibleSymbolsNumber(char c) { + if (c != '\t') { + return 1; + } + int result = EditorUtil.getTabSize(myEditor); + return result <= 0 ? 1 : result; + } + + private void initIfNecessary() { + if (myInitialized) { + return; + } + myInitialized = true; + ScrollingModel scrollingModel = myEditor.getScrollingModel(); + scrollingModel.addVisibleAreaListener(new VisibleAreaListener() { + public void visibleAreaChanged(VisibleAreaEvent e) { + updateVisibleAreaChange(e.getNewRectangle()); + } + }); + updateVisibleAreaChange(scrollingModel.getVisibleArea()); + } + + private void updateVisibleAreaChange(@Nullable Rectangle visibleArea) { + if (visibleArea == null || !isSoftWrappingEnabled()) { + return; + } + myActive++; + try { + doUpdateVisibleAreaChange(visibleArea); + } + finally { + myActive--; + } + } + + /** + * Notifies current model that visible rectangle is changed in order for it to update the state accordingly. + *

+ * This method is introduced mostly for business-logic vs try/finally separation. + * + * @param visibleArea current visible area of the editor managed by the current model + */ + private void doUpdateVisibleAreaChange(@NotNull Rectangle visibleArea) { + // Update information about the first visible line. + LogicalPosition logicalPositionOfVisibleAreaStart = myEditor.xyToLogicalPosition(visibleArea.getLocation()); + myFirstVisibleSymbolOffset = myEditor.logicalPositionToOffset(logicalPositionOfVisibleAreaStart); + myFirstVisibleLine = myEditor.logicalToVisualPosition(logicalPositionOfVisibleAreaStart).line; + + // Update right edge. + int currentRightEdgeLocation = visibleArea.x + visibleArea.width; + if (myRightEdgeLocation != currentRightEdgeLocation) { + myDataIsDirty = true; + myRightEdgeLocation = currentRightEdgeLocation; + } + } +} 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 From 060cb0ab135d5905ba1a06f95c4a11a0fdedca40 Mon Sep 17 00:00:00 2001 From: Denis Zhdanov Date: Fri, 18 Jun 2010 16:31:24 +0400 Subject: [PATCH 13/20] IDEA-53596 Soft wrap for editors Second soft wraps implementation iteration. Current state: * soft wraps are represented at the editor during its repainting; * line numbers representations is consistent with soft-wrapped lines presence; * logical position representation is consistent with soft-wrapped lines presence; * foldings are correctly represented at gutter during soft wraps adding/removing; * active line background is drawn correctly in situation when single logical line is sread to multiple visual lines; Bugfixes: * editor repainting was incorrect during vertical document scrolling; Other: * green code policy is applied whenever possible. Most of the time these are minor rename refactorings to make spell checker plugin happy; --- .../intellij/openapi/editor/CaretModel.java | 6 + .../openapi/editor/SoftWrapModel.java | 37 -- .../openapi/editor/ex/SoftWrapModelEx.java | 64 ++ .../openapi/editor/impl/CaretModelImpl.java | 96 ++- .../impl/EditorGutterComponentImpl.java | 93 +-- .../openapi/editor/impl/EditorImpl.java | 161 +++-- .../openapi/editor/impl/FoldingModelImpl.java | 2 +- .../editor/impl/SoftWrapModelImpl.java | 590 +++++++++++++----- 8 files changed, 741 insertions(+), 308 deletions(-) create mode 100644 platform/platform-impl/src/com/intellij/openapi/editor/ex/SoftWrapModelEx.java 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/SoftWrapModel.java b/platform/platform-api/src/com/intellij/openapi/editor/SoftWrapModel.java index bf391b437bd5..4acd37e0eaf0 100644 --- a/platform/platform-api/src/com/intellij/openapi/editor/SoftWrapModel.java +++ b/platform/platform-api/src/com/intellij/openapi/editor/SoftWrapModel.java @@ -15,11 +15,8 @@ */ package com.intellij.openapi.editor; -import gnu.trove.TIntObjectHashMap; import org.jetbrains.annotations.Nullable; -import java.util.Collection; - /** * Defines contract for the services that bring 'soft wrapping' to the editor. *

@@ -78,38 +75,6 @@ public interface SoftWrapModel { */ boolean isSoftWrappingEnabled(); - /** - * Allows to retrieve number of lines feeds from 'soft wraps' located before the given visual position. - *

- * There is a possible case that given visual position points to location inside soft wrap - line feeds of that soft wrap - * are not counted then. - *

- * Example: - *

- *

-   *     public class Test {                      | <- right margin
-   *                                              |
-   *         public void baz() {                  |
-   *             foo("test1", "test2", "test3", [p1]
-   *  [p2            "test4", "test5", "test6",   |
-   *                 "test7", "test8", "test8"); [p3]
-   *         }                                    |
-   *                                              |
-   *  [p4]   public void foo(Object ... args) {   |
-   *         }                                    |
-   *     }                                        |
-   *      
- *

- * Here all 'foo()' call arguments are soft wrapped into three lines. However zero is returned for visual position - * 'p1'-'p3' because they are located inside soft wrap; - * '2' is returned for 'p4'. - * - * @param position target visual position - * @return number of lines feeds from 'soft wraps' located before the given visual position if any; - * 0 otherwise - */ - int getSoftWrapLineFeedsBefore(VisualPosition position); - /** * Asks current model for the soft wrap registered for the given document offset if any. * @@ -118,6 +83,4 @@ public interface SoftWrapModel { */ @Nullable TextChange getSoftWrap(int offset); - - LogicalPosition adjustLogicalPositionIfNecessary(LogicalPosition logical, VisualPosition visual); } 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..cab5fa33a077 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/editor/ex/SoftWrapModelEx.java @@ -0,0 +1,64 @@ +/* + * 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.VisualPosition; +import org.jetbrains.annotations.NotNull; + +/** + * 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, it's primary purpose is to be returned if + * {@code visual -> logical} transformation is not affected by soft wraps. + * + * @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); +} 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 a8fe1d75119b..581438f5e877 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,13 +99,12 @@ 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()); @@ -116,7 +117,7 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener { 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 +145,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(); @@ -268,11 +270,10 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener { ((FoldingModelImpl)myEditor.getFoldingModel()).flushCaretPosition(); - int oldY = myEditor.visibleLineNumberToYPosition(myVisibleCaret.line); - + VerticalInfo oldInfo = myCaretInfo; LogicalPosition oldCaretPosition = myLogicalCaret; - myLogicalCaret = new LogicalPosition(line, column, softWrapLines, linesFromCurrentSoftWrap, softWrapColumns); + setCurrentLogicalCaret(new LogicalPosition(line, column, softWrapLines, linesFromCurrentSoftWrap, softWrapColumns)); final int offset = myEditor.logicalPositionToOffset(myLogicalCaret); @@ -297,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.softWrapColumns != myLogicalCaret.column + myLogicalCaret.softWrapColumns + || oldCaretPosition.line + oldCaretPosition.softWrapLines != myLogicalCaret.line + myLogicalCaret.softWrapLines) + { CaretEvent event = new CaretEvent(myEditor, oldCaretPosition, myLogicalCaret); for (CaretListener listener : myCaretListeners) { listener.caretPositionChanged(event); @@ -311,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() { @@ -353,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; } @@ -406,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) { @@ -428,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; @@ -444,4 +459,33 @@ 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; + int y = myEditor.logicalPositionToXY(myEditor.offsetToLogicalPosition(document.getLineStartOffset(line))).y; + int height = myEditor.getLineHeight(); + if (line < document.getLineCount() - 1) { + height = myEditor.logicalPositionToXY(myEditor.offsetToLogicalPosition(document.getLineStartOffset(line + 1))).y - y; + } + 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 783e66fad3fc..23156bbef758 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) { @@ -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); } } @@ -508,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() { @@ -518,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); @@ -553,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; @@ -698,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(); @@ -969,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); @@ -987,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)); } @@ -1006,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); @@ -1021,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); @@ -1060,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; @@ -1130,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(); } @@ -1319,7 +1320,7 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse } }, dragSourceListener); } - catch (InvalidDnDOperationException idoe) { + catch (InvalidDnDOperationException e) { // OK, can't dnd } } @@ -1329,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); @@ -1359,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 6b0ee8732f63..30b7e3720560 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 @@ -424,7 +424,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi } @NotNull - public CaretModel getCaretModel() { + public CaretModelImpl getCaretModel() { return myCaretModel; } @@ -433,6 +433,11 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi return myScrollingModel; } + @NotNull + public SoftWrapModelImpl getSoftWrapModel() { + return mySoftWrapModel; + } + @NotNull public EditorSettings getSettings() { assertReadAccess(); @@ -948,9 +953,7 @@ 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 new LogicalPosition(line, column); + return mySoftWrapModel.offsetToLogicalPosition(offset); } @NotNull @@ -1105,30 +1108,30 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi } private void repaintToScreenBottom(int startLine) { - Rectangle visibleRect = getScrollingModel().getVisibleArea(); + 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); } @@ -1213,7 +1216,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi } } - void recalcSizeAndRepaint() { + void recalculateSizeAndRepaint() { mySizeContainer.reset(); validateSize(); myEditorComponent.repaintEditorComponent(); @@ -1474,6 +1477,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); @@ -1514,6 +1518,11 @@ 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); + + // Holds offset of the first newly introduced soft wrap if any (non-negative value). + int newSoftWrapOffset = -1; + + outer: while (!iterationState.atEnd() && !lIterator.atEnd()) { int hEnd = iterationState.getEndOffset(); int lEnd = lIterator.getEnd(); @@ -1552,6 +1561,38 @@ 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(start); + if (newSoftWrapOffset < 0) newSoftWrapOffset = start; + } + 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); @@ -1574,6 +1615,20 @@ 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 new soft wrap is introduced 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. + if (newSoftWrapOffset >= 0) { + // 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, position.y, myGutterComponent.getWidth(), myGutterComponent.getHeight() - position.y); + + // Ask caret model to update visual caret position. + if (getCaretModel().getOffset() >= newSoftWrapOffset) { + getCaretModel().moveToOffset(getCaretModel().getOffset()); + } + } } private void paintRectangularSelection(Graphics g) { @@ -1749,9 +1804,8 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi } else { boolean breakWholeIteration = false; - if (mySoftWrapModel.shouldWrap(chars, start, hEnd, position)) { - TextChange softWrap = mySoftWrapModel.wrap(start); - + 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; @@ -1916,6 +1970,14 @@ 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. + LogicalPosition storedPosition = getCaretModel().getLogicalPosition(); + LogicalPosition actualPosition = visualToLogicalPosition(getCaretModel().getVisualPosition()); + if (!storedPosition.equals(actualPosition)) { + getCaretModel().moveToLogicalPosition(storedPosition); + } + myCaretCursor.paint(g); } @@ -2387,7 +2449,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi LOG.assertTrue(line >= 0); - return new VisualPosition(line + logicalPos.softWrapLines, Math.max(0, column + logicalPos.softWrapColumns)); + return mySoftWrapModel.adjustVisualPosition(logicalPos, new VisualPosition(line, Math.max(0, column))); } @Nullable @@ -2482,7 +2544,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi if (column < 0) column = 0; - return mySoftWrapModel.adjustLogicalPositionIfNecessary(new LogicalPosition(line, column), visiblePos); + return mySoftWrapModel.adjustLogicalPosition(new LogicalPosition(line, column), visiblePos); } private int calcLogicalLineNumber(int offset) { @@ -2493,20 +2555,13 @@ 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; + return mySoftWrapModel.offsetToLogicalPosition(offset).line; } public int calcColumnNumber(int offset, int lineIndex) { 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)); + return mySoftWrapModel.offsetToLogicalPosition(offset).column; } private void moveCaretToScreenPos(int x, int y) { @@ -2703,7 +2758,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(); @@ -2712,23 +2767,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) { @@ -3014,8 +3069,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; } @@ -3169,8 +3224,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) { @@ -3185,8 +3240,8 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi } /** - * 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. */ @@ -3195,11 +3250,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()); @@ -3211,8 +3266,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. */ @@ -3221,11 +3276,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()); 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/SoftWrapModelImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/SoftWrapModelImpl.java index 20247b9faa53..1a272db823ea 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/SoftWrapModelImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/SoftWrapModelImpl.java @@ -16,10 +16,13 @@ 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.util.EditorUtil; +import com.intellij.openapi.editor.ex.SoftWrapModelEx; import com.intellij.openapi.editor.TextChange; +import com.intellij.openapi.editor.ex.util.EditorUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.text.CharArrayUtil; import gnu.trove.TIntArrayList; @@ -28,24 +31,37 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.awt.*; +import java.nio.CharBuffer; +import java.util.HashMap; +import java.util.Map; /** - * //TODO den add doc + * Default {@link SoftWrapModelEx} implementation. *

* Not thread-safe. * * @author Denis Zhdanov * @since Jun 8, 2010 12:47:32 PM */ -public class SoftWrapModelImpl implements SoftWrapModel { +public class SoftWrapModelImpl implements SoftWrapModelEx { - private static final TIntObjectHashMap EMPTY = new TIntObjectHashMap(); + private final CharBuffer myCharBuffer = CharBuffer.allocate(1); /** Holds mappings like {@code 'soft wrap start offset at document' -> 'soft wrap indent spaces'}. */ private final TIntObjectHashMap myWraps = new TIntObjectHashMap(); + + /** Holds soft wraps offsets in ascending order. */ + private final TIntArrayList myWrapOffsets = new TIntArrayList(); + + /** Caches soft wrap-aware logical positions by offset. */ + private final TIntObjectHashMap myLogicalPositionsByOffsets = new TIntObjectHashMap(); + + private final Map myLogicalPositionsByVisual = new HashMap(); + private final EditorImpl myEditor; - private int myFirstVisibleSymbolOffset = -1; - private int myFirstVisibleLine = -1; + private Rectangle myLastVisibleArea; + private VisualPosition myFirstLineVisualPosition = new VisualPosition(0, 0); + private LogicalPosition myFirstLineLogicalPosition = new LogicalPosition(0, 0); private int myRightEdgeLocation = -1; private boolean myInitialized; @@ -69,7 +85,21 @@ public class SoftWrapModelImpl implements SoftWrapModel { return myEditor.getSettings().isUseSoftWraps(); } - //TODO den add doc + @Nullable + public TextChange getSoftWrap(int offset) { + return myWraps.get(offset); + } + + /** + * 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() || containsOnlyWhiteSpaces(chars, start, end)) { return false; @@ -87,12 +117,7 @@ public class SoftWrapModelImpl implements SoftWrapModel { } //TODO den implement - boolean b = position.x + (end - start) * 7 > myRightEdgeLocation; - //TODO den remove - if (b) { - int i = 1; - } - return b; + return position.x + (end - start) * 7 > myRightEdgeLocation; } private static boolean containsOnlyWhiteSpaces(char[] chars, int start, int end) { @@ -100,7 +125,12 @@ public class SoftWrapModelImpl implements SoftWrapModel { return i >= end; } - //TODO den add doc + /** + * Asks current model to register soft wrap at the current offset of the active editor document. + * + * @param offset target offset of the editor document where soft wrap should be registered + * @return soft wrap registered for the given offset + */ public TextChange wrap(int offset) { TextChange result = myWraps.get(offset); if (result != null) { @@ -110,32 +140,87 @@ public class SoftWrapModelImpl implements SoftWrapModel { dropDataIfNecessary(); result = new TextChange("\n ", offset); //TODO den implement indent calculation on formatting options basis. myWraps.put(offset, result); + + int i = myWrapOffsets.binarySearch(offset); + if (i < 0) { + i = -i - 1; + + } + if (i < myWrapOffsets.size()) { + myWrapOffsets.insert(i, offset); + } else { + myWrapOffsets.add(offset); + } + + updateCachesOnNewSoftWrapAddition(result); return result; } + private void updateCachesOnNewSoftWrapAddition(TextChange softWrap) { + int lineFeedsAtNewWrap = StringUtil.countNewLines(softWrap.getText()); + if (lineFeedsAtNewWrap <= 0) { + return; + } + + Map updated = new HashMap(); + boolean shouldUpdate = false; + for (Map.Entry entry : myLogicalPositionsByVisual.entrySet()) { + if (myEditor.logicalPositionToOffset(entry.getValue()) <= softWrap.getStart()) { + updated.put(entry.getKey(), entry.getValue()); + continue; + } + shouldUpdate = true; + VisualPosition oldVisual = entry.getKey(); + LogicalPosition oldLogical = entry.getValue(); + + VisualPosition newVisual = new VisualPosition(oldVisual.line + lineFeedsAtNewWrap, oldVisual.column); + LogicalPosition newLogical = new LogicalPosition( + oldLogical.line, oldLogical.column, oldLogical.softWrapLines + lineFeedsAtNewWrap, + oldLogical.linesFromActiveSoftWrap, oldLogical.softWrapColumns + ); + updated.put(newVisual, newLogical); + } + + if (shouldUpdate) { + myLogicalPositionsByVisual.clear(); + myLogicalPositionsByVisual.putAll(updated); + } + } + + /** * Drops information about registered soft wraps if they are marked as 'dirty'. * * @see #myDataIsDirty */ private void dropDataIfNecessary() { - if (myDataIsDirty) { - myWraps.clear(); - myDataIsDirty = false; + if (!myDataIsDirty) { + return; + } + + myDataIsDirty = false; + myWraps.clear(); + myWrapOffsets.clear(); + myLogicalPositionsByOffsets.clear(); + myLogicalPositionsByVisual.clear(); } - public TextChange getSoftWrap(int offset) { - return myWraps.get(offset); - } - - public LogicalPosition adjustLogicalPositionIfNecessary(LogicalPosition logical, VisualPosition visual) { + @NotNull + public LogicalPosition adjustLogicalPosition(@NotNull LogicalPosition defaultLogical, @NotNull VisualPosition visual) { if (myActive > 0 || !isSoftWrappingEnabled() || myWraps.isEmpty()) { - return logical; + return defaultLogical; } + + LogicalPosition cached = myLogicalPositionsByVisual.get(visual); + if (cached != null) { + return cached; + } + + updateVisibleAreaIfNecessary(); myActive++; try { - return doAdjustLogicalPositionIfNecessary(logical, visual); + return doAdjustLogicalPosition(defaultLogical, visual); } finally { myActive--; @@ -143,26 +228,28 @@ public class SoftWrapModelImpl implements SoftWrapModel { } @SuppressWarnings({"AssignmentToForLoopParameter"}) - private LogicalPosition doAdjustLogicalPositionIfNecessary(LogicalPosition logical, VisualPosition visual) { - CharSequence chars = ((DocumentImpl)myEditor.getDocument()).getCharsNoThreadCheck(); - int softWrapIntroducedLines = 0; - int linesFromCurrentSoftWrap = 0; - int symbolsOnCurrentLogicalLine = 0; - int symbolsOnCurrentVisibleLine = 0; - int softWrapsSymbolsOnCurrentVisibleLine = 0; - + private LogicalPosition doAdjustLogicalPosition(LogicalPosition defaultLogical, VisualPosition visual) { + Context context = new Context(); + DocumentImpl document = (DocumentImpl)myEditor.getDocument(); + CharSequence chars = document.getCharsNoThreadCheck(); FoldingModel foldingModel = myEditor.getFoldingModel(); - int currentLine = myFirstVisibleLine; - for (int i = myFirstVisibleSymbolOffset, max = chars.length(); i < max && currentLine <= visual.line; i++) { - if (currentLine == visual.line) { - if (symbolsOnCurrentVisibleLine >= visual.column) { - int softWrapColumns = softWrapsSymbolsOnCurrentVisibleLine > 0 - ? symbolsOnCurrentVisibleLine - symbolsOnCurrentLogicalLine : 0; - return new LogicalPosition( - logical.line - softWrapIntroducedLines, symbolsOnCurrentLogicalLine, softWrapIntroducedLines, - linesFromCurrentSoftWrap, softWrapColumns - ); - } + + // There is a possible case that first line of current visible area points to the line that is soft wrapped. + // Example: + // foo("xxx1", "xxx2", "xxx3", <- soft wrap + // "xxx4") <- first visible line + // We start processing from the start of the logical line that corresponds to the first visible line ('foo()' call in our example), + // hence, we need to skip document and soft wrap symbols that are located outside the visible area. + // This variable with value over than zero defines that all document and soft wrap symbols should be skipped until necessary + // number of soft wrap line feeds are encountered. + int softWrapLinesToSkip = myFirstLineLogicalPosition.linesFromActiveSoftWrap; + + int start = document.getLineStartOffset(myFirstLineLogicalPosition.line); + for (int i = start, max = chars.length(); i < max && getCurrentVisualLine(context) <= visual.line; i++) { + if (getCurrentVisualLine(context) == visual.line && context.symbolsOnCurrentVisibleLine >= visual.column) { + int softWrapColumns = context.softWrapsSymbolsOnCurrentVisibleLine > 0 + ? context.symbolsOnCurrentVisibleLine - context.symbolsOnCurrentLogicalLine : 0; + return context.buildLogicalPosition(softWrapColumns); } FoldRegion region = foldingModel.getCollapsedRegionAtOffset(i); @@ -176,143 +263,238 @@ public class SoftWrapModelImpl implements SoftWrapModel { if (softWrap != null) { CharSequence softWrapText = softWrap.getText(); for (int j = 0; j < softWrapText.length(); j++) { - if (currentLine == visual.line && symbolsOnCurrentVisibleLine >= visual.column) { - return new LogicalPosition( - logical.line - softWrapIntroducedLines, symbolsOnCurrentLogicalLine, softWrapIntroducedLines, - linesFromCurrentSoftWrap, softWrapsSymbolsOnCurrentVisibleLine - symbolsOnCurrentLogicalLine - ); + if (getCurrentVisualLine(context) == visual.line && context.symbolsOnCurrentVisibleLine >= visual.column) { + return context.buildLogicalPosition(context.softWrapsSymbolsOnCurrentVisibleLine - context.symbolsOnCurrentLogicalLine); } if (softWrapText.charAt(j) == '\n') { - if (currentLine == visual.line) { - return new LogicalPosition( - logical.line - softWrapIntroducedLines, symbolsOnCurrentLogicalLine, softWrapIntroducedLines, - linesFromCurrentSoftWrap, visual.column - symbolsOnCurrentLogicalLine - ); + if (softWrapLinesToSkip-- > 0) { + continue; + } + if (getCurrentVisualLine(context) == visual.line) { + return context.buildLogicalPosition(visual.column - context.symbolsOnCurrentLogicalLine); } else { - softWrapIntroducedLines++; - linesFromCurrentSoftWrap++; - currentLine++; - symbolsOnCurrentVisibleLine = 0; - softWrapsSymbolsOnCurrentVisibleLine = 0; + context.onLineFeedInsideSoftWrap(); + updateLogicalByVisualCache(context); } } else { - symbolsOnCurrentVisibleLine++; - softWrapsSymbolsOnCurrentVisibleLine++; + context.onNonLineFeedInsideSoftWrap(softWrapText.charAt(j)); } } } - if (currentLine == visual.line && symbolsOnCurrentVisibleLine >= visual.column) { - return new LogicalPosition( - logical.line - softWrapIntroducedLines, symbolsOnCurrentLogicalLine, softWrapIntroducedLines, - linesFromCurrentSoftWrap, softWrapsSymbolsOnCurrentVisibleLine - symbolsOnCurrentLogicalLine - ); + if (softWrapLinesToSkip > 0) { + continue; + } + + if (getCurrentVisualLine(context) == visual.line && context.symbolsOnCurrentVisibleLine >= visual.column) { + return context.buildLogicalPosition(context.softWrapsSymbolsOnCurrentVisibleLine - context.symbolsOnCurrentLogicalLine); } char c = chars.charAt(i); // Check if there is a line break at the document. - if (c == '\n') { - if (currentLine == visual.line) { - int columnToUse = symbolsOnCurrentLogicalLine + visual.column - symbolsOnCurrentVisibleLine; - return new LogicalPosition( - logical.line - softWrapIntroducedLines, columnToUse, softWrapIntroducedLines, - linesFromCurrentSoftWrap, visual.column - columnToUse - ); - } - else { - currentLine++; - linesFromCurrentSoftWrap = 0; - symbolsOnCurrentVisibleLine = 0; - symbolsOnCurrentLogicalLine = 0; - softWrapsSymbolsOnCurrentVisibleLine = 0; - } + if (c != '\n') { + context.onNonLineFeedOutsideSoftWrap(c); + continue; + } - else { - symbolsOnCurrentLogicalLine++; - symbolsOnCurrentVisibleLine++; + + if (getCurrentVisualLine(context) != visual.line) { + context.onLineFeedOutsideSoftWrap(); + updateLogicalByVisualCache(context); + continue; } + + int column = context.symbolsOnCurrentLogicalLine; + int columnDiff = visual.column - context.symbolsOnCurrentVisibleLine; + if (columnDiff > 0) { + column += columnDiff; + } + LogicalPosition result = new LogicalPosition( + myFirstLineLogicalPosition.line + context.visualLineOnCurrentScreen - context.softWrapIntroducedLines, + column, + context.softWrapIntroducedLines + myFirstLineLogicalPosition.softWrapLines, + context.linesFromCurrentSoftWrap, + visual.column - column + ); + if (visual.column == 0) { + myLogicalPositionsByVisual.put(visual, result); + } + return result; } - return logical; + return defaultLogical; } - public int getSoftWrapLineFeedsBefore(VisualPosition position) { - if (myActive > 0 || !isSoftWrappingEnabled() || myWraps.isEmpty()) { - return 0; - } - myActive++; - try { - return doGetSoftWrappedLinesFor(position); - } - finally { - myActive--; + private int getCurrentVisualLine(Context context) { + return myFirstLineVisualPosition.line + context.visualLineOnCurrentScreen; + } + + private void updateLogicalByVisualCache(Context context) { + VisualPosition visual = new VisualPosition(getCurrentVisualLine(context), context.symbolsOnCurrentVisibleLine); + LogicalPosition logical = context.buildLogicalPosition(context.symbolsOnCurrentVisibleLine - context.symbolsOnCurrentLogicalLine); + if (myLogicalPositionsByVisual.containsKey(visual)) { + return; } + myLogicalPositionsByVisual.put(visual, logical); } @SuppressWarnings({"AssignmentToForLoopParameter"}) - private int doGetSoftWrappedLinesFor(VisualPosition position) { - // It's assumed that there are two possible cases when visual position may differ from logical - folding and soft wraps. - // The main idea is to start from document offset that corresponds to the top left position of visible editor area and count - // number of occurred soft wraps before the line of given position (avoiding to count soft wraps from folded regions) - CharSequence chars = ((DocumentImpl)myEditor.getDocument()).getCharsNoThreadCheck(); - int result = 0; - int processedLinesNumber = 0; + @NotNull + public LogicalPosition offsetToLogicalPosition(int offset) { + DocumentImpl document = (DocumentImpl)myEditor.getDocument(); + CharSequence chars = document.getCharsNoThreadCheck(); + + int targetLine = document.getLineNumber(offset); + int targetLineStartOffset = document.getLineStartOffset(targetLine); + + updateVisibleAreaIfNecessary(); + + // Return eagerly if the result is already cached. + LogicalPosition cached = myLogicalPositionsByOffsets.get(offset); + if (cached != null) { + return cached; + } + + int softWrapIntroducedLines = 0; + int linesFromCurrentSoftWrap = 0; + int symbolsOnCurrentLogicalLine = 0; + int symbolsOnCurrentVisibleLine = 0; + + // Retrieve information about logical position that is soft-wraps unaware. + int rawColumn = toVisualColumnSymbolsNumber(chars, targetLineStartOffset, offset); + LogicalPosition rawLineStartLogicalPosition = new LogicalPosition(targetLine, rawColumn); + + // Calculate number of soft wrap-introduced lines before the line that holds target offset. + int index = myWrapOffsets.binarySearch(targetLineStartOffset); + if (index < 0) { + index = -index - 1; + } + int max = Math.min(index, myWrapOffsets.size()); + for (int j = 0; j < max; j++) { + softWrapIntroducedLines += StringUtil.countNewLines(myWraps.get(myWrapOffsets.get(j)).getText()); + } + + // Return eagerly if there is no soft wraps before the target offset on a line that contains it. + if (max >= myWrapOffsets.size() || myWrapOffsets.get(max) > offset) { + return new LogicalPosition(rawLineStartLogicalPosition.line, rawLineStartLogicalPosition.column, softWrapIntroducedLines, 0, 0); + } + + // Calculate number of lines and columns introduced by soft wrap located at the line that holds target offset if any. FoldingModel foldingModel = myEditor.getFoldingModel(); - int maxLinesToCheck = position.line - myFirstVisibleLine; // Number of the first line that shouldn't be checked. - int charsInRow = 0; - for (int i = myFirstVisibleSymbolOffset, max = chars.length(); i < max && processedLinesNumber < maxLinesToCheck; i++) { - // There is a possible situation that there is a soft wrap at the end of the row denoted by given position. - // We need to avoid counting it then, hence, we stop processing if current line is a line of visual position - // and we checked necessary number of chars. - if (processedLinesNumber == maxLinesToCheck && charsInRow >= position.column) { - break; + max = Math.min(chars.length(), offset); + for (int i = targetLineStartOffset; i < max; i++) { + FoldRegion region = foldingModel.getCollapsedRegionAtOffset(i); + if (region != null && !region.isExpanded()) { + // Assuming that folded region placeholder doesn't contain line feed symbols. + i = region.getEndOffset(); + symbolsOnCurrentLogicalLine += region.getEndOffset() - region.getStartOffset(); + symbolsOnCurrentVisibleLine += region.getPlaceholderText().length(); + continue; } TextChange softWrap = myWraps.get(i); if (softWrap != null) { CharSequence softWrapText = softWrap.getText(); for (int j = 0; j < softWrapText.length(); j++) { - if (softWrapText.charAt(j) == '\n') { - processedLinesNumber++; - charsInRow = -1; // We set this to '-1' assuming that it's incremented in loop's 'update' block - continue; + if (softWrapText.charAt(j) == '\n') { + softWrapIntroducedLines++; + linesFromCurrentSoftWrap++; + symbolsOnCurrentVisibleLine = 0; + } + else { + symbolsOnCurrentVisibleLine++; } - charsInRow += charToVisibleSymbolsNumber(softWrapText.charAt(j)); } - continue; } - char c = chars.charAt(i); - - // Check if there is a line break at the document. - if (c == '\n') { - processedLinesNumber++; - charsInRow = -1; // We set this to '-1' assuming that it's incremented in loop's 'update' block - continue; - } - - FoldRegion region = foldingModel.getCollapsedRegionAtOffset(i); - if (region != null && !region.isExpanded()) { - i = region.getEndOffset(); - // Assuming that folded region is not represented in more than one line. - charsInRow += region.getPlaceholderText().length() - 1; - continue; - } - - charsInRow += charToVisibleSymbolsNumber(c); + // Assuming that no line feed is contained before target offset on a line that holds it. + symbolsOnCurrentLogicalLine++; + symbolsOnCurrentVisibleLine++; } + LogicalPosition result = new LogicalPosition( + rawLineStartLogicalPosition.line, symbolsOnCurrentLogicalLine, softWrapIntroducedLines, linesFromCurrentSoftWrap, + symbolsOnCurrentVisibleLine - symbolsOnCurrentLogicalLine + ); + myLogicalPositionsByOffsets.put(offset, result); return result; } - private int charToVisibleSymbolsNumber(char c) { - if (c != '\t') { - return 1; + @NotNull + public VisualPosition adjustVisualPosition(@NotNull LogicalPosition logical, @NotNull VisualPosition defaultVisual) { + if (myActive > 0 || !isSoftWrappingEnabled() || myWraps.isEmpty()) { + return defaultVisual; } - int result = EditorUtil.getTabSize(myEditor); - return result <= 0 ? 1 : result; + + if (isSoftWrapAware(logical)) { + // We don't need to recalculate logical position adjustments because given object already has them. + return new VisualPosition(logical.line + logical.softWrapLines, logical.column + logical.softWrapColumns); + } + + 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 = myWrapOffsets.binarySearch(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 >= myWrapOffsets.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--) { + int offset = myWrapOffsets.get(i); + + if (foldingModel.isOffsetCollapsed(offset)) { + continue; + } + TextChange softWrap = myWraps.get(offset); + if (softWrap == null) { + assert false; + 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 && offset >= targetLogicalLineStartOffset) { + for (int j = softWrapText.length() - 1; j >= 0; j--) { + if (softWrapText.charAt(j) == '\n') { + column = maxOffset - offset - j + 1; + break; + } + } + } + } + + int columnToUse = column >= 0 ? column : visual.column; + return new VisualPosition(visual.line + lineDiff, columnToUse); } private void initIfNecessary() { @@ -320,17 +502,74 @@ public class SoftWrapModelImpl implements SoftWrapModel { return; } myInitialized = true; + + // Subscribe for visible area changes notifications. ScrollingModel scrollingModel = myEditor.getScrollingModel(); scrollingModel.addVisibleAreaListener(new VisibleAreaListener() { public void visibleAreaChanged(VisibleAreaEvent e) { - updateVisibleAreaChange(e.getNewRectangle()); + updateVisibleAreaIfNecessary(e.getNewRectangle()); + } + }); + updateVisibleAreaIfNecessary(scrollingModel.getVisibleArea()); + + // Subscribe for document change updates. + myEditor.getDocument().addDocumentListener(new DocumentListener() { + public void beforeDocumentChange(DocumentEvent event) { + //// Drop offset-logical position mappings. + //myLogicalPositionsByOffsets.clear(); + // + //// Drop all soft wraps from logical line that is being changed. + //TIntArrayList indices = getSoftWrapIndicesForLogicalLine(event.getOffset()); + //if (indices.isEmpty()) { + // return; + //} + //for (int i = 0; i < indices.size(); i++) { + // myWraps.remove(myWrapOffsets.get(indices.get(i))); + //} + //myWrapOffsets.remove(indices.get(0), indices.size()); + } + + public void documentChanged(DocumentEvent event) { } }); - updateVisibleAreaChange(scrollingModel.getVisibleArea()); } - private void updateVisibleAreaChange(@Nullable Rectangle visibleArea) { - if (visibleArea == null || !isSoftWrappingEnabled()) { + /** + * Allows to ask for indices that are used to store soft wraps offsets at {@link #myWrapOffsets} for the line that holds given + * document offset. + * + * @param offset target document offset + * @return collection that contains indices of soft wrap offsets at {@link #myWrapOffsets} collection for the line + * that contains document text at given offset + */ + private TIntArrayList getSoftWrapIndicesForLogicalLine(int offset) { + TIntArrayList result = new TIntArrayList(); + + Document document = myEditor.getDocument(); + int targetLine = document.getLineNumber(offset); + int start = document.getLineStartOffset(targetLine); + int end = document.getLineEndOffset(targetLine); + + int i = myWrapOffsets.binarySearch(start); + if (i < 0) { + i = -i - 1; + } + + for (; i < myWrapOffsets.size(); i++) { + if (i >= end) { + break; + } + result.add(i); + } + return result; + } + + private void updateVisibleAreaIfNecessary() { + updateVisibleAreaIfNecessary(myEditor.getScrollingModel().getVisibleArea()); + } + + private void updateVisibleAreaIfNecessary(@Nullable Rectangle visibleArea) { + if (visibleArea == null || myActive > 0 || !isSoftWrappingEnabled() || visibleArea.equals(myLastVisibleArea)) { return; } myActive++; @@ -340,6 +579,7 @@ public class SoftWrapModelImpl implements SoftWrapModel { finally { myActive--; } + myLastVisibleArea = visibleArea; } /** @@ -351,9 +591,11 @@ public class SoftWrapModelImpl implements SoftWrapModel { */ private void doUpdateVisibleAreaChange(@NotNull Rectangle visibleArea) { // Update information about the first visible line. - LogicalPosition logicalPositionOfVisibleAreaStart = myEditor.xyToLogicalPosition(visibleArea.getLocation()); - myFirstVisibleSymbolOffset = myEditor.logicalPositionToOffset(logicalPositionOfVisibleAreaStart); - myFirstVisibleLine = myEditor.logicalToVisualPosition(logicalPositionOfVisibleAreaStart).line; + myFirstLineVisualPosition = myEditor.xyToVisualPosition(visibleArea.getLocation()); + myFirstLineLogicalPosition = myLogicalPositionsByVisual.get(myFirstLineVisualPosition); + if (myFirstLineLogicalPosition == null) { + myFirstLineLogicalPosition = myEditor.visualToLogicalPosition(myFirstLineVisualPosition); + } // Update right edge. int currentRightEdgeLocation = visibleArea.x + visibleArea.width; @@ -362,4 +604,62 @@ public class SoftWrapModelImpl implements SoftWrapModel { myRightEdgeLocation = currentRightEdgeLocation; } } + + private int toVisualColumnSymbolsNumber(char c) { + myCharBuffer.clear(); + myCharBuffer.put(c); + myCharBuffer.flip(); + return toVisualColumnSymbolsNumber(myCharBuffer, 0, 1); + } + + private int toVisualColumnSymbolsNumber(CharSequence text, int start, int end) { + return EditorUtil.calcColumnNumber(myEditor, text, start, end, EditorUtil.getTabSize(myEditor)); + } + + private static boolean isSoftWrapAware(LogicalPosition position) { + return position.softWrapLines != 0 || position.softWrapColumns != 0; + } + + private class Context { + + public int softWrapIntroducedLines; + public int linesFromCurrentSoftWrap; + public int visualLineOnCurrentScreen; + public int symbolsOnCurrentLogicalLine; + public int symbolsOnCurrentVisibleLine; + public int softWrapsSymbolsOnCurrentVisibleLine; + + public void onNonLineFeedInsideSoftWrap(char c) { + symbolsOnCurrentVisibleLine++; + softWrapsSymbolsOnCurrentVisibleLine += toVisualColumnSymbolsNumber(c); + } + + public void onLineFeedInsideSoftWrap() { + softWrapIntroducedLines++; + linesFromCurrentSoftWrap++; + visualLineOnCurrentScreen++; + symbolsOnCurrentVisibleLine = 0; + softWrapsSymbolsOnCurrentVisibleLine = 0; + } + + public void onNonLineFeedOutsideSoftWrap(char c) { + symbolsOnCurrentLogicalLine++; + symbolsOnCurrentVisibleLine += toVisualColumnSymbolsNumber(c); + } + + public void onLineFeedOutsideSoftWrap() { + visualLineOnCurrentScreen++; + linesFromCurrentSoftWrap = 0; + symbolsOnCurrentVisibleLine = 0; + symbolsOnCurrentLogicalLine = 0; + softWrapsSymbolsOnCurrentVisibleLine = 0; + } + + public LogicalPosition buildLogicalPosition(int softWrapColumns) { + return new LogicalPosition( + myFirstLineLogicalPosition.line + visualLineOnCurrentScreen - softWrapIntroducedLines, symbolsOnCurrentLogicalLine, + softWrapIntroducedLines + myFirstLineLogicalPosition.softWrapLines, linesFromCurrentSoftWrap, softWrapColumns + ); + } + } } From a68ece25cf2687633ce002ca4f3ded48465fa996 Mon Sep 17 00:00:00 2001 From: Denis Zhdanov Date: Thu, 24 Jun 2010 14:21:08 +0400 Subject: [PATCH 14/20] IDEA-53596 Soft wrap for editors 1. Soft wraps processing algorithm is corrected; 2. Combined soft wraps & folding processing is corrected; 3. Added support for 'soft wrap' -> 'hard wrap' conversion; --- .../intention/impl/config/LazyEditor.java | 5 + .../injected/editor/EditorWindow.java | 5 + .../com/intellij/openapi/editor/Editor.java | 8 + .../editor/EditorModificationUtil.java | 7 +- .../openapi/editor/LogicalPosition.java | 104 ++- .../openapi/editor/SoftWrapModel.java | 11 + .../ex/EditorSettingsExternalizable.java | 2 +- .../openapi/editor/ex/SoftWrapModelEx.java | 11 +- .../openapi/editor/ex/util/EditorUtil.java | 3 +- .../openapi/editor/impl/CaretModelImpl.java | 28 +- .../impl/EditorGutterComponentImpl.java | 7 + .../openapi/editor/impl/EditorImpl.java | 172 ++-- .../editor/impl/SoftWrapModelImpl.java | 795 +++++++++++------- .../editor/textarea/TextComponentEditor.java | 7 + .../textarea/TextComponentSoftWrapModel.java | 39 + 15 files changed, 836 insertions(+), 368 deletions(-) create mode 100644 platform/platform-impl/src/com/intellij/openapi/editor/textarea/TextComponentSoftWrapModel.java 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/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/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/LogicalPosition.java b/platform/platform-api/src/com/intellij/openapi/editor/LogicalPosition.java index aa203871aeb1..ff62e540b587 100644 --- a/platform/platform-api/src/com/intellij/openapi/editor/LogicalPosition.java +++ b/platform/platform-api/src/com/intellij/openapi/editor/LogicalPosition.java @@ -22,7 +22,10 @@ import org.jetbrains.annotations.NonNls; * 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. *

- * //TODO den add doc equals/compareTo() behavior difference because of soft wraps. + * 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) @@ -35,30 +38,96 @@ import org.jetbrains.annotations.NonNls; public class LogicalPosition implements Comparable { public final int line; public final int column; - //TODO den add doc + + /** + * 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; - //TODO den add doc + + /** + * 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; - //TODO den add doc - public final int softWrapColumns; + + /** + * 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); + this(line, column, 0, 0, 0, 0, 0, false); } - public LogicalPosition(int line, int column, int softWrapLines, int linesFromActiveSoftWrap, int softWrapColumns) { + 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.softWrapColumns = softWrapColumns; + this.softWrapColumnDiff = softWrapColumnDiff; + this.foldedLines = foldedLines; + this.foldingColumnDiff = foldingColumnDiff; + this.visualPositionAware = visualPositionAware; } - //TODO den add doc + /** + * 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 softWrapColumns != 0; + 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) { @@ -66,25 +135,30 @@ public class LogicalPosition implements Comparable { final LogicalPosition logicalPosition = (LogicalPosition) o; return column == logicalPosition.column && line == logicalPosition.line && softWrapLines == logicalPosition.softWrapLines - && softWrapColumns == logicalPosition.softWrapColumns; + && linesFromActiveSoftWrap == logicalPosition.linesFromActiveSoftWrap + && softWrapColumnDiff == logicalPosition.softWrapColumnDiff && foldedLines == logicalPosition.foldedLines + && foldingColumnDiff == logicalPosition.foldingColumnDiff; } public int hashCode() { int result = 29 * line + column; result = result * 29 + softWrapLines; - return 29 * result + softWrapColumns; + result = 29 * result + softWrapColumnDiff; + result = 29 * result + foldedLines; + return 29 * result + foldingColumnDiff; } @NonNls public String toString() { - return "LogicalPosition: line = " + line + " column = " + column + "; soft wrap: lines = " + softWrapLines - + " (active = " + linesFromActiveSoftWrap + ") columns = " + softWrapColumns; + 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; if (column != position.column) return column - position.column; if (softWrapLines != position.softWrapLines) return softWrapLines - position.softWrapLines; - return softWrapColumns - position.softWrapColumns; + 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 index 4acd37e0eaf0..67bd7828d84c 100644 --- a/platform/platform-api/src/com/intellij/openapi/editor/SoftWrapModel.java +++ b/platform/platform-api/src/com/intellij/openapi/editor/SoftWrapModel.java @@ -15,6 +15,7 @@ */ package com.intellij.openapi.editor; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** @@ -83,4 +84,14 @@ public interface SoftWrapModel { */ @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-impl/src/com/intellij/openapi/editor/ex/EditorSettingsExternalizable.java b/platform/platform-impl/src/com/intellij/openapi/editor/ex/EditorSettingsExternalizable.java index 473734e1a5cf..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,7 +40,7 @@ public class EditorSettingsExternalizable implements NamedJDOMExternalizable, Ex //Q: make it interface? public static class OptionSet implements Cloneable { public String LINE_SEPARATOR; - public boolean USE_SOFT_WRAPS = true; + public boolean USE_SOFT_WRAPS = false; public boolean IS_VIRTUAL_SPACE = true; public boolean IS_CARET_INSIDE_TABS; @NonNls public String STRIP_TRAILING_SPACES = "Changed"; diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/ex/SoftWrapModelEx.java b/platform/platform-impl/src/com/intellij/openapi/editor/ex/SoftWrapModelEx.java index cab5fa33a077..a819f812971e 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/ex/SoftWrapModelEx.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/ex/SoftWrapModelEx.java @@ -17,9 +17,12 @@ 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. * @@ -31,8 +34,7 @@ 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, it's primary purpose is to be returned if - * {@code visual -> logical} transformation is not affected by soft wraps. + * 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 @@ -61,4 +63,9 @@ public interface SoftWrapModelEx extends SoftWrapModel { */ @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/impl/CaretModelImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/CaretModelImpl.java index 581438f5e877..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 @@ -108,10 +108,8 @@ 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))); + 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(); @@ -231,7 +229,7 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener { int line = pos.line; int softWrapLines = pos.softWrapLines; int linesFromCurrentSoftWrap = pos.linesFromActiveSoftWrap; - int softWrapColumns = pos.softWrapColumns; + int softWrapColumns = pos.softWrapColumnDiff; Document doc = myEditor.getDocument(); @@ -273,7 +271,9 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener { VerticalInfo oldInfo = myCaretInfo; LogicalPosition oldCaretPosition = myLogicalCaret; - setCurrentLogicalCaret(new LogicalPosition(line, column, softWrapLines, linesFromCurrentSoftWrap, softWrapColumns)); + setCurrentLogicalCaret(new LogicalPosition( + line, column, softWrapLines, linesFromCurrentSoftWrap, softWrapColumns, pos.foldedLines, pos.foldingColumnDiff + )); final int offset = myEditor.logicalPositionToOffset(myLogicalCaret); @@ -306,7 +306,7 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener { myEditor.updateCaretCursor(); requestRepaint(oldInfo); - if (oldCaretPosition.column + oldCaretPosition.softWrapColumns != myLogicalCaret.column + myLogicalCaret.softWrapColumns + if (oldCaretPosition.column + oldCaretPosition.softWrapColumnDiff != myLogicalCaret.column + myLogicalCaret.softWrapColumnDiff || oldCaretPosition.line + oldCaretPosition.softWrapLines != myLogicalCaret.line + myLogicalCaret.softWrapLines) { CaretEvent event = new CaretEvent(myEditor, oldCaretPosition, myLogicalCaret); @@ -468,10 +468,22 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener { 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) { - height = myEditor.logicalPositionToXY(myEditor.offsetToLogicalPosition(document.getLineStartOffset(line + 1))).y - y; + 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); } 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 23156bbef758..1aa6a3138cc3 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 @@ -122,6 +122,13 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse repaint(); } + //TODO den remove + @Override + public void repaint(long tm, int x, int y, int width, int height) { + System.out.printf("EditorGutterComponentImpl.repaint(): (%d; %d) - [%d; %d]%n", x, y, width, height); + super.repaint(tm, x, y, width, height); + } + public void paint(Graphics g) { ((ApplicationImpl)ApplicationManager.getApplication()).editorPaintStart(); 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 30b7e3720560..7367936e3d01 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 @@ -99,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; @@ -172,6 +173,8 @@ 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; @@ -505,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() { @@ -953,7 +956,17 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi @NotNull public LogicalPosition offsetToLogicalPosition(int offset) { - return mySoftWrapModel.offsetToLogicalPosition(offset); + 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); } @NotNull @@ -1254,6 +1267,10 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi return isReleased; } + public boolean isUnderRepainting() { + return myUnderRepainting; + } + void paint(Graphics g) { startOptimizedScrolling(); @@ -1280,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) { @@ -1519,9 +1542,6 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi CharSequence text = myDocument.getCharsNoThreadCheck(); int lastLineIndex = Math.max(0, myDocument.getLineCount() - 1); - // Holds offset of the first newly introduced soft wrap if any (non-negative value). - int newSoftWrapOffset = -1; - outer: while (!iterationState.atEnd() && !lIterator.atEnd()) { int hEnd = iterationState.getEndOffset(); @@ -1563,8 +1583,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi else { TextChange softWrap = getSoftWrapModel().getSoftWrap(start); if (softWrap == null && getSoftWrapModel().shouldWrap(myDocument.getRawChars(), start, hEnd, position)) { - softWrap = getSoftWrapModel().wrap(start); - if (newSoftWrapOffset < 0) newSoftWrapOffset = start; + softWrap = getSoftWrapModel().wrap(myDocument.getRawChars(), start, hEnd); } if (softWrap != null) { // Draw soft wrap text background if any. @@ -1616,19 +1635,23 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight, defaultBackground); } - // Perform additional activity if new soft wrap is introduced during repainting. + // 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. - if (newSoftWrapOffset >= 0) { + 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, position.y, myGutterComponent.getWidth(), myGutterComponent.getHeight() - position.y); + myGutterComponent.repaint(0, clip.y, myGutterComponent.getWidth(), myGutterComponent.getHeight() - clip.y); // Ask caret model to update visual caret position. - if (getCaretModel().getOffset() >= newSoftWrapOffset) { - getCaretModel().moveToOffset(getCaretModel().getOffset()); - } + getCaretModel().moveToOffset(getCaretModel().getOffset()); } + mySoftWrapsOnLastRepaint.clear(); + mySoftWrapsOnLastRepaint.addAll(softWrapsAfterRepaint); } private void paintRectangularSelection(Graphics g) { @@ -1972,11 +1995,12 @@ 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. - LogicalPosition storedPosition = getCaretModel().getLogicalPosition(); - LogicalPosition actualPosition = visualToLogicalPosition(getCaretModel().getVisualPosition()); - if (!storedPosition.equals(actualPosition)) { - getCaretModel().moveToLogicalPosition(storedPosition); - } + //TODO den check + //LogicalPosition storedPosition = getCaretModel().getLogicalPosition(); + //LogicalPosition actualPosition = visualToLogicalPosition(getCaretModel().getVisualPosition()); + //if (!storedPosition.equals(actualPosition)) { + // getCaretModel().moveToLogicalPosition(storedPosition); + //} myCaretCursor.paint(g); } @@ -2406,6 +2430,10 @@ 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() && !mySoftWrapModel.isSoftWrappingEnabled()) { return new VisualPosition(logicalPos.line, logicalPos.column); @@ -2431,15 +2459,16 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi line -= myFoldingModel.getFoldedLinesCountBefore(offset); FoldRegion[] topLevel = myFoldingModel.fetchTopLevel(); + LogicalPosition anchorFoldingPosition = logicalPos; for (int idx = myFoldingModel.getLastTopLevelIndexBefore(offset); idx >= 0; 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; @@ -2449,7 +2478,13 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi LOG.assertTrue(line >= 0); - return mySoftWrapModel.adjustVisualPosition(logicalPos, 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 @@ -2528,26 +2563,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()); //TODO den check if we need soft-wrap processing here + 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 mySoftWrapModel.adjustLogicalPosition(new LogicalPosition(line, column), visiblePos); + 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; @@ -2555,13 +2605,32 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi throw new IndexOutOfBoundsException("Wrong offset: " + offset + " textLength: " + textLength); } - return mySoftWrapModel.offsetToLogicalPosition(offset).line; + 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; - return mySoftWrapModel.offsetToLogicalPosition(offset).column; + 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) { @@ -2575,7 +2644,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi int lineNumber = pos.line; int softWrapLines = pos.softWrapLines; int linesFromCurrentSoftWrap = pos.linesFromActiveSoftWrap; - int softWrapColumns = pos.softWrapColumns; + int softWrapColumns = pos.softWrapColumnDiff; if (lineNumber < 0) { lineNumber = 0; @@ -2616,7 +2685,9 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi } } } - LogicalPosition pos1 = new LogicalPosition(lineNumber, columnNumber, softWrapLines, linesFromCurrentSoftWrap, softWrapColumns); + LogicalPosition pos1 = new LogicalPosition( + lineNumber, columnNumber, softWrapLines, linesFromCurrentSoftWrap, softWrapColumns, pos.foldedLines, pos.foldingColumnDiff + ); getCaretModel().moveToLogicalPosition(pos1); } @@ -3721,10 +3792,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(); @@ -4344,6 +4417,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/SoftWrapModelImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/SoftWrapModelImpl.java index 1a272db823ea..763d100a23f1 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/SoftWrapModelImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/SoftWrapModelImpl.java @@ -21,23 +21,38 @@ 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.TextChange; import com.intellij.openapi.editor.ex.util.EditorUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.text.CharArrayUtil; -import gnu.trove.TIntArrayList; -import gnu.trove.TIntObjectHashMap; +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.HashMap; -import java.util.Map; +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 @@ -45,23 +60,30 @@ import java.util.Map; */ 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 mappings like {@code 'soft wrap start offset at document' -> 'soft wrap indent spaces'}. */ - private final TIntObjectHashMap myWraps = new TIntObjectHashMap(); + /** + * 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 soft wraps offsets in ascending order. */ - private final TIntArrayList myWrapOffsets = new TIntArrayList(); - - /** Caches soft wrap-aware logical positions by offset. */ - private final TIntObjectHashMap myLogicalPositionsByOffsets = new TIntObjectHashMap(); - - private final Map myLogicalPositionsByVisual = new HashMap(); + /** 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 Rectangle myLastVisibleArea; - private VisualPosition myFirstLineVisualPosition = new VisualPosition(0, 0); - private LogicalPosition myFirstLineLogicalPosition = new LogicalPosition(0, 0); private int myRightEdgeLocation = -1; private boolean myInitialized; @@ -87,7 +109,47 @@ public class SoftWrapModelImpl implements SoftWrapModelEx { @Nullable public TextChange getSoftWrap(int offset) { - return myWraps.get(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; } /** @@ -101,13 +163,13 @@ public class SoftWrapModelImpl implements SoftWrapModelEx { * @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() || containsOnlyWhiteSpaces(chars, start, end)) { + if (!isSoftWrappingEnabled()) { return false; } initIfNecessary(); dropDataIfNecessary(); - if (myWraps.contains(start)) { + if (hasSoftWrapAt(start)) { return true; } @@ -126,84 +188,67 @@ public class SoftWrapModelImpl implements SoftWrapModelEx { } /** - * Asks current model to register soft wrap at the current offset of the active editor document. + * Asks current model to perform soft wrapping for the characters sub-sequent identified by the given parameters. * - * @param offset target offset of the editor document where soft wrap should be registered + * @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(int offset) { - TextChange result = myWraps.get(offset); + public TextChange wrap(char[] chars, int start, int end) { + TextChange result = getSoftWrap(start); if (result != null) { return result; } dropDataIfNecessary(); - result = new TextChange("\n ", offset); //TODO den implement indent calculation on formatting options basis. - myWraps.put(offset, result); - - int i = myWrapOffsets.binarySearch(offset); - if (i < 0) { - i = -i - 1; - - } - if (i < myWrapOffsets.size()) { - myWrapOffsets.insert(i, offset); - } else { - myWrapOffsets.add(offset); - } - - updateCachesOnNewSoftWrapAddition(result); + //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; } - private void updateCachesOnNewSoftWrapAddition(TextChange softWrap) { - int lineFeedsAtNewWrap = StringUtil.countNewLines(softWrap.getText()); - if (lineFeedsAtNewWrap <= 0) { - return; - } - - Map updated = new HashMap(); - boolean shouldUpdate = false; - for (Map.Entry entry : myLogicalPositionsByVisual.entrySet()) { - if (myEditor.logicalPositionToOffset(entry.getValue()) <= softWrap.getStart()) { - updated.put(entry.getKey(), entry.getValue()); - continue; - } - shouldUpdate = true; - VisualPosition oldVisual = entry.getKey(); - LogicalPosition oldLogical = entry.getValue(); - - VisualPosition newVisual = new VisualPosition(oldVisual.line + lineFeedsAtNewWrap, oldVisual.column); - LogicalPosition newLogical = new LogicalPosition( - oldLogical.line, oldLogical.column, oldLogical.softWrapLines + lineFeedsAtNewWrap, - oldLogical.linesFromActiveSoftWrap, oldLogical.softWrapColumns - ); - updated.put(newVisual, newLogical); - } - - if (shouldUpdate) { - myLogicalPositionsByVisual.clear(); - myLogicalPositionsByVisual.putAll(updated); - } + 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(); - myWrapOffsets.clear(); - myLogicalPositionsByOffsets.clear(); - myLogicalPositionsByVisual.clear(); } @NotNull @@ -212,10 +257,10 @@ public class SoftWrapModelImpl implements SoftWrapModelEx { return defaultLogical; } - LogicalPosition cached = myLogicalPositionsByVisual.get(visual); - if (cached != null) { - return cached; - } + //TODO den check + //if (defaultLogical.softWrapAware) { + // return defaultLogical; + //} updateVisibleAreaIfNecessary(); myActive++; @@ -227,123 +272,202 @@ public class SoftWrapModelImpl implements SoftWrapModelEx { } } - @SuppressWarnings({"AssignmentToForLoopParameter"}) private LogicalPosition doAdjustLogicalPosition(LogicalPosition defaultLogical, VisualPosition visual) { - Context context = new Context(); DocumentImpl document = (DocumentImpl)myEditor.getDocument(); - CharSequence chars = document.getCharsNoThreadCheck(); + 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(); - - // There is a possible case that first line of current visible area points to the line that is soft wrapped. - // Example: - // foo("xxx1", "xxx2", "xxx3", <- soft wrap - // "xxx4") <- first visible line - // We start processing from the start of the logical line that corresponds to the first visible line ('foo()' call in our example), - // hence, we need to skip document and soft wrap symbols that are located outside the visible area. - // This variable with value over than zero defines that all document and soft wrap symbols should be skipped until necessary - // number of soft wrap line feeds are encountered. - int softWrapLinesToSkip = myFirstLineLogicalPosition.linesFromActiveSoftWrap; - - int start = document.getLineStartOffset(myFirstLineLogicalPosition.line); - for (int i = start, max = chars.length(); i < max && getCurrentVisualLine(context) <= visual.line; i++) { - if (getCurrentVisualLine(context) == visual.line && context.symbolsOnCurrentVisibleLine >= visual.column) { - int softWrapColumns = context.softWrapsSymbolsOnCurrentVisibleLine > 0 - ? context.symbolsOnCurrentVisibleLine - context.symbolsOnCurrentLogicalLine : 0; - return context.buildLogicalPosition(softWrapColumns); - } - - FoldRegion region = foldingModel.getCollapsedRegionAtOffset(i); - if (region != null && !region.isExpanded()) { - // Assuming that folded region placeholder doesn't contain line feed symbols. - i = region.getEndOffset(); + 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; } - TextChange softWrap = myWraps.get(i); - if (softWrap != null) { - CharSequence softWrapText = softWrap.getText(); - for (int j = 0; j < softWrapText.length(); j++) { - if (getCurrentVisualLine(context) == visual.line && context.symbolsOnCurrentVisibleLine >= visual.column) { - return context.buildLogicalPosition(context.softWrapsSymbolsOnCurrentVisibleLine - context.symbolsOnCurrentLogicalLine); - } + 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); + } - if (softWrapText.charAt(j) == '\n') { - if (softWrapLinesToSkip-- > 0) { - continue; - } - if (getCurrentVisualLine(context) == visual.line) { - return context.buildLogicalPosition(visual.column - context.symbolsOnCurrentLogicalLine); - } - else { - context.onLineFeedInsideSoftWrap(); - updateLogicalByVisualCache(context); + 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; } } - else { - context.onNonLineFeedInsideSoftWrap(softWrapText.charAt(j)); - } + } + + // Process document symbol. + LogicalPosition result = context.onNonSoftWrapSymbol(documentText.charAt(j)); + if (result != null) { + return result; } } - if (softWrapLinesToSkip > 0) { - continue; - } - - if (getCurrentVisualLine(context) == visual.line && context.symbolsOnCurrentVisibleLine >= visual.column) { - return context.buildLogicalPosition(context.softWrapsSymbolsOnCurrentVisibleLine - context.symbolsOnCurrentLogicalLine); - } - - char c = chars.charAt(i); - - // Check if there is a line break at the document. - if (c != '\n') { - context.onNonLineFeedOutsideSoftWrap(c); - continue; - - } - - if (getCurrentVisualLine(context) != visual.line) { - context.onLineFeedOutsideSoftWrap(); - updateLogicalByVisualCache(context); - continue; - } - - int column = context.symbolsOnCurrentLogicalLine; - int columnDiff = visual.column - context.symbolsOnCurrentVisibleLine; - if (columnDiff > 0) { - column += columnDiff; - } - LogicalPosition result = new LogicalPosition( - myFirstLineLogicalPosition.line + context.visualLineOnCurrentScreen - context.softWrapIntroducedLines, - column, - context.softWrapIntroducedLines + myFirstLineLogicalPosition.softWrapLines, - context.linesFromCurrentSoftWrap, - visual.column - column + // 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 ); - if (visual.column == 0) { - myLogicalPositionsByVisual.put(visual, result); + 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 result; } - return defaultLogical; + + return new LogicalPosition( + position.line, position.column, position.softWrapLines, position.linesFromActiveSoftWrap, + softWrapColumnDiff, foldedLines, foldColumnDiff + ); } - private int getCurrentVisualLine(Context context) { - return myFirstLineVisualPosition.line + context.visualLineOnCurrentScreen; + 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); } - private void updateLogicalByVisualCache(Context context) { - VisualPosition visual = new VisualPosition(getCurrentVisualLine(context), context.symbolsOnCurrentVisibleLine); - LogicalPosition logical = context.buildLogicalPosition(context.symbolsOnCurrentVisibleLine - context.symbolsOnCurrentLogicalLine); - if (myLogicalPositionsByVisual.containsKey(visual)) { - return; + @NotNull + public LogicalPosition offsetToLogicalPosition(int offset) { + if (myActive > 0) { + return myEditor.offsetToLogicalPosition(offset, false); + } + + myActive++; + try { + return doOffsetToLogicalPosition(offset); + } finally { + myActive--; } - myLogicalPositionsByVisual.put(visual, logical); } @SuppressWarnings({"AssignmentToForLoopParameter"}) - @NotNull - public LogicalPosition offsetToLogicalPosition(int offset) { + private LogicalPosition doOffsetToLogicalPosition(int offset) { DocumentImpl document = (DocumentImpl)myEditor.getDocument(); CharSequence chars = document.getCharsNoThreadCheck(); @@ -352,50 +476,51 @@ public class SoftWrapModelImpl implements SoftWrapModelEx { updateVisibleAreaIfNecessary(); - // Return eagerly if the result is already cached. - LogicalPosition cached = myLogicalPositionsByOffsets.get(offset); - if (cached != null) { - return cached; - } - int softWrapIntroducedLines = 0; int linesFromCurrentSoftWrap = 0; int symbolsOnCurrentLogicalLine = 0; int symbolsOnCurrentVisibleLine = 0; // Retrieve information about logical position that is soft-wraps unaware. - int rawColumn = toVisualColumnSymbolsNumber(chars, targetLineStartOffset, offset); - LogicalPosition rawLineStartLogicalPosition = new LogicalPosition(targetLine, rawColumn); + LogicalPosition rawLineStartLogicalPosition = myEditor.offsetToLogicalPosition(targetLineStartOffset, false); // Calculate number of soft wrap-introduced lines before the line that holds target offset. - int index = myWrapOffsets.binarySearch(targetLineStartOffset); + int index = getSoftWrapIndex(targetLineStartOffset); if (index < 0) { index = -index - 1; } - int max = Math.min(index, myWrapOffsets.size()); + int max = Math.min(index, myWraps.size()); for (int j = 0; j < max; j++) { - softWrapIntroducedLines += StringUtil.countNewLines(myWraps.get(myWrapOffsets.get(j)).getText()); + softWrapIntroducedLines += StringUtil.countNewLines(myWraps.get(j).getText()); } - // Return eagerly if there is no soft wraps before the target offset on a line that contains it. - if (max >= myWrapOffsets.size() || myWrapOffsets.get(max) > offset) { - return new LogicalPosition(rawLineStartLogicalPosition.line, rawLineStartLogicalPosition.column, softWrapIntroducedLines, 0, 0); + 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. - FoldingModel foldingModel = myEditor.getFoldingModel(); - max = Math.min(chars.length(), offset); + + + // 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 && !region.isExpanded()) { + if (region != null) { // Assuming that folded region placeholder doesn't contain line feed symbols. i = region.getEndOffset(); - symbolsOnCurrentLogicalLine += region.getEndOffset() - region.getStartOffset(); symbolsOnCurrentVisibleLine += region.getPlaceholderText().length(); continue; } - TextChange softWrap = myWraps.get(i); + TextChange softWrap = getSoftWrap(i); if (softWrap != null) { CharSequence softWrapText = softWrap.getText(); for (int j = 0; j < softWrapText.length(); j++) { @@ -410,16 +535,21 @@ public class SoftWrapModelImpl implements SoftWrapModelEx { } } + // 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 result = new LogicalPosition( + + LogicalPosition foldingUnawarePosition = new LogicalPosition( rawLineStartLogicalPosition.line, symbolsOnCurrentLogicalLine, softWrapIntroducedLines, linesFromCurrentSoftWrap, - symbolsOnCurrentVisibleLine - symbolsOnCurrentLogicalLine + symbolsOnCurrentVisibleLine - symbolsOnCurrentLogicalLine, 0, 0 ); - myLogicalPositionsByOffsets.put(offset, result); - return result; + return adjustFoldingData(foldingModel, foldingUnawarePosition); } @NotNull @@ -428,9 +558,9 @@ public class SoftWrapModelImpl implements SoftWrapModelEx { return defaultVisual; } - if (isSoftWrapAware(logical)) { + if (logical.visualPositionAware) { // We don't need to recalculate logical position adjustments because given object already has them. - return new VisualPosition(logical.line + logical.softWrapLines, logical.column + logical.softWrapColumns); + return logical.toVisualPosition(); } updateVisibleAreaIfNecessary(); @@ -447,14 +577,14 @@ public class SoftWrapModelImpl implements SoftWrapModelEx { 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 = myWrapOffsets.binarySearch(maxOffset); + 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 >= myWrapOffsets.size()) { + if (endIndex < 0 || endIndex >= myWraps.size()) { return visual; } @@ -464,17 +594,16 @@ public class SoftWrapModelImpl implements SoftWrapModelEx { FoldingModel foldingModel = myEditor.getFoldingModel(); int targetLogicalLineStartOffset = myEditor.logicalPositionToOffset(new LogicalPosition(logical.line, 0)); for (int i = endIndex; i >= 0; i--) { - int offset = myWrapOffsets.get(i); - - if (foldingModel.isOffsetCollapsed(offset)) { - continue; - } - TextChange softWrap = myWraps.get(offset); + 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); @@ -483,10 +612,10 @@ public class SoftWrapModelImpl implements SoftWrapModelEx { lineDiff += softWrapLines; // Count soft wrap column offset only if it's located at the same line as the target offset. - if (softWrapLines > 0 && offset >= targetLogicalLineStartOffset) { + if (softWrapLines > 0 && softWrap.getStart() >= targetLogicalLineStartOffset) { for (int j = softWrapText.length() - 1; j >= 0; j--) { if (softWrapText.charAt(j) == '\n') { - column = maxOffset - offset - j + 1; + column = maxOffset - softWrap.getStart() - j + 1; break; } } @@ -497,6 +626,62 @@ public class SoftWrapModelImpl implements SoftWrapModelEx { 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; @@ -512,64 +697,29 @@ public class SoftWrapModelImpl implements SoftWrapModelEx { }); updateVisibleAreaIfNecessary(scrollingModel.getVisibleArea()); - // Subscribe for document change updates. + // Subscribe for document change events. myEditor.getDocument().addDocumentListener(new DocumentListener() { public void beforeDocumentChange(DocumentEvent event) { - //// Drop offset-logical position mappings. - //myLogicalPositionsByOffsets.clear(); - // - //// Drop all soft wraps from logical line that is being changed. - //TIntArrayList indices = getSoftWrapIndicesForLogicalLine(event.getOffset()); - //if (indices.isEmpty()) { - // return; - //} - //for (int i = 0; i < indices.size(); i++) { - // myWraps.remove(myWrapOffsets.get(indices.get(i))); - //} - //myWrapOffsets.remove(indices.get(0), indices.size()); } public void documentChanged(DocumentEvent event) { + myActive++; + try { + updateRegisteredSoftWraps(new TextChange(event.getNewFragment(), event.getOffset(), event.getOffset() + event.getOldLength())); + } + finally { + myActive--; + } } }); - } - - /** - * Allows to ask for indices that are used to store soft wraps offsets at {@link #myWrapOffsets} for the line that holds given - * document offset. - * - * @param offset target document offset - * @return collection that contains indices of soft wrap offsets at {@link #myWrapOffsets} collection for the line - * that contains document text at given offset - */ - private TIntArrayList getSoftWrapIndicesForLogicalLine(int offset) { - TIntArrayList result = new TIntArrayList(); - - Document document = myEditor.getDocument(); - int targetLine = document.getLineNumber(offset); - int start = document.getLineStartOffset(targetLine); - int end = document.getLineEndOffset(targetLine); - - int i = myWrapOffsets.binarySearch(start); - if (i < 0) { - i = -i - 1; - } - - for (; i < myWrapOffsets.size(); i++) { - if (i >= end) { - break; - } - result.add(i); - } - return result; - } + } private void updateVisibleAreaIfNecessary() { updateVisibleAreaIfNecessary(myEditor.getScrollingModel().getVisibleArea()); } private void updateVisibleAreaIfNecessary(@Nullable Rectangle visibleArea) { - if (visibleArea == null || myActive > 0 || !isSoftWrappingEnabled() || visibleArea.equals(myLastVisibleArea)) { + if (visibleArea == null || myActive > 0 || !isSoftWrappingEnabled()) { return; } myActive++; @@ -579,7 +729,6 @@ public class SoftWrapModelImpl implements SoftWrapModelEx { finally { myActive--; } - myLastVisibleArea = visibleArea; } /** @@ -590,13 +739,6 @@ public class SoftWrapModelImpl implements SoftWrapModelEx { * @param visibleArea current visible area of the editor managed by the current model */ private void doUpdateVisibleAreaChange(@NotNull Rectangle visibleArea) { - // Update information about the first visible line. - myFirstLineVisualPosition = myEditor.xyToVisualPosition(visibleArea.getLocation()); - myFirstLineLogicalPosition = myLogicalPositionsByVisual.get(myFirstLineVisualPosition); - if (myFirstLineLogicalPosition == null) { - myFirstLineLogicalPosition = myEditor.visualToLogicalPosition(myFirstLineVisualPosition); - } - // Update right edge. int currentRightEdgeLocation = visibleArea.x + visibleArea.width; if (myRightEdgeLocation != currentRightEdgeLocation) { @@ -612,54 +754,129 @@ public class SoftWrapModelImpl implements SoftWrapModelEx { 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 static boolean isSoftWrapAware(LogicalPosition position) { - return position.softWrapLines != 0 || position.softWrapColumns != 0; - } - private class Context { - public int softWrapIntroducedLines; - public int linesFromCurrentSoftWrap; - public int visualLineOnCurrentScreen; + 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 symbolsOnCurrentVisibleLine; - public int softWrapsSymbolsOnCurrentVisibleLine; + public int symbolsOnCurrentVisualLine; - public void onNonLineFeedInsideSoftWrap(char c) { - symbolsOnCurrentVisibleLine++; - softWrapsSymbolsOnCurrentVisibleLine += toVisualColumnSymbolsNumber(c); + 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; } - public void onLineFeedInsideSoftWrap() { - softWrapIntroducedLines++; - linesFromCurrentSoftWrap++; - visualLineOnCurrentScreen++; - symbolsOnCurrentVisibleLine = 0; - softWrapsSymbolsOnCurrentVisibleLine = 0; + /** + * 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; + } } - public void onNonLineFeedOutsideSoftWrap(char c) { + /** + * 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++; - symbolsOnCurrentVisibleLine += toVisualColumnSymbolsNumber(c); + + + if (targetVisualPosition.column <= symbolsOnCurrentVisualLine) { + return build(); + } + else { + return null; + } } - public void onLineFeedOutsideSoftWrap() { - visualLineOnCurrentScreen++; - linesFromCurrentSoftWrap = 0; - symbolsOnCurrentVisibleLine = 0; - symbolsOnCurrentLogicalLine = 0; - softWrapsSymbolsOnCurrentVisibleLine = 0; + private LogicalPosition build() { + return build(symbolsOnCurrentVisualLine - symbolsOnCurrentLogicalLine); } - public LogicalPosition buildLogicalPosition(int softWrapColumns) { - return new LogicalPosition( - myFirstLineLogicalPosition.line + visualLineOnCurrentScreen - softWrapIntroducedLines, symbolsOnCurrentLogicalLine, - softWrapIntroducedLines + myFirstLineLogicalPosition.softWrapLines, linesFromCurrentSoftWrap, softWrapColumns + 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/textarea/TextComponentSoftWrapModel.java b/platform/platform-impl/src/com/intellij/openapi/editor/textarea/TextComponentSoftWrapModel.java new file mode 100644 index 000000000000..39e0fa40ed8c --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/editor/textarea/TextComponentSoftWrapModel.java @@ -0,0 +1,39 @@ +/* + * 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.textarea; + +import com.intellij.openapi.editor.SoftWrapModel; +import com.intellij.openapi.editor.TextChange; +import com.intellij.openapi.editor.VisualPosition; +import org.jetbrains.annotations.NotNull; + +/** + * @author Denis Zhdanov + * @since Jun 22, 2010 5:56:23 PM + */ +public class TextComponentSoftWrapModel implements SoftWrapModel { + + public boolean isSoftWrappingEnabled() { + return false; + } + + public TextChange getSoftWrap(int offset) { + return null; + } + + public void beforeDocumentChange(@NotNull VisualPosition position) { + } +} From 349846d21d7b679bff23432c49fada5124f949e9 Mon Sep 17 00:00:00 2001 From: Denis Zhdanov Date: Thu, 24 Jun 2010 14:31:30 +0400 Subject: [PATCH 15/20] IDEA-53596 Soft wrap for editors 1. Debug call to System.out.println() is removed; 2. Minor improvements; --- .../openapi/editor/impl/EditorGutterComponentImpl.java | 9 +-------- .../src/com/intellij/openapi/editor/impl/EditorImpl.java | 8 +------- .../intellij/openapi/editor/impl/SoftWrapModelImpl.java | 2 +- 3 files changed, 3 insertions(+), 16 deletions(-) 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 1aa6a3138cc3..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 @@ -121,14 +121,7 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse myBackgroundColor = null; repaint(); } - - //TODO den remove - @Override - public void repaint(long tm, int x, int y, int width, int height) { - System.out.printf("EditorGutterComponentImpl.repaint(): (%d; %d) - [%d; %d]%n", x, y, width, height); - super.repaint(tm, x, y, width, height); - } - + public void paint(Graphics g) { ((ApplicationImpl)ApplicationManager.getApplication()).editorPaintStart(); 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 7367936e3d01..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 @@ -1995,13 +1995,7 @@ 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. - //TODO den check - //LogicalPosition storedPosition = getCaretModel().getLogicalPosition(); - //LogicalPosition actualPosition = visualToLogicalPosition(getCaretModel().getVisualPosition()); - //if (!storedPosition.equals(actualPosition)) { - // getCaretModel().moveToLogicalPosition(storedPosition); - //} - + myCaretCursor.paint(g); } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/SoftWrapModelImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/SoftWrapModelImpl.java index 763d100a23f1..8af6f3f0600d 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/SoftWrapModelImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/SoftWrapModelImpl.java @@ -454,7 +454,7 @@ public class SoftWrapModelImpl implements SoftWrapModelEx { @NotNull public LogicalPosition offsetToLogicalPosition(int offset) { - if (myActive > 0) { + if (myActive > 0 || !isSoftWrappingEnabled()) { return myEditor.offsetToLogicalPosition(offset, false); } From b99febdc23fd73720f4801cdded9ea251c1fa59b Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Thu, 24 Jun 2010 15:20:36 +0400 Subject: [PATCH 16/20] better logic for checking implicit usages of fields --- .../intellij/codeInsight/daemon/impl/PostHighlightingPass.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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..47c6833455bd 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; } From 26e4a6cfa426ad3c9fe90f87ef03e4978a8888c9 Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Thu, 24 Jun 2010 15:35:58 +0400 Subject: [PATCH 17/20] one more case --- .../intellij/codeInsight/daemon/impl/PostHighlightingPass.java | 3 +++ 1 file changed, 3 insertions(+) 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 47c6833455bd..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 @@ -396,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); } From 412e0cf689409e936de207a00ff5a1e4261e9bf9 Mon Sep 17 00:00:00 2001 From: "Maxim.Mossienko" Date: Thu, 24 Jun 2010 16:42:05 +0400 Subject: [PATCH 18/20] removed unneeded extension point --- .../intellij/codeInsight/generation/SelfManagingCommenter.java | 2 -- .../platform-resources/src/META-INF/LangExtensionPoints.xml | 2 -- 2 files changed, 4 deletions(-) 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/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 @@ - From 181d47fe10d483d25ad93ac6402ca0772013eebe Mon Sep 17 00:00:00 2001 From: anna Date: Thu, 24 Jun 2010 15:48:53 +0400 Subject: [PATCH 19/20] cleanup --- .../theoryinpractice/testng/inspection/JUnitConvertTool.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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); From 798f8c893a0943fb4173809fc3e3daf21c469054 Mon Sep 17 00:00:00 2001 From: anna Date: Thu, 24 Jun 2010 16:47:55 +0400 Subject: [PATCH 20/20] NPE (@see EditorTextField) --- .../src/com/intellij/openapi/wm/impl/status/PositionPanel.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 797dbc916eef..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 @@ -141,7 +141,8 @@ public class PositionPanel implements StatusBarWidget, StatusBarWidget.TextPrese public void caretPositionChanged(final CaretEvent e) { final Editor editor = e.getEditor(); - if (FileEditorManager.getInstance(editor.getProject()).getSelectedTextEditor() == e.getEditor()) { + Project project = editor.getProject(); + if (project != null && FileEditorManager.getInstance(project).getSelectedTextEditor() == e.getEditor()) { updatePosition(editor); } }