From 2ab625ca9a5651d8daeae4d15c39c7f4b536e2e9 Mon Sep 17 00:00:00 2001 From: "Vassiliy.Kudryashov" Date: Mon, 17 Nov 2014 01:20:47 +0300 Subject: [PATCH 1/9] IDEA-112485 Shortcut to maximize toolwindow --- .../openapi/ui/ThreeComponentsSplitter.java | 70 +++++++++------ .../openapi/wm/ToolWindowManager.java | 4 + .../openapi/wm/impl/InternalDecorator.java | 1 + .../wm/impl/MaximizeToolWindowAction.java | 60 +++++++++++++ .../openapi/wm/impl/ToolWindowHeader.java | 8 ++ .../impl/ToolWindowHeadlessManagerImpl.java | 9 ++ .../wm/impl/ToolWindowManagerImpl.java | 11 +++ .../openapi/wm/impl/ToolWindowsPane.java | 86 ++++++++++++++----- .../src/messages/ActionsBundle.properties | 3 + .../src/idea/Keymap_Default.xml | 3 + .../src/idea/PlatformActions.xml | 1 + .../src/com/intellij/mock/Mock.java | 9 ++ 12 files changed, 218 insertions(+), 47 deletions(-) create mode 100644 platform/platform-impl/src/com/intellij/openapi/wm/impl/MaximizeToolWindowAction.java diff --git a/platform/platform-api/src/com/intellij/openapi/ui/ThreeComponentsSplitter.java b/platform/platform-api/src/com/intellij/openapi/ui/ThreeComponentsSplitter.java index 62864d01b91a..e6311fbae473 100644 --- a/platform/platform-api/src/com/intellij/openapi/ui/ThreeComponentsSplitter.java +++ b/platform/platform-api/src/com/intellij/openapi/ui/ThreeComponentsSplitter.java @@ -37,6 +37,8 @@ import java.awt.event.MouseEvent; * @author Vladimir Kondratyev */ public class ThreeComponentsSplitter extends JPanel implements Disposable { + public static final int MIN_SIZE = 48; + private int myDividerWidth; /** * /------/ @@ -189,14 +191,22 @@ public class ThreeComponentsSplitter extends JPanel implements Disposable { else { firstCompontSize = getFirstSize(); lastComponentSize = getLastSize(); - int sizeLack = firstCompontSize + lastComponentSize - (componentSize - dividersCount * dividerWidth); + int sizeLack = firstCompontSize + lastComponentSize - (componentSize - dividersCount * dividerWidth - MIN_SIZE); if (sizeLack > 0) { - // Lacking size. Reduce first component's size, inner -> empty - firstCompontSize -= sizeLack; - innerComponentSize = 0; + // Lacking size. Reduce first & last component's size, inner -> MIN_SIZE + double firstSizeRatio = (double)firstCompontSize / (firstCompontSize + lastComponentSize); + if (firstCompontSize > 0) { + firstCompontSize -= sizeLack * firstSizeRatio; + firstCompontSize = Math.max(MIN_SIZE, firstCompontSize); + } + if (lastComponentSize > 0) { + lastComponentSize -= sizeLack * (1 - firstSizeRatio); + lastComponentSize = Math.max(MIN_SIZE, lastComponentSize); + } + innerComponentSize = MIN_SIZE; } else { - innerComponentSize = componentSize - dividersCount * dividerWidth - getFirstSize() - getLastSize(); + innerComponentSize = Math.max(MIN_SIZE, componentSize - dividersCount * dividerWidth - getFirstSize() - getLastSize()); } if (!innerVisible()) { @@ -395,6 +405,29 @@ public class ThreeComponentsSplitter extends JPanel implements Disposable { return lastVisible() ? myLastSize : 0; } + public int getMinSize(boolean first) { + return getMinSize(first? myFirstComponent : myLastComponent); + } + + public int getMaxSize(boolean first) { + final int size = getOrientation() ? this.getHeight() : this.getWidth(); + return size - (first? myLastSize: myFirstSize) - MIN_SIZE; + } + + private int getMinSize(JComponent component) { + if (isHonorMinimumSize()) { + if (component != null && myFirstComponent != null && myFirstComponent.isVisible() && myLastComponent != null && myLastComponent.isVisible()) { + if (getOrientation()) { + return component.getMinimumSize().height; + } + else { + return component.getMinimumSize().width; + } + } + } + return MIN_SIZE; + } + @Override public void dispose() { myLastComponent = null; @@ -633,23 +666,24 @@ public class ThreeComponentsSplitter extends JPanel implements Disposable { myGlassPane.setCursor(getResizeCursor(), myListener); myPoint = SwingUtilities.convertPoint(this, e.getPoint(), ThreeComponentsSplitter.this); + final int size = getOrientation() ? ThreeComponentsSplitter.this.getHeight() : ThreeComponentsSplitter.this.getWidth(); if (getOrientation()) { - if (getHeight() > 0 || myDividerZone > 0) { + if (size > 0 || myDividerZone > 0) { if (myIsFirst) { - setFirstSize(Math.max(getMinSize(myFirstComponent), myPoint.y)); + setFirstSize(Math.min(size - myLastSize - MIN_SIZE, Math.max(getMinSize(myFirstComponent), myPoint.y))); } else { - setLastSize(Math.max(getMinSize(myLastComponent), ThreeComponentsSplitter.this.getHeight() - myPoint.y - getDividerWidth())); + setLastSize(Math.min(size - myFirstSize - MIN_SIZE, Math.max(getMinSize(myLastComponent), size - myPoint.y - getDividerWidth()))); } } } else { - if (getWidth() > 0 || myDividerZone > 0) { + if (size > 0 || myDividerZone > 0) { if (myIsFirst) { - setFirstSize(Math.max(getMinSize(myFirstComponent), myPoint.x)); + setFirstSize(Math.min(size - myLastSize - MIN_SIZE, Math.max(getMinSize(myFirstComponent), myPoint.x))); } else { - setLastSize(Math.max(getMinSize(myLastComponent), ThreeComponentsSplitter.this.getWidth() - myPoint.x - getDividerWidth())); + setLastSize(Math.min(size - myFirstSize - MIN_SIZE, Math.max(getMinSize(myLastComponent), size - myPoint.x - getDividerWidth()))); } } } @@ -670,20 +704,6 @@ public class ThreeComponentsSplitter extends JPanel implements Disposable { } } - private int getMinSize(JComponent component) { - if (isHonorMinimumSize()) { - if (component != null && myFirstComponent != null && myFirstComponent.isVisible() && myLastComponent != null && myLastComponent.isVisible()) { - if (getOrientation()) { - return component.getMinimumSize().height; - } - else { - return component.getMinimumSize().width; - } - } - } - return 0; - } - protected void processMouseEvent(MouseEvent e) { super.processMouseEvent(e); if (!isShowing()) { diff --git a/platform/platform-api/src/com/intellij/openapi/wm/ToolWindowManager.java b/platform/platform-api/src/com/intellij/openapi/wm/ToolWindowManager.java index b616429a11c8..654f43e0c2ec 100644 --- a/platform/platform-api/src/com/intellij/openapi/wm/ToolWindowManager.java +++ b/platform/platform-api/src/com/intellij/openapi/wm/ToolWindowManager.java @@ -154,4 +154,8 @@ public abstract class ToolWindowManager { @Nullable public abstract Balloon getToolWindowBalloon(String id); + + public abstract boolean isMaximized(@NotNull ToolWindow wnd); + + public abstract void setMaximized(@NotNull ToolWindow wnd, boolean maximized); } diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/InternalDecorator.java b/platform/platform-impl/src/com/intellij/openapi/wm/impl/InternalDecorator.java index bf9a015bdfeb..371d847f1699 100644 --- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/InternalDecorator.java +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/InternalDecorator.java @@ -402,6 +402,7 @@ public final class InternalDecorator extends JPanel implements Queryable, DataPr resize.add(new ResizeToolWindowAction.Right(myToolWindow, this)); resize.add(new ResizeToolWindowAction.Up(myToolWindow, this)); resize.add(new ResizeToolWindowAction.Down(myToolWindow, this)); + resize.add(ActionManager.getInstance().getAction("MaximizeToolWindow")); group.add(resize); diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/MaximizeToolWindowAction.java b/platform/platform-impl/src/com/intellij/openapi/wm/impl/MaximizeToolWindowAction.java new file mode 100644 index 000000000000..29d68ad59080 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/MaximizeToolWindowAction.java @@ -0,0 +1,60 @@ +/* + * Copyright 2000-2014 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.wm.impl; + +import com.intellij.idea.ActionsBundle; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.wm.ToolWindow; +import com.intellij.openapi.wm.ToolWindowManager; +import org.jetbrains.annotations.NotNull; + +public class MaximizeToolWindowAction extends AnAction { + public MaximizeToolWindowAction() { + super(); + } + + @Override + public void actionPerformed(@NotNull AnActionEvent e) { + Project project = e.getProject(); + if (project == null || project.isDisposed()) return; + ToolWindow toolWindow = e.getData(PlatformDataKeys.TOOL_WINDOW); + if (toolWindow == null) return; + ToolWindowManager manager = ToolWindowManager.getInstance(project); + manager.setMaximized(toolWindow, !manager.isMaximized(toolWindow)); + } + + @Override + public void update(@NotNull AnActionEvent e) { + e.getPresentation().setEnabled(true); + Project project = e.getProject(); + if (project == null || project.isDisposed()) { + e.getPresentation().setEnabled(false); + return; + } + ToolWindow toolWindow = e.getData(PlatformDataKeys.TOOL_WINDOW); + if (toolWindow == null) { + e.getPresentation().setEnabled(false); + return; + } + ToolWindowManager manager = ToolWindowManager.getInstance(project); + e.getPresentation().setText(manager.isMaximized(toolWindow) ? + ActionsBundle.message("action.ResizeToolWindowMaximize.text.alternative") : + ActionsBundle.message("action.ResizeToolWindowMaximize.text")); + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/ToolWindowHeader.java b/platform/platform-impl/src/com/intellij/openapi/wm/impl/ToolWindowHeader.java index 101d3ab2762b..7d64b1180e15 100644 --- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/ToolWindowHeader.java +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/ToolWindowHeader.java @@ -25,9 +25,11 @@ import com.intellij.openapi.actionSystem.ex.ActionManagerEx; import com.intellij.openapi.actionSystem.impl.*; import com.intellij.openapi.keymap.KeymapUtil; import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.Project; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.wm.ToolWindow; import com.intellij.openapi.wm.ToolWindowAnchor; +import com.intellij.openapi.wm.ToolWindowManager; import com.intellij.openapi.wm.ToolWindowType; import com.intellij.openapi.wm.impl.content.ToolWindowContentUi; import com.intellij.ui.InplaceButton; @@ -210,6 +212,12 @@ public abstract class ToolWindowHeader extends JPanel implements Disposable, UIS }; } + void switchMaximizedState(Project project) { + if (project == null || project.isDisposed()) return; + ToolWindowManager mgr = ToolWindowManager.getInstance(project); + mgr.setMaximized(myToolWindow, !mgr.isMaximized(myToolWindow)); + } + @Override public void uiSettingsChanged(UISettings source) { clearCaches(); diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/ToolWindowHeadlessManagerImpl.java b/platform/platform-impl/src/com/intellij/openapi/wm/impl/ToolWindowHeadlessManagerImpl.java index 7b9a7494c640..5fa360235cb2 100644 --- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/ToolWindowHeadlessManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/ToolWindowHeadlessManagerImpl.java @@ -198,6 +198,15 @@ public class ToolWindowHeadlessManagerImpl extends ToolWindowManagerEx { return null; } + @Override + public boolean isMaximized(@NotNull ToolWindow wnd) { + return false; + } + + @Override + public void setMaximized(@NotNull ToolWindow wnd, boolean maximized) { + } + @Override public void initToolWindow(@NotNull ToolWindowEP bean) { diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/ToolWindowManagerImpl.java b/platform/platform-impl/src/com/intellij/openapi/wm/impl/ToolWindowManagerImpl.java index c0c27ba8c128..8acebb5b5bfd 100644 --- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/ToolWindowManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/ToolWindowManagerImpl.java @@ -120,6 +120,7 @@ public final class ToolWindowManagerImpl extends ToolWindowManagerEx implements private final FileEditorManager myFileEditorManager; private final LafManager myLafManager; private final Map myWindow2Balloon = new HashMap(); + private Pair myMaximizedToolwindowSize = null; private KeyState myCurrentState = KeyState.waiting; private final Alarm myWaiterForSecondPress = new Alarm(); @@ -1982,6 +1983,16 @@ public final class ToolWindowManagerImpl extends ToolWindowManagerEx implements myToolWindowsPane.stretchWidth(toolWindow, value); } + @Override + public boolean isMaximized(@NotNull ToolWindow wnd) { + return myToolWindowsPane.isMaximized(wnd); + } + + @Override + public void setMaximized(@NotNull ToolWindow wnd, boolean maximized) { + myToolWindowsPane.setMaximized(wnd, maximized); + } + public void stretchHeight(ToolWindowImpl toolWindow, int value) { myToolWindowsPane.stretchHeight(toolWindow, value); } diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/ToolWindowsPane.java b/platform/platform-impl/src/com/intellij/openapi/wm/impl/ToolWindowsPane.java index d1232749d3c8..3606939167a8 100644 --- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/ToolWindowsPane.java +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/ToolWindowsPane.java @@ -22,20 +22,27 @@ import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Splitter; import com.intellij.openapi.ui.ThreeComponentsSplitter; +import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.registry.Registry; +import com.intellij.openapi.wm.IdeGlassPaneUtil; import com.intellij.openapi.wm.ToolWindow; import com.intellij.openapi.wm.ToolWindowAnchor; import com.intellij.openapi.wm.ToolWindowType; -import com.intellij.openapi.wm.ex.ToolWindowEx; import com.intellij.openapi.wm.impl.commands.FinalizableCommand; import com.intellij.ui.ScreenUtil; import com.intellij.ui.components.JBLayeredPane; import com.intellij.util.containers.HashMap; import com.intellij.util.ui.FadeInFadeOut; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; @@ -58,6 +65,7 @@ public final class ToolWindowsPane extends JBLayeredPane implements Disposable { private final HashMap myButton2Info; private final HashMap myDecorator2Info; private final HashMap myId2SplitProportion; + private Pair myMaximizedProportion = null; /** * This panel is the layered pane where all sliding tool windows are located. The DEFAULT * layer contains splitters. The PALETTE layer contains all sliding tool windows. @@ -192,6 +200,24 @@ public final class ToolWindowsPane extends JBLayeredPane implements Disposable { super.addNotify(); if (ScreenUtil.isStandardAddRemoveNotify(this)) { UISettings.getInstance().addUISettingsListener(myUISettingsListener, myDisposable); + IdeGlassPaneUtil.find(this).addMousePreprocessor(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (SwingUtilities.isLeftMouseButton(e) && e.getClickCount() == 2 && e.getModifiersEx() == 0) { + e = SwingUtilities.convertMouseEvent(e.getComponent(), e, ToolWindowsPane.this); + Component component = SwingUtilities.getDeepestComponentAt(ToolWindowsPane.this, e.getX(), e.getY()); + Component header = component == null ? null : UIUtil.findParentByCondition((JComponent)component, new Condition() { + @Override + public boolean value(Component component) { + return component instanceof ToolWindowHeader; + } + }); + if (header instanceof ToolWindowHeader) { + ((ToolWindowHeader)header).switchMaximizedState(myFrame.getProject()); + } + } + } + }, this); } } @@ -490,7 +516,21 @@ public final class ToolWindowsPane extends JBLayeredPane implements Disposable { } private void stretch(ToolWindow wnd, int value) { - if (!wnd.isVisible()) return; + Pair pair = findResizerAndComponent(wnd); + if (pair == null) return; + + boolean vertical = wnd.getAnchor() == ToolWindowAnchor.TOP || wnd.getAnchor() == ToolWindowAnchor.BOTTOM; + int actualSize = (vertical ? pair.second.getHeight() : pair.second.getWidth()) + value; + boolean first = wnd.getAnchor() == ToolWindowAnchor.LEFT || wnd.getAnchor() == ToolWindowAnchor.TOP; + int maxValue = vertical ? myVerticalSplitter.getMaxSize(first) : myHorizontalSplitter.getMaxSize(first); + int minValue = vertical ? myVerticalSplitter.getMinSize(first) : myHorizontalSplitter.getMinSize(first);; + + pair.first.setSize(Math.max(minValue, Math.min(maxValue, actualSize))); + } + + @Nullable + private Pair findResizerAndComponent(ToolWindow wnd) { + if (!wnd.isVisible()) return null; Resizer resizer = null; Component cmp = null; @@ -534,26 +574,7 @@ public final class ToolWindowsPane extends JBLayeredPane implements Disposable { } } - if (resizer == null) return; - - int currentValue = wnd.getAnchor().isHorizontal() ? cmp.getHeight() : cmp.getWidth(); - - int actualSize = currentValue + value; - - int minValue = - wnd.getAnchor().isHorizontal() ? ((ToolWindowEx)wnd).getDecorator().getHeaderHeight() : 16 + myHorizontalSplitter.getDividerWidth(); - int maxValue = wnd.getAnchor().isHorizontal() ? myLayeredPane.getHeight() : myLayeredPane.getWidth(); - - - if (actualSize < minValue) { - actualSize = minValue; - } - - if (actualSize > maxValue) { - actualSize = maxValue; - } - - resizer.setSize(actualSize); + return resizer != null ? Pair.create(resizer, cmp) : null; } private void updateLayout() { @@ -598,6 +619,27 @@ public final class ToolWindowsPane extends JBLayeredPane implements Disposable { } } + public boolean isMaximized(@NotNull ToolWindow wnd) { + return myMaximizedProportion != null && myMaximizedProportion.first == wnd; + } + + public void setMaximized(@NotNull ToolWindow wnd, boolean maximized) { + Pair resizerAndComponent = findResizerAndComponent(wnd); + if (resizerAndComponent == null) return; + + if (!maximized) { + ToolWindow maximizedWindow = myMaximizedProportion.first; + assert maximizedWindow == wnd; + resizerAndComponent.first.setSize(myMaximizedProportion.second); + myMaximizedProportion = null; + } else { + int size = wnd.getAnchor().isHorizontal() ? resizerAndComponent.second.getHeight() : resizerAndComponent.second.getWidth(); + stretch(wnd, Short.MAX_VALUE); + myMaximizedProportion = Pair.create(wnd, size); + } + doLayout(); + } + interface Resizer { void setSize(int size); diff --git a/platform/platform-resources-en/src/messages/ActionsBundle.properties b/platform/platform-resources-en/src/messages/ActionsBundle.properties index 58adbf219537..138a5e3b60b5 100644 --- a/platform/platform-resources-en/src/messages/ActionsBundle.properties +++ b/platform/platform-resources-en/src/messages/ActionsBundle.properties @@ -842,6 +842,9 @@ action.ResizeToolWindowUp.text=Stretch to Top action.ResizeToolWindowUp.description=Resize active tool window to the top action.ResizeToolWindowDown.text=Stretch to Bottom action.ResizeToolWindowDown.description=Resize active tool window to the bottom +action.ResizeToolWindowMaximize.text=Maximize tool window +action.ResizeToolWindowMaximize.text.alternative=Restore tool window size +action.ResizeToolWindowMaximize.description=Maximize tool widow action.IncrementWindowWidth.text=Increment Width action.DecrementWindowWidth.text=Decrement Width action.IncrementWindowHeight.text=Increment Height diff --git a/platform/platform-resources/src/idea/Keymap_Default.xml b/platform/platform-resources/src/idea/Keymap_Default.xml index 405316ff8468..8907cc61f633 100644 --- a/platform/platform-resources/src/idea/Keymap_Default.xml +++ b/platform/platform-resources/src/idea/Keymap_Default.xml @@ -774,6 +774,9 @@ + + + diff --git a/platform/platform-resources/src/idea/PlatformActions.xml b/platform/platform-resources/src/idea/PlatformActions.xml index fb3b31747ae9..4e2657b427ee 100644 --- a/platform/platform-resources/src/idea/PlatformActions.xml +++ b/platform/platform-resources/src/idea/PlatformActions.xml @@ -355,6 +355,7 @@ + diff --git a/platform/testFramework/src/com/intellij/mock/Mock.java b/platform/testFramework/src/com/intellij/mock/Mock.java index a2768f2e1851..4ee338aba63c 100644 --- a/platform/testFramework/src/com/intellij/mock/Mock.java +++ b/platform/testFramework/src/com/intellij/mock/Mock.java @@ -654,6 +654,15 @@ public class Mock { return null; } + @Override + public boolean isMaximized(@NotNull ToolWindow wnd) { + return false; + } + + @Override + public void setMaximized(@NotNull ToolWindow wnd, boolean maximized) { + } + @Override public void notifyByBalloon(@NotNull final String toolWindowId, @NotNull final MessageType type, @NotNull final String htmlBody) { } From 914d0c758d0a883617d48c27308ce89f859c82a6 Mon Sep 17 00:00:00 2001 From: Dmitry Batrak Date: Thu, 6 Nov 2014 14:23:02 +0400 Subject: [PATCH 2/9] EA-39459 - allow safe access to folding model's cached data from non-EDT threads (used by EditFoldingOptionsAction) --- .../openapi/editor/impl/FoldRegionsTree.java | 149 +++++++++--------- 1 file changed, 76 insertions(+), 73 deletions(-) diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/FoldRegionsTree.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/FoldRegionsTree.java index 32e31900b804..d9522f90c4de 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/FoldRegionsTree.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/FoldRegionsTree.java @@ -30,19 +30,13 @@ import java.util.*; * User: cdr */ abstract class FoldRegionsTree { + @NotNull private CachedData myCachedData = new CachedData(); - @SuppressWarnings("UseOfArchaicSystemPropertyAccessors") - public static final boolean DEBUG = Boolean.getBoolean("idea.editor.debug.folding"); - - private FoldRegion[] myCachedVisible; - private FoldRegion[] myCachedTopLevelRegions; - private int[] myCachedEndOffsets; - private int[] myCachedStartOffsets; - private int[] myCachedFoldedLines; int myCachedLastIndex = -1; //sorted using RangeMarker.BY_START_OFFSET comparator //i.e., first by start offset, then, if start offsets are equal, by end offset + @NotNull private List myRegions = ContainerUtil.newArrayList(); private static final Comparator BY_END_OFFSET = new Comparator() { @@ -60,25 +54,15 @@ abstract class FoldRegionsTree { void clear() { clearCachedValues(); - if (myRegions != null) { - for (FoldRegion region : myRegions) { - region.dispose(); - } + for (FoldRegion region : myRegions) { + region.dispose(); } myRegions = new ArrayList(); } void clearCachedValues() { - myCachedVisible = null; - myCachedTopLevelRegions = null; - myCachedEndOffsets = null; - myCachedStartOffsets = null; - myCachedFoldedLines = null; - } - - private boolean isFoldingEnabledAndUpToDate() { - return isFoldingEnabled() && myCachedVisible != null; + myCachedData = new CachedData(); } protected abstract boolean isFoldingEnabled(); @@ -116,13 +100,13 @@ abstract class FoldRegionsTree { myRegions = allValid; } - myCachedTopLevelRegions = toFoldArray(topLevels); - myCachedVisible = toFoldArray(visible); + FoldRegion[] topLevelRegions = toFoldArray(topLevels); + FoldRegion[] visibleRegions = toFoldArray(visible); - Arrays.sort(myCachedTopLevelRegions, BY_END_OFFSET); - Arrays.sort(myCachedVisible, BY_END_OFFSET_REVERSE); + Arrays.sort(topLevelRegions, BY_END_OFFSET); + Arrays.sort(visibleRegions, BY_END_OFFSET_REVERSE); - updateCachedOffsets(); + updateCachedOffsets(visibleRegions, topLevelRegions); } private static void removeRegionsWithSameStartOffset(List regions, FoldRegion region) { @@ -142,44 +126,42 @@ abstract class FoldRegionsTree { } void updateCachedOffsets() { + CachedData cachedData = myCachedData; + updateCachedOffsets(cachedData.visibleRegions, cachedData.topLevelRegions); + } + + private void updateCachedOffsets(FoldRegion[] visibleRegions, FoldRegion[] topLevelRegions) { if (!isFoldingEnabled()) { return; } - if (myCachedVisible == null) { + if (visibleRegions == null) { rebuild(); return; } - for (FoldRegion foldRegion : myCachedVisible) { + for (FoldRegion foldRegion : visibleRegions) { if (!foldRegion.isValid()) { rebuild(); return; } } - int length = myCachedTopLevelRegions.length; - if (myCachedEndOffsets == null || myCachedEndOffsets.length != length) { - if (length != 0) { - myCachedEndOffsets = new int[length]; - myCachedStartOffsets = new int[length]; - myCachedFoldedLines = new int[length]; - } - else { - myCachedEndOffsets = ArrayUtil.EMPTY_INT_ARRAY; - myCachedStartOffsets = ArrayUtil.EMPTY_INT_ARRAY; - myCachedFoldedLines = ArrayUtil.EMPTY_INT_ARRAY; - } - } - + int length = topLevelRegions.length; + int[] startOffsets = length == 0 ? ArrayUtil.EMPTY_INT_ARRAY : new int[length]; + int[] endOffsets = length == 0 ? ArrayUtil.EMPTY_INT_ARRAY : new int[length]; + int[] foldedLines = length == 0 ? ArrayUtil.EMPTY_INT_ARRAY : new int[length]; + int sum = 0; for (int i = 0; i < length; i++) { - FoldRegion region = myCachedTopLevelRegions[i]; - myCachedStartOffsets[i] = region.getStartOffset(); - myCachedEndOffsets[i] = region.getEndOffset() - 1; + FoldRegion region = topLevelRegions[i]; + startOffsets[i] = region.getStartOffset(); + endOffsets[i] = region.getEndOffset() - 1; Document document = region.getDocument(); sum += document.getLineNumber(region.getEndOffset()) - document.getLineNumber(region.getStartOffset()); - myCachedFoldedLines[i] = sum; + foldedLines[i] = sum; } + + myCachedData = new CachedData(visibleRegions, topLevelRegions, startOffsets, endOffsets, foldedLines); } boolean addRegion(@NotNull FoldRegion range) { @@ -225,10 +207,11 @@ abstract class FoldRegionsTree { @Nullable FoldRegion fetchOutermost(int offset) { - if (!isFoldingEnabledAndUpToDate()) return null; + CachedData cachedData = myCachedData; + if (cachedData.isUnavailable()) return null; - final int[] starts = myCachedStartOffsets; - final int[] ends = myCachedEndOffsets; + final int[] starts = cachedData.startOffsets; + final int[] ends = cachedData.endOffsets; if (starts == null || ends == null) { return null; } @@ -244,20 +227,7 @@ abstract class FoldRegionsTree { start = i + 1; } else { - // We encountered situation when cached data is inconsistent. It's not clear what produced that, so, the following was done: - // 1. Corresponding check was added and cached data is rebuilt in case of inconsistency; - // 2. Debug asserts are activated if dedicated flag is on (it's off by default); - if (myCachedStartOffsets[i] != myCachedTopLevelRegions[i].getStartOffset()) { - if (DEBUG) { - assert false : - "inconsistent cached fold data detected. Start offsets: " + Arrays.toString(myCachedStartOffsets) - + ", end offsets: " + Arrays.toString(myCachedEndOffsets) + ", top regions: " + Arrays.toString(myCachedTopLevelRegions) - + ", visible regions: " + Arrays.toString(myCachedVisible); - } - rebuild(); - return fetchOutermost(offset); - } - return myCachedTopLevelRegions[i]; + return cachedData.topLevelRegions[i]; } } @@ -265,14 +235,14 @@ abstract class FoldRegionsTree { } FoldRegion[] fetchVisible() { - if (!isFoldingEnabledAndUpToDate()) return FoldRegion.EMPTY_ARRAY; - return myCachedVisible; + CachedData cachedData = myCachedData; + return cachedData.isUnavailable() ? FoldRegion.EMPTY_ARRAY : cachedData.visibleRegions; } @Nullable FoldRegion[] fetchTopLevel() { - if (!isFoldingEnabledAndUpToDate()) return null; - return myCachedTopLevelRegions; + CachedData cachedData = myCachedData; + return cachedData.isUnavailable() ? null : cachedData.topLevelRegions; } private static boolean contains(FoldRegion outer, FoldRegion inner) { @@ -292,7 +262,7 @@ abstract class FoldRegionsTree { } public FoldRegion[] fetchCollapsedAt(int offset) { - if (!isFoldingEnabledAndUpToDate()) return FoldRegion.EMPTY_ARRAY; + if (myCachedData.isUnavailable()) return FoldRegion.EMPTY_ARRAY; ArrayList allCollapsed = new ArrayList(); for (FoldRegion region : myRegions) { if (!region.isExpanded() && contains(region, offset)) { @@ -316,7 +286,7 @@ abstract class FoldRegionsTree { } FoldRegion[] fetchAllRegions() { - if (!isFoldingEnabledAndUpToDate()) return FoldRegion.EMPTY_ARRAY; + if (myCachedData.isUnavailable()) return FoldRegion.EMPTY_ARRAY; return toFoldArray(myRegions); } @@ -326,14 +296,19 @@ abstract class FoldRegionsTree { } int getFoldedLinesCountBefore(int offset) { - int idx = getLastTopLevelIndexBefore(offset); + CachedData snapshot = myCachedData; + int idx = getLastTopLevelIndexBefore(snapshot, offset); if (idx == -1) return 0; - return myCachedFoldedLines[idx]; + return snapshot.foldedLines[idx]; } public int getLastTopLevelIndexBefore(int offset) { - int[] endOffsets = myCachedEndOffsets; - if (!isFoldingEnabledAndUpToDate() || endOffsets == null) return -1; + return getLastTopLevelIndexBefore(myCachedData, offset); + } + + private static int getLastTopLevelIndexBefore(CachedData snapshot, int offset) { + int[] endOffsets = snapshot.endOffsets; + if (snapshot.isUnavailable() || endOffsets == null) return -1; offset--; // end offsets are decremented in cache int start = 0; @@ -353,4 +328,32 @@ abstract class FoldRegionsTree { return end; } + + private class CachedData implements Cloneable { + private final FoldRegion[] visibleRegions; + private final FoldRegion[] topLevelRegions; + private final int[] startOffsets; + private final int[] endOffsets; + private final int[] foldedLines; + + private CachedData() { + this.visibleRegions = null; + this.topLevelRegions = null; + this.startOffsets = null; + this.endOffsets = null; + this.foldedLines = null; + } + + private CachedData(FoldRegion[] visibleRegions, FoldRegion[] topLevelRegions, int[] startOffsets, int[] endOffsets, int[] foldedLines) { + this.visibleRegions = visibleRegions; + this.topLevelRegions = topLevelRegions; + this.startOffsets = startOffsets; + this.endOffsets = endOffsets; + this.foldedLines = foldedLines; + } + + private boolean isUnavailable() { + return !isFoldingEnabled() || visibleRegions == null; + } + } } From b19bc02f0f5dbb48c836c3e64a19e952cd0a2fd1 Mon Sep 17 00:00:00 2001 From: Dmitry Batrak Date: Fri, 7 Nov 2014 15:33:03 +0400 Subject: [PATCH 3/9] stricter validation in folding model on adding fold regions --- .../folding/impl/FoldingUpdate.java | 8 +- .../openapi/editor/impl/FoldRegionsTree.java | 73 +++++++------------ .../openapi/editor/impl/FoldingModelImpl.java | 8 -- .../intellij/openapi/editor/FoldingTest.java | 14 ++++ 4 files changed, 45 insertions(+), 58 deletions(-) diff --git a/platform/lang-impl/src/com/intellij/codeInsight/folding/impl/FoldingUpdate.java b/platform/lang-impl/src/com/intellij/codeInsight/folding/impl/FoldingUpdate.java index 62f641b37807..07bd92cb687a 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/folding/impl/FoldingUpdate.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/folding/impl/FoldingUpdate.java @@ -59,11 +59,11 @@ public class FoldingUpdate { private static final Key>> CODE_FOLDING_KEY = Key.create("code folding"); private static final Key CODE_FOLDING_FILE_EXTENSION_KEY = Key.create("code folding file extension"); - private static final Comparator COMPARE_BY_OFFSET = new Comparator() { + private static final Comparator COMPARE_BY_OFFSET_REVERSED = new Comparator() { @Override public int compare(PsiElement element, PsiElement element1) { - int startOffsetDiff = element.getTextRange().getStartOffset() - element1.getTextRange().getStartOffset(); - return startOffsetDiff == 0 ? element.getTextRange().getEndOffset() - element1.getTextRange().getEndOffset() : startOffsetDiff; + int startOffsetDiff = element1.getTextRange().getStartOffset() - element.getTextRange().getStartOffset(); + return startOffsetDiff == 0 ? element1.getTextRange().getEndOffset() - element.getTextRange().getEndOffset() : startOffsetDiff; } }; @@ -284,7 +284,7 @@ public class FoldingUpdate { @NotNull @Override protected Map> createMap() { - return new TreeMap>(COMPARE_BY_OFFSET); + return new TreeMap>(COMPARE_BY_OFFSET_REVERSED); } @NotNull diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/FoldRegionsTree.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/FoldRegionsTree.java index d9522f90c4de..758447179367 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/FoldRegionsTree.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/FoldRegionsTree.java @@ -17,8 +17,6 @@ package com.intellij.openapi.editor.impl; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.FoldRegion; -import com.intellij.openapi.editor.RangeMarker; -import com.intellij.openapi.util.TextRange; import com.intellij.util.ArrayUtil; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; @@ -32,8 +30,6 @@ import java.util.*; abstract class FoldRegionsTree { @NotNull private CachedData myCachedData = new CachedData(); - int myCachedLastIndex = -1; - //sorted using RangeMarker.BY_START_OFFSET comparator //i.e., first by start offset, then, if start offsets are equal, by end offset @NotNull @@ -67,8 +63,6 @@ abstract class FoldRegionsTree { protected abstract boolean isFoldingEnabled(); - protected abstract boolean isBatchFoldingProcessing(); - void rebuild() { List topLevels = new ArrayList(myRegions.size() / 2); List visible = new ArrayList(myRegions.size()); @@ -165,43 +159,38 @@ abstract class FoldRegionsTree { } boolean addRegion(@NotNull FoldRegion range) { - // During batchProcessing elements are inserted in ascending order, - // binary search find acceptable insertion place first time - boolean canUseCachedValue = false; - if (isBatchFoldingProcessing() && myCachedLastIndex >= 0 && myCachedLastIndex < myRegions.size()) { - FoldRegion lastRegion = myRegions.get(myCachedLastIndex); - if (RangeMarker.BY_START_OFFSET.compare(lastRegion, range) < 0) { - canUseCachedValue = myCachedLastIndex == (myRegions.size() - 1) - || RangeMarker.BY_START_OFFSET.compare(range, myRegions.get(myCachedLastIndex + 1)) <= 0; + int start = range.getStartOffset(); + int end = range.getEndOffset(); + int insertionIndex = myRegions.size(); + for (int i = 0; i < myRegions.size(); i++) { + FoldRegion region = myRegions.get(i); + int rStart = region.getStartOffset(); + int rEnd = region.getEndOffset(); + if (rStart < start) { + if (region.isValid() && start < rEnd && rEnd < end) { + return false; + } } - } - int index = canUseCachedValue ? myCachedLastIndex + 1 : Collections.binarySearch(myRegions, range, RangeMarker.BY_START_OFFSET); - if (index < 0) index = -index - 1; - - if (index < myRegions.size()) { - FoldRegion foldRegion = myRegions.get(index); - if (TextRange.areSegmentsEqual(foldRegion, range)) { - return false; + else if (rStart == start) { + if (rEnd == end) { + return false; + } + else if (rEnd > end) { + insertionIndex = Math.min(insertionIndex, i); + } } - } - - for (int i = index - 1; i >=0; --i) { - final FoldRegion region = myRegions.get(i); - if (region.getEndOffset() < range.getStartOffset()) break; - if (region.isValid() && intersects(region, range)) { - return false; + else { + insertionIndex = Math.min(insertionIndex, i); + if (rStart > end) { + break; + } + if (region.isValid() && rStart < end && end < rEnd) { + return false; + } } } - for (int i = index; i < myRegions.size(); i++) { - final FoldRegion region = myRegions.get(i); - if (region.getStartOffset() > range.getEndOffset()) break; - if (region.isValid() && intersects(region, range)) { - return false; - } - } - - myRegions.add(myCachedLastIndex = index,range); + myRegions.add(insertionIndex, range); return true; } @@ -249,14 +238,6 @@ abstract class FoldRegionsTree { return outer.getStartOffset() <= inner.getStartOffset() && outer.getEndOffset() >= inner.getEndOffset(); } - private static boolean intersects(FoldRegion r1, FoldRegion r2) { - final int s1 = r1.getStartOffset(); - final int s2 = r2.getStartOffset(); - final int e1 = r1.getEndOffset(); - final int e2 = r2.getEndOffset(); - return s1 < s2 && s2 < e1 && e1 < e2 || s2 < s1 && s1 < e2 && e2 < e1; - } - static boolean contains(FoldRegion region, int offset) { return region.getStartOffset() < offset && region.getEndOffset() > offset; } 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 7dbd0e3e9211..153d3a9405e4 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 @@ -79,11 +79,6 @@ public class FoldingModelImpl implements FoldingModelEx, PrioritizedDocumentList protected boolean isFoldingEnabled() { return FoldingModelImpl.this.isFoldingEnabled(); } - - @Override - protected boolean isBatchFoldingProcessing() { - return myIsBatchFoldingProcessing; - } }; myFoldRegionsProcessed = false; refreshSettings(); @@ -210,12 +205,9 @@ public class FoldingModelImpl implements FoldingModelEx, PrioritizedDocumentList } myIsBatchFoldingProcessing = true; - myFoldTree.myCachedLastIndex = -1; try { operation.run(); } finally { - myFoldTree.myCachedLastIndex = -1; - if (!oldBatchFlag) { if (myFoldRegionsProcessed) { notifyBatchFoldingProcessingDone(moveCaret); diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/editor/FoldingTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/editor/FoldingTest.java index 2f0ee4cebc01..9409cf539c7b 100644 --- a/platform/platform-tests/testSrc/com/intellij/openapi/editor/FoldingTest.java +++ b/platform/platform-tests/testSrc/com/intellij/openapi/editor/FoldingTest.java @@ -86,6 +86,20 @@ public class FoldingTest extends AbstractEditorTest { }); } + public void testIntersectsWithRegionFarInStorageOrder() { + myModel.runBatchFoldingOperation(new Runnable() { + @Override + public void run() { + FoldRegion region = myModel.addFoldRegion(0, 10, "."); + assertNotNull(region); + region = myModel.addFoldRegion(1, 5, "."); + assertNotNull(region); + region = myModel.addFoldRegion(6, 11, "."); + assertNull(region); + } + }); + } + public void testAddEmptyRegion() { FoldRegion region = null; try { From 73c748ee6d398967469934504669a51fd265fa5d Mon Sep 17 00:00:00 2001 From: Dmitry Batrak Date: Fri, 7 Nov 2014 20:12:43 +0400 Subject: [PATCH 4/9] speed up restoring of folding state in editor (DocumentFoldingInfo#setToEditor) currently it's slow for a file with large number of fold regions, that was previously closed with a lot of regions not in their default state - e.g. large XML file after collapsing all regions * use binary search instead of linear search * move search to FoldingModelImpl to avoid excessive garbage creation (for copying an array of fold regions) --- .../intellij/openapi/editor/FoldingModel.java | 6 + .../codeInsight/folding/impl/FoldingUtil.java | 14 +-- .../injected/editor/FoldingModelWindow.java | 14 +++ .../openapi/editor/impl/FoldRegionsTree.java | 111 +++++++++++++++++- .../openapi/editor/impl/FoldingModelImpl.java | 7 ++ .../textarea/TextComponentFoldingModel.java | 7 ++ 6 files changed, 145 insertions(+), 14 deletions(-) diff --git a/platform/editor-ui-api/src/com/intellij/openapi/editor/FoldingModel.java b/platform/editor-ui-api/src/com/intellij/openapi/editor/FoldingModel.java index 289c8166ec8f..96080cf19986 100644 --- a/platform/editor-ui-api/src/com/intellij/openapi/editor/FoldingModel.java +++ b/platform/editor-ui-api/src/com/intellij/openapi/editor/FoldingModel.java @@ -85,6 +85,12 @@ public interface FoldingModel { @Nullable FoldRegion getCollapsedRegionAtOffset(int offset); + /** + * Returns fold region with given boundaries, if it exists, or null otherwise. + */ + @Nullable + FoldRegion getFoldRegion(int startOffset, int endOffset); + /** * Runs an operation which is allowed to modify fold regions in the editor by calling * {@link #addFoldRegion(int, int, String)} and {@link #removeFoldRegion(FoldRegion)}. diff --git a/platform/lang-impl/src/com/intellij/codeInsight/folding/impl/FoldingUtil.java b/platform/lang-impl/src/com/intellij/codeInsight/folding/impl/FoldingUtil.java index f150f9924cb8..8a00fc94d2cb 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/folding/impl/FoldingUtil.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/folding/impl/FoldingUtil.java @@ -29,17 +29,9 @@ public class FoldingUtil { private FoldingUtil() {} @Nullable - public static FoldRegion findFoldRegion(Editor editor, int startOffset, int endOffset) { - FoldRegion[] foldRegions = editor.getFoldingModel().getAllFoldRegions(); - for (FoldRegion region : foldRegions) { - if (region.isValid() && - region.getStartOffset() == startOffset - && region.getEndOffset() == endOffset) { - return region; - } - } - - return null; + public static FoldRegion findFoldRegion(@NotNull Editor editor, int startOffset, int endOffset) { + FoldRegion region = editor.getFoldingModel().getFoldRegion(startOffset, endOffset); + return region != null && region.isValid() ? region : null; } @Nullable diff --git a/platform/lang-impl/src/com/intellij/injected/editor/FoldingModelWindow.java b/platform/lang-impl/src/com/intellij/injected/editor/FoldingModelWindow.java index d87aa78c7ea3..3c839f208312 100644 --- a/platform/lang-impl/src/com/intellij/injected/editor/FoldingModelWindow.java +++ b/platform/lang-impl/src/com/intellij/injected/editor/FoldingModelWindow.java @@ -26,6 +26,7 @@ import com.intellij.openapi.editor.markup.TextAttributes; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.TextRange; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.awt.*; import java.util.ArrayList; @@ -114,6 +115,19 @@ public class FoldingModelWindow implements FoldingModelEx{ return host; //todo convert to window? } + @Nullable + @Override + public FoldRegion getFoldRegion(int startOffset, int endOffset) { + TextRange range = new TextRange(startOffset, endOffset); + TextRange hostRange = myDocumentWindow.injectedToHost(range); + FoldRegion hostRegion = myDelegate.getFoldRegion(hostRange.getStartOffset(), hostRange.getEndOffset()); + if (hostRegion == null) { + return null; + } + FoldingRegionWindow window = hostRegion.getUserData(FOLD_REGION_WINDOW); + return window != null && window.getEditor() == myEditorWindow ? window : null; + } + @Override public void runBatchFoldingOperation(@NotNull Runnable operation) { myDelegate.runBatchFoldingOperation(operation); diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/FoldRegionsTree.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/FoldRegionsTree.java index 758447179367..b339d670989f 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/FoldRegionsTree.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/FoldRegionsTree.java @@ -15,8 +15,8 @@ */ package com.intellij.openapi.editor.impl; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.FoldRegion; +import com.intellij.openapi.editor.*; +import com.intellij.openapi.util.Key; import com.intellij.util.ArrayUtil; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; @@ -309,7 +309,13 @@ abstract class FoldRegionsTree { return end; } - + + @Nullable + public FoldRegion getRegionAt(int startOffset, int endOffset) { + int index = Collections.binarySearch(myRegions, new DummyFoldRegion(startOffset, endOffset), RangeMarker.BY_START_OFFSET); + return index < 0 ? null : myRegions.get(index); + } + private class CachedData implements Cloneable { private final FoldRegion[] visibleRegions; private final FoldRegion[] topLevelRegions; @@ -337,4 +343,103 @@ abstract class FoldRegionsTree { return !isFoldingEnabled() || visibleRegions == null; } } + + private static class DummyFoldRegion implements FoldRegion { + private final int myStartOffset; + private final int myEndOffset; + + private DummyFoldRegion(int startOffset, int endOffset) { + myStartOffset = startOffset; + myEndOffset = endOffset; + } + + @Override + public boolean isExpanded() { + throw new UnsupportedOperationException(); + } + + @Override + public void setExpanded(boolean expanded) { + throw new UnsupportedOperationException(); + } + + @NotNull + @Override + public String getPlaceholderText() { + throw new UnsupportedOperationException(); + } + + @Override + public Editor getEditor() { + throw new UnsupportedOperationException(); + } + + @Nullable + @Override + public FoldingGroup getGroup() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean shouldNeverExpand() { + throw new UnsupportedOperationException(); + } + + @NotNull + @Override + public Document getDocument() { + throw new UnsupportedOperationException(); + } + + @Override + public int getStartOffset() { + return myStartOffset; + } + + @Override + public int getEndOffset() { + return myEndOffset; + } + + @Override + public boolean isValid() { + throw new UnsupportedOperationException(); + } + + @Override + public void setGreedyToLeft(boolean greedy) { + throw new UnsupportedOperationException(); + } + + @Override + public void setGreedyToRight(boolean greedy) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isGreedyToRight() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isGreedyToLeft() { + throw new UnsupportedOperationException(); + } + + @Override + public void dispose() { + throw new UnsupportedOperationException(); + } + + @Nullable + @Override + public T getUserData(@NotNull Key key) { + throw new UnsupportedOperationException(); + } + + @Override + public void putUserData(@NotNull Key key, @Nullable T value) { + throw new UnsupportedOperationException(); + } + } } 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 153d3a9405e4..cec6a03feaec 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 @@ -245,6 +245,13 @@ public class FoldingModelImpl implements FoldingModelEx, PrioritizedDocumentList return myFoldTree.fetchOutermost(offset); } + @Nullable + @Override + public FoldRegion getFoldRegion(int startOffset, int endOffset) { + assertReadAccess(); + return myFoldTree.getRegionAt(startOffset, endOffset); + } + @Override @Nullable public FoldRegion getFoldingPlaceholderAt(Point p) { diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/textarea/TextComponentFoldingModel.java b/platform/platform-impl/src/com/intellij/openapi/editor/textarea/TextComponentFoldingModel.java index e8a0fd840b8e..a6350f7b3e4c 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/textarea/TextComponentFoldingModel.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/textarea/TextComponentFoldingModel.java @@ -18,6 +18,7 @@ package com.intellij.openapi.editor.textarea; import com.intellij.openapi.editor.FoldRegion; import com.intellij.openapi.editor.FoldingModel; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; /** * @author Denis Zhdanov @@ -55,6 +56,12 @@ public class TextComponentFoldingModel implements FoldingModel { return null; } + @Nullable + @Override + public FoldRegion getFoldRegion(int startOffset, int endOffset) { + return null; + } + @Override public void runBatchFoldingOperation(@NotNull Runnable operation) { } From c9d338b7d364c3af008f36cd35791638ce5c6d64 Mon Sep 17 00:00:00 2001 From: Dmitry Avdeev Date: Fri, 14 Nov 2014 17:18:33 +0300 Subject: [PATCH 5/9] cleanup --- .../fixtures/impl/CodeInsightTestFixtureImpl.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/platform/testFramework/src/com/intellij/testFramework/fixtures/impl/CodeInsightTestFixtureImpl.java b/platform/testFramework/src/com/intellij/testFramework/fixtures/impl/CodeInsightTestFixtureImpl.java index dc0e25bfae3c..51b906eb32ef 100644 --- a/platform/testFramework/src/com/intellij/testFramework/fixtures/impl/CodeInsightTestFixtureImpl.java +++ b/platform/testFramework/src/com/intellij/testFramework/fixtures/impl/CodeInsightTestFixtureImpl.java @@ -180,7 +180,6 @@ public class CodeInsightTestFixtureImpl extends BaseFixture implements CodeInsig @Override public VirtualFile copyFileToProject(@NotNull @NonNls final String sourceFilePath, @NotNull @NonNls final String targetPath) { final String testDataPath = getTestDataPath(); - assert testDataPath != null : "test data path not specified"; File fromFile = new File(testDataPath + "/" + sourceFilePath); if (!fromFile.exists()) { @@ -236,7 +235,6 @@ public class CodeInsightTestFixtureImpl extends BaseFixture implements CodeInsig @Override public VirtualFile copyDirectoryToProject(@NotNull @NonNls final String sourceFilePath, @NotNull @NonNls final String targetPath) { final String testDataPath = getTestDataPath(); - assert testDataPath != null : "test data path not specified"; final File fromFile = new File(testDataPath + "/" + sourceFilePath); if (myTempDirFixture instanceof LightTempDirTestFixtureImpl) { @@ -1276,7 +1274,7 @@ public class CodeInsightTestFixtureImpl extends BaseFixture implements CodeInsig @Override public boolean isToolEnabled(HighlightDisplayKey key, PsiElement element) { - return key != null && key.toString() != null && myAvailableTools.containsKey(key.toString()) && !myDisabledInspections.contains(key.toString()); + return key != null && myAvailableTools.containsKey(key.toString()) && !myDisabledInspections.contains(key.toString()); } @Override From 440244dfca340369f104addb85848a07f4789876 Mon Sep 17 00:00:00 2001 From: Dmitry Avdeev Date: Mon, 17 Nov 2014 11:24:32 +0300 Subject: [PATCH 6/9] fighting memory leak --- .../src/com/intellij/ui/docking/impl/DockManagerImpl.java | 1 + 1 file changed, 1 insertion(+) diff --git a/platform/platform-impl/src/com/intellij/ui/docking/impl/DockManagerImpl.java b/platform/platform-impl/src/com/intellij/ui/docking/impl/DockManagerImpl.java index 04033418742f..84cc23a37ca9 100644 --- a/platform/platform-impl/src/com/intellij/ui/docking/impl/DockManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/ui/docking/impl/DockManagerImpl.java @@ -427,6 +427,7 @@ public class DockManagerImpl extends DockManager implements PersistentStateCompo private DockWindow createWindowFor(@Nullable String id, DockContainer container) { String windowId = id != null ? id : String.valueOf(myWindowIdCounter++); DockWindow window = new DockWindow(windowId, myProject, container, container instanceof DockContainer.Dialog); + Disposer.register(container, window); window.setDimensionKey("dock-window-" + windowId); myWindows.put(container, window); return window; From 2825ea41cce4eef1bfc761546fb63b61f7967c01 Mon Sep 17 00:00:00 2001 From: Dmitry Batrak Date: Wed, 5 Nov 2014 17:02:22 +0400 Subject: [PATCH 7/9] IDEA-87318 Backspace should delete whitespace to chosen indent level, or be configurable --- .../codeInsight/CodeInsightSettings.java | 3 +- .../AbstractIndentingBackspaceHandler.java | 68 +++++++++++++++++++ .../editorActions/BackspaceHandler.java | 14 +++- .../SimpleIndentingBackspaceHandler.java | 43 ++++++++++++ ...va => SmartIndentingBackspaceHandler.java} | 60 +++++++--------- .../SimpleIndentingBackspaceHandlerTest.java | 45 ++++++++++++ .../src/META-INF/LangExtensions.xml | 3 +- python/src/META-INF/python-core.xml | 1 - .../python/editor/PythonBackspaceHandler.java | 50 -------------- 9 files changed, 196 insertions(+), 91 deletions(-) create mode 100644 platform/lang-impl/src/com/intellij/codeInsight/editorActions/AbstractIndentingBackspaceHandler.java create mode 100644 platform/lang-impl/src/com/intellij/codeInsight/editorActions/SimpleIndentingBackspaceHandler.java rename platform/lang-impl/src/com/intellij/codeInsight/editorActions/{IndentingBackspaceHandler.java => SmartIndentingBackspaceHandler.java} (85%) create mode 100644 platform/lang-impl/testSources/com/intellij/codeInsight/editorActions/SimpleIndentingBackspaceHandlerTest.java delete mode 100644 python/src/com/jetbrains/python/editor/PythonBackspaceHandler.java diff --git a/platform/lang-impl/src/com/intellij/codeInsight/CodeInsightSettings.java b/platform/lang-impl/src/com/intellij/codeInsight/CodeInsightSettings.java index 99becc715a44..2d368c5f86cb 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/CodeInsightSettings.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/CodeInsightSettings.java @@ -76,10 +76,11 @@ public class CodeInsightSettings implements PersistentStateComponent, C public boolean SHOW_FULL_SIGNATURES_IN_PARAMETER_INFO = false; - @MagicConstant(intValues = {OFF, AUTOINDENT}) + @MagicConstant(intValues = {OFF, AUTOINDENT, INDENT}) public int SMART_BACKSPACE = AUTOINDENT; public static final int OFF = 0; public static final int AUTOINDENT = 1; + public static final int INDENT = 2; public boolean SMART_INDENT_ON_ENTER = true; public boolean INSERT_BRACE_ON_ENTER = true; diff --git a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/AbstractIndentingBackspaceHandler.java b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/AbstractIndentingBackspaceHandler.java new file mode 100644 index 000000000000..bbe65098e818 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/AbstractIndentingBackspaceHandler.java @@ -0,0 +1,68 @@ +/* + * Copyright 2000-2014 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.codeInsight.editorActions; + +import com.intellij.codeInsight.CodeInsightSettings; +import com.intellij.lang.Language; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.PsiFile; +import com.intellij.psi.codeStyle.LanguageCodeStyleSettingsProvider; + +abstract class AbstractIndentingBackspaceHandler extends BackspaceHandlerDelegate { + private final int myMode; + private boolean myEnabled; + + AbstractIndentingBackspaceHandler(int mode) { + myMode = mode; + } + + @Override + public void beforeCharDeleted(char c, PsiFile file, Editor editor) { + myEnabled = false; + if (!StringUtil.isWhiteSpace(c)) { + return; + } + int mode = getBackspaceMode(file.getLanguage()); + if (mode != myMode) { + return; + } + doBeforeCharDeleted(c, file, editor); + myEnabled = true; + } + + @Override + public boolean charDeleted(char c, PsiFile file, Editor editor) { + if (!myEnabled) { + return false; + } + return doCharDeleted(c, file, editor); + } + + protected abstract void doBeforeCharDeleted(char c, PsiFile file, Editor editor); + + protected abstract boolean doCharDeleted(char c, PsiFile file, Editor editor); + + private static int getBackspaceMode(Language language) { + LanguageCodeStyleSettingsProvider codeStyleSettingsProvider = LanguageCodeStyleSettingsProvider.forLanguage(language); + if (codeStyleSettingsProvider != null && codeStyleSettingsProvider.isIndentBasedLanguageSemantics()) { + return CodeInsightSettings.INDENT; + } + else { + return CodeInsightSettings.getInstance().SMART_BACKSPACE; + } + } +} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/BackspaceHandler.java b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/BackspaceHandler.java index 62088f6bff8c..78a81f855915 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/BackspaceHandler.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/BackspaceHandler.java @@ -23,6 +23,7 @@ import com.intellij.lang.injection.InjectedLanguageManager; import com.intellij.openapi.actionSystem.CommonDataKeys; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.EditorModificationUtil; import com.intellij.openapi.editor.LogicalPosition; import com.intellij.openapi.editor.actionSystem.EditorActionHandler; import com.intellij.openapi.editor.actionSystem.EditorWriteActionHandler; @@ -36,6 +37,7 @@ import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiFile; import com.intellij.psi.codeStyle.CodeStyleSettingsManager; import com.intellij.psi.util.PsiUtilBase; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.List; @@ -167,7 +169,7 @@ public class BackspaceHandler extends EditorWriteActionHandler { if (editor.getSelectionModel().hasSelection() || editor.getSelectionModel().hasBlockSelection()) return null; final LogicalPosition caretPos = editor.getCaretModel().getLogicalPosition(); - if (caretPos.line == 0 || caretPos.column == 0) { + if (caretPos.column == 0) { return null; } if (!isWhitespaceBeforeCaret(editor)) { @@ -175,13 +177,21 @@ public class BackspaceHandler extends EditorWriteActionHandler { } // Decrease column down to indentation * n - final int indent = CodeStyleSettingsManager.getSettings(file.getProject()).getIndentSize(file.getFileType()); + final int indent = CodeStyleSettingsManager.getSettings(file.getProject()).getIndentOptionsByFile(file).INDENT_SIZE; int column = (caretPos.column - 1) / indent * indent; if (column < 0) { column = 0; } return new LogicalPosition(caretPos.line, column); } + + public static void deleteToTargetPosition(@NotNull Editor editor, @NotNull LogicalPosition pos) { + final int offset = editor.getCaretModel().getOffset(); + final int targetOffset = editor.logicalPositionToOffset(pos); + editor.getSelectionModel().setSelection(targetOffset, offset); + EditorModificationUtil.deleteSelectedText(editor); + editor.getCaretModel().moveToLogicalPosition(pos); + } public static boolean isWhitespaceBeforeCaret(Editor editor) { final LogicalPosition caretPos = editor.getCaretModel().getLogicalPosition(); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/SimpleIndentingBackspaceHandler.java b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/SimpleIndentingBackspaceHandler.java new file mode 100644 index 000000000000..17e0e0227bfd --- /dev/null +++ b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/SimpleIndentingBackspaceHandler.java @@ -0,0 +1,43 @@ +/* + * Copyright 2000-2014 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.codeInsight.editorActions; + +import com.intellij.codeInsight.CodeInsightSettings; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.LogicalPosition; +import com.intellij.psi.PsiFile; + +public class SimpleIndentingBackspaceHandler extends AbstractIndentingBackspaceHandler { + private LogicalPosition myTargetPosition; + + public SimpleIndentingBackspaceHandler() { + super(CodeInsightSettings.INDENT); + } + + @Override + protected void doBeforeCharDeleted(char c, PsiFile file, Editor editor) { + myTargetPosition = BackspaceHandler.getBackspaceUnindentPosition(file, editor); + } + + @Override + protected boolean doCharDeleted(char c, PsiFile file, Editor editor) { + if (myTargetPosition != null) { + BackspaceHandler.deleteToTargetPosition(editor, myTargetPosition); + return true; + } + return false; + } +} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/IndentingBackspaceHandler.java b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/SmartIndentingBackspaceHandler.java similarity index 85% rename from platform/lang-impl/src/com/intellij/codeInsight/editorActions/IndentingBackspaceHandler.java rename to platform/lang-impl/src/com/intellij/codeInsight/editorActions/SmartIndentingBackspaceHandler.java index 7429f557b29c..8814534d6ea9 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/IndentingBackspaceHandler.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/SmartIndentingBackspaceHandler.java @@ -17,7 +17,9 @@ package com.intellij.codeInsight.editorActions; import com.intellij.codeInsight.CodeInsightSettings; import com.intellij.codeStyle.CodeStyleFacade; -import com.intellij.formatting.*; +import com.intellij.formatting.FormatterEx; +import com.intellij.formatting.FormattingModel; +import com.intellij.formatting.FormattingModelBuilder; import com.intellij.lang.LanguageFormatting; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.CaretModel; @@ -33,40 +35,30 @@ import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiFile; import com.intellij.psi.codeStyle.CodeStyleSettings; import com.intellij.psi.codeStyle.CodeStyleSettingsManager; -import com.intellij.psi.codeStyle.LanguageCodeStyleSettingsProvider; import com.intellij.util.text.CharArrayUtil; import org.jetbrains.annotations.NotNull; -/** - * Makes Backspace action delete all whitespace till next valid indent position - */ -public class IndentingBackspaceHandler extends BackspaceHandlerDelegate { - private static final Logger LOG = Logger.getInstance(IndentingBackspaceHandler.class); +public class SmartIndentingBackspaceHandler extends AbstractIndentingBackspaceHandler { + private static final Logger LOG = Logger.getInstance(SmartIndentingBackspaceHandler.class); - private boolean isApplicable; private boolean caretWasAtLineStart; private String precalculatedSpacing; + public SmartIndentingBackspaceHandler() { + super(CodeInsightSettings.AUTOINDENT); + } + @Override - public void beforeCharDeleted(char c, PsiFile file, Editor editor) { - if (CodeInsightSettings.getInstance().SMART_BACKSPACE != CodeInsightSettings.AUTOINDENT || !StringUtil.isWhiteSpace(c)) { - isApplicable = false; - return; - } - LanguageCodeStyleSettingsProvider codeStyleSettingsProvider = LanguageCodeStyleSettingsProvider.forLanguage(file.getLanguage()); - if (codeStyleSettingsProvider != null && codeStyleSettingsProvider.isIndentBasedLanguageSemantics()) { - isApplicable = false; - return; - } + protected void doBeforeCharDeleted(char c, PsiFile file, Editor editor) { Document document = editor.getDocument(); CharSequence charSequence = document.getCharsSequence(); CaretModel caretModel = editor.getCaretModel(); int caretOffset = caretModel.getOffset(); LogicalPosition pos = caretModel.getLogicalPosition(); - isApplicable = true; caretWasAtLineStart = pos.column == 0; precalculatedSpacing = null; - if (caretWasAtLineStart && pos.line > 0 && caretOffset < charSequence.length() && !StringUtil.isWhiteSpace(charSequence.charAt(caretOffset))) { + if (caretWasAtLineStart && pos.line > 0 && caretOffset < charSequence.length() + && !StringUtil.isWhiteSpace(charSequence.charAt(caretOffset))) { int prevLineEnd = document.getLineEndOffset(pos.line - 1); if (prevLineEnd > 0 && !StringUtil.isWhiteSpace(charSequence.charAt(prevLineEnd - 1))) { PsiDocumentManager.getInstance(file.getProject()).commitDocument(document); @@ -76,11 +68,7 @@ public class IndentingBackspaceHandler extends BackspaceHandlerDelegate { } @Override - public boolean charDeleted(char c, PsiFile file, Editor editor) { - if (!isApplicable) { - return false; - } - + protected boolean doCharDeleted(char c, PsiFile file, Editor editor) { Project project = file.getProject(); Document document = editor.getDocument(); CaretModel caretModel = editor.getCaretModel(); @@ -153,6 +141,17 @@ public class IndentingBackspaceHandler extends BackspaceHandlerDelegate { return true; } + private static String getSpacing(PsiFile file, int offset) { + FormattingModelBuilder builder = LanguageFormatting.INSTANCE.forContext(file); + if (builder == null) { + return ""; + } + CodeStyleSettings settings = CodeStyleSettingsManager.getSettings(file.getProject()); + FormattingModel model = builder.createModel(file, settings); + int spacing = FormatterEx.getInstance().getSpacingForBlockAtOffset(model, offset); + return StringUtil.repeatSymbol(' ', spacing); + } + private static int getTabSize(@NotNull CodeStyleFacade codeStyleFacade, @NotNull Document document) { VirtualFile file = FileDocumentManager.getInstance().getFile(document); FileType fileType = file == null ? null : file.getFileType(); @@ -175,15 +174,4 @@ public class IndentingBackspaceHandler extends BackspaceHandlerDelegate { } return width; } - - private static String getSpacing(PsiFile file, int offset) { - FormattingModelBuilder builder = LanguageFormatting.INSTANCE.forContext(file); - if (builder == null) { - return ""; - } - CodeStyleSettings settings = CodeStyleSettingsManager.getSettings(file.getProject()); - FormattingModel model = builder.createModel(file, settings); - int spacing = FormatterEx.getInstance().getSpacingForBlockAtOffset(model, offset); - return StringUtil.repeatSymbol(' ', spacing); - } } diff --git a/platform/lang-impl/testSources/com/intellij/codeInsight/editorActions/SimpleIndentingBackspaceHandlerTest.java b/platform/lang-impl/testSources/com/intellij/codeInsight/editorActions/SimpleIndentingBackspaceHandlerTest.java new file mode 100644 index 000000000000..514cf7fcdcf1 --- /dev/null +++ b/platform/lang-impl/testSources/com/intellij/codeInsight/editorActions/SimpleIndentingBackspaceHandlerTest.java @@ -0,0 +1,45 @@ +/* + * Copyright 2000-2014 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.codeInsight.editorActions; + +import com.intellij.codeInsight.CodeInsightSettings; +import com.intellij.openapi.actionSystem.IdeActions; +import com.intellij.testFramework.LightPlatformCodeInsightTestCase; + +public class SimpleIndentingBackspaceHandlerTest extends LightPlatformCodeInsightTestCase { + public void testBasicUnindent() { + doTest(" text", + " text"); + } + + public void testAtLineStart() { + doTest("line1\nline2", + "line1line2"); + } + + private void doTest(String before, String after) { + int savedMode = CodeInsightSettings.getInstance().SMART_BACKSPACE; + try { + CodeInsightSettings.getInstance().SMART_BACKSPACE = CodeInsightSettings.INDENT; + configureFromFileText(getTestName(false) + ".txt", before); + executeAction(IdeActions.ACTION_EDITOR_BACKSPACE); + checkResultByText(after); + } + finally { + CodeInsightSettings.getInstance().SMART_BACKSPACE = savedMode; + } + } +} diff --git a/platform/platform-resources/src/META-INF/LangExtensions.xml b/platform/platform-resources/src/META-INF/LangExtensions.xml index 43c887388073..04d82a551392 100644 --- a/platform/platform-resources/src/META-INF/LangExtensions.xml +++ b/platform/platform-resources/src/META-INF/LangExtensions.xml @@ -578,7 +578,8 @@ id="EnterBetweenBracesHandler"/> - + + diff --git a/python/src/META-INF/python-core.xml b/python/src/META-INF/python-core.xml index 5e4cb1964c76..395c968e5882 100644 --- a/python/src/META-INF/python-core.xml +++ b/python/src/META-INF/python-core.xml @@ -39,7 +39,6 @@ - diff --git a/python/src/com/jetbrains/python/editor/PythonBackspaceHandler.java b/python/src/com/jetbrains/python/editor/PythonBackspaceHandler.java deleted file mode 100644 index 3a056f64639b..000000000000 --- a/python/src/com/jetbrains/python/editor/PythonBackspaceHandler.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2000-2014 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.jetbrains.python.editor; - -import com.intellij.codeInsight.editorActions.BackspaceHandler; -import com.intellij.codeInsight.editorActions.BackspaceHandlerDelegate; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.EditorModificationUtil; -import com.intellij.openapi.editor.LogicalPosition; -import com.intellij.psi.PsiFile; -import com.jetbrains.python.PythonFileType; - -/** - * @author yole - */ -public class PythonBackspaceHandler extends BackspaceHandlerDelegate { - private LogicalPosition myTargetPosition; - - public void beforeCharDeleted(final char c, final PsiFile file, final Editor editor) { - if (PythonFileType.INSTANCE != file.getFileType()) return; - myTargetPosition = BackspaceHandler.getBackspaceUnindentPosition(file, editor); - } - - public boolean charDeleted(final char c, final PsiFile file, final Editor editor) { - if (myTargetPosition != null) { - // Remove all the following spaces before moving to targetPosition - final int offset = editor.getCaretModel().getOffset(); - final int targetOffset = editor.logicalPositionToOffset(myTargetPosition); - editor.getSelectionModel().setSelection(targetOffset, offset); - EditorModificationUtil.deleteSelectedText(editor); - editor.getCaretModel().moveToLogicalPosition(myTargetPosition); - myTargetPosition = null; - return true; - } - return false; - } -} From 83289cacdf164c3e1ea8c794b95a77a5afadc057 Mon Sep 17 00:00:00 2001 From: Dmitry Batrak Date: Thu, 6 Nov 2014 13:43:05 +0400 Subject: [PATCH 8/9] fix for a specific case of IDEA-131454, refactoring --- .../SmartIndentingBackspaceHandler.java | 121 ++++++------------ .../beforeCommentWithTab-after.java | 3 + .../beforeCommentWithTab.java | 4 + 3 files changed, 48 insertions(+), 80 deletions(-) create mode 100644 platform/lang-impl/testData/editor/indentingBackspace/beforeCommentWithTab-after.java create mode 100644 platform/lang-impl/testData/editor/indentingBackspace/beforeCommentWithTab.java diff --git a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/SmartIndentingBackspaceHandler.java b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/SmartIndentingBackspaceHandler.java index 8814534d6ea9..8ab82c453ba1 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/SmartIndentingBackspaceHandler.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/SmartIndentingBackspaceHandler.java @@ -22,10 +22,7 @@ import com.intellij.formatting.FormattingModel; import com.intellij.formatting.FormattingModelBuilder; import com.intellij.lang.LanguageFormatting; import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.CaretModel; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.LogicalPosition; +import com.intellij.openapi.editor.*; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.project.Project; @@ -41,8 +38,8 @@ import org.jetbrains.annotations.NotNull; public class SmartIndentingBackspaceHandler extends AbstractIndentingBackspaceHandler { private static final Logger LOG = Logger.getInstance(SmartIndentingBackspaceHandler.class); - private boolean caretWasAtLineStart; - private String precalculatedSpacing; + private String myReplacement; + private int myStartOffset; public SmartIndentingBackspaceHandler() { super(CodeInsightSettings.AUTOINDENT); @@ -50,94 +47,58 @@ public class SmartIndentingBackspaceHandler extends AbstractIndentingBackspaceHa @Override protected void doBeforeCharDeleted(char c, PsiFile file, Editor editor) { + Project project = file.getProject(); Document document = editor.getDocument(); - CharSequence charSequence = document.getCharsSequence(); + CharSequence charSequence = document.getImmutableCharSequence(); CaretModel caretModel = editor.getCaretModel(); int caretOffset = caretModel.getOffset(); LogicalPosition pos = caretModel.getLogicalPosition(); - caretWasAtLineStart = pos.column == 0; - precalculatedSpacing = null; - if (caretWasAtLineStart && pos.line > 0 && caretOffset < charSequence.length() - && !StringUtil.isWhiteSpace(charSequence.charAt(caretOffset))) { - int prevLineEnd = document.getLineEndOffset(pos.line - 1); - if (prevLineEnd > 0 && !StringUtil.isWhiteSpace(charSequence.charAt(prevLineEnd - 1))) { - PsiDocumentManager.getInstance(file.getProject()).commitDocument(document); - precalculatedSpacing = getSpacing(file, caretOffset); + int lineStartOffset = document.getLineStartOffset(pos.line); + int beforeWhitespaceOffset = CharArrayUtil.shiftBackward(charSequence, caretOffset - 1, " \t") + 1; + if (beforeWhitespaceOffset != lineStartOffset) { + myReplacement = null; + return; + } + PsiDocumentManager.getInstance(project).commitDocument(document); + CodeStyleFacade codeStyleFacade = CodeStyleFacade.getInstance(project); + myReplacement = codeStyleFacade.getLineIndent(document, lineStartOffset); + if (myReplacement == null) { + return; + } + int tabSize = getTabSize(codeStyleFacade, document); + int targetColumn = getWidth(myReplacement, tabSize); + int endOffset = CharArrayUtil.shiftForward(charSequence, caretOffset, " \t"); + LogicalPosition logicalPosition = caretOffset < endOffset ? editor.offsetToLogicalPosition(endOffset) : pos; + int currentColumn = logicalPosition.column; + if (currentColumn > targetColumn) { + myStartOffset = lineStartOffset; + } + else if (logicalPosition.line == 0) { + myStartOffset = 0; + myReplacement = ""; + } + else { + int prevLineEndOffset = document.getLineEndOffset(logicalPosition.line - 1); + myStartOffset = CharArrayUtil.shiftBackward(charSequence, prevLineEndOffset - 1, " \t") + 1; + if (myStartOffset != document.getLineStartOffset(logicalPosition.line - 1)) { + myReplacement = getSpacing(file, endOffset); } } } @Override protected boolean doCharDeleted(char c, PsiFile file, Editor editor) { - Project project = file.getProject(); + if (myReplacement == null) { + return false; + } + Document document = editor.getDocument(); CaretModel caretModel = editor.getCaretModel(); + int endOffset = CharArrayUtil.shiftForward(document.getImmutableCharSequence(), caretModel.getOffset(), " \t"); - int caretOffset = caretModel.getOffset(); - int offset = CharArrayUtil.shiftForward(document.getCharsSequence(), caretOffset, " \t"); - int beforeWhitespaceOffset = CharArrayUtil.shiftBackward(document.getCharsSequence(), offset - 1, " \t") + 1; - LogicalPosition logicalPosition = caretOffset < offset ? editor.offsetToLogicalPosition(offset) : caretModel.getLogicalPosition(); - int lineStartOffset = document.getLineStartOffset(logicalPosition.line); - if (lineStartOffset < beforeWhitespaceOffset) { - if (caretWasAtLineStart && beforeWhitespaceOffset <= offset) { - String spacing; - if (precalculatedSpacing == null) { - PsiDocumentManager.getInstance(project).commitDocument(document); - spacing = getSpacing(file, offset); - } - else { - spacing = precalculatedSpacing; - } - if (beforeWhitespaceOffset < offset || !spacing.isEmpty()) { - document.replaceString(beforeWhitespaceOffset, offset, spacing); - caretModel.moveToOffset(beforeWhitespaceOffset + spacing.length()); - return true; - } - } - return false; - } + document.replaceString(myStartOffset, endOffset, myReplacement); + caretModel.moveToOffset(myStartOffset + myReplacement.length()); - PsiDocumentManager.getInstance(project).commitDocument(document); - CodeStyleFacade codeStyleFacade = CodeStyleFacade.getInstance(project); - String indent = codeStyleFacade.getLineIndent(document, lineStartOffset); - if (indent == null) { - return false; - } - - int tabSize = getTabSize(codeStyleFacade, document); - int targetColumn = getWidth(indent, tabSize); - - if (logicalPosition.column == targetColumn) { - if (caretOffset < offset) { - caretModel.moveToLogicalPosition(logicalPosition); - return true; - } - return false; - } - - if (caretWasAtLineStart || logicalPosition.column > targetColumn) { - document.replaceString(lineStartOffset, offset, indent); - caretModel.moveToLogicalPosition(new LogicalPosition(logicalPosition.line, targetColumn)); - return true; - } - - if (logicalPosition.line == 0) { - return false; - } - - int prevLineStartOffset = document.getLineStartOffset(logicalPosition.line - 1); - int prevLineEndOffset = document.getLineEndOffset(logicalPosition.line - 1); - int targetOffset = CharArrayUtil.shiftBackward(document.getCharsSequence(), prevLineEndOffset - 1, " \t") + 1; - - if (prevLineStartOffset < targetOffset) { - String spacing = getSpacing(file, offset); - document.replaceString(targetOffset, offset, spacing); - caretModel.moveToOffset(targetOffset + spacing.length()); - } - else { - document.replaceString(prevLineStartOffset, offset, indent); - caretModel.moveToLogicalPosition(new LogicalPosition(logicalPosition.line - 1, targetColumn)); - } return true; } diff --git a/platform/lang-impl/testData/editor/indentingBackspace/beforeCommentWithTab-after.java b/platform/lang-impl/testData/editor/indentingBackspace/beforeCommentWithTab-after.java new file mode 100644 index 000000000000..de6981762dd0 --- /dev/null +++ b/platform/lang-impl/testData/editor/indentingBackspace/beforeCommentWithTab-after.java @@ -0,0 +1,3 @@ +class A { + // comment +} diff --git a/platform/lang-impl/testData/editor/indentingBackspace/beforeCommentWithTab.java b/platform/lang-impl/testData/editor/indentingBackspace/beforeCommentWithTab.java new file mode 100644 index 000000000000..3d98bc8d7b2f --- /dev/null +++ b/platform/lang-impl/testData/editor/indentingBackspace/beforeCommentWithTab.java @@ -0,0 +1,4 @@ +class A { + + // comment +} From 6e6ce9aaace980530566ba37a15f871d817277a1 Mon Sep 17 00:00:00 2001 From: Dmitry Batrak Date: Mon, 17 Nov 2014 13:02:45 +0300 Subject: [PATCH 9/9] IDEA-97689 Undo causes code corruption --- .../command/impl/DocumentUndoProvider.java | 49 ++++++++++--------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/platform/platform-impl/src/com/intellij/openapi/command/impl/DocumentUndoProvider.java b/platform/platform-impl/src/com/intellij/openapi/command/impl/DocumentUndoProvider.java index 5895026caaf5..777ec3a6ea1e 100644 --- a/platform/platform-impl/src/com/intellij/openapi/command/impl/DocumentUndoProvider.java +++ b/platform/platform-impl/src/com/intellij/openapi/command/impl/DocumentUndoProvider.java @@ -20,12 +20,11 @@ import com.intellij.openapi.command.undo.DocumentReference; import com.intellij.openapi.command.undo.DocumentReferenceManager; import com.intellij.openapi.command.undo.UndoConstants; import com.intellij.openapi.command.undo.UndoManager; -import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.EditorFactory; -import com.intellij.openapi.editor.event.DocumentAdapter; import com.intellij.openapi.editor.event.DocumentEvent; +import com.intellij.openapi.editor.event.DocumentListener; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Key; @@ -33,7 +32,6 @@ import com.intellij.openapi.vfs.VirtualFile; import org.jetbrains.annotations.Nullable; public class DocumentUndoProvider implements Disposable { - private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.command.impl.DocumentUndoProvider"); private static final Key UNDOING_EDITOR_CHANGE = Key.create("DocumentUndoProvider.UNDOING_EDITOR_CHANGE"); private final Project myProject; @@ -61,32 +59,39 @@ public class DocumentUndoProvider implements Disposable { if (doc != null) doc.putUserData(UNDOING_EDITOR_CHANGE, null); } - private class MyEditorDocumentListener extends DocumentAdapter { + private class MyEditorDocumentListener implements DocumentListener { + @Override + public void beforeDocumentChange(DocumentEvent e) { + Document document = e.getDocument(); + if (shouldBeIgnored(document)) return; + + UndoManagerImpl undoManager = getUndoManager(); + if (undoManager.isActive() && isUndoable(document) && (undoManager.isUndoInProgress() || undoManager.isRedoInProgress()) && + document.getUserData(UNDOING_EDITOR_CHANGE) != Boolean.TRUE) { + throw new IllegalStateException("Do not change documents during undo as it will break undo sequence."); + } + } + @Override public void documentChanged(final DocumentEvent e) { Document document = e.getDocument(); - - // if we don't ignore copy's events, we will receive notification - // for the same event twice (from original document too) - // and undo will work incorrectly - if (UndoManagerImpl.isCopy(document)) return; - - if (allEditorsAreViewersFor(document)) return; - if (!shouldRecordActions(document)) return; + if (shouldBeIgnored(document)) return; UndoManagerImpl undoManager = getUndoManager(); - if (!undoManager.isActive() || !isUndoable(document)) { + if (undoManager.isActive() && isUndoable(document)) { + registerUndoableAction(e); + } + else { registerNonUndoableAction(document); - return; } - - if (undoManager.isUndoInProgress() || undoManager.isRedoInProgress()) { - if (document.getUserData(UNDOING_EDITOR_CHANGE) != Boolean.TRUE) { - LOG.error("Do not change documents during undo as it will break undo sequence."); - } - } - - registerUndoableAction(e); + } + + private boolean shouldBeIgnored(Document document) { + return UndoManagerImpl.isCopy(document) // if we don't ignore copy's events, we will receive notification + // for the same event twice (from original document too) + // and undo will work incorrectly + || allEditorsAreViewersFor(document) + || !shouldRecordActions(document); } private boolean shouldRecordActions(final Document document) {