Merge remote-tracking branch 'origin/master'

This commit is contained in:
Konstantin Bulenkov
2014-11-17 11:19:49 +01:00
35 changed files with 829 additions and 460 deletions
@@ -85,6 +85,12 @@ public interface FoldingModel {
@Nullable
FoldRegion getCollapsedRegionAtOffset(int offset);
/**
* Returns fold region with given boundaries, if it exists, or <code>null</code> 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)}.
@@ -76,10 +76,11 @@ public class CodeInsightSettings implements PersistentStateComponent<Element>, 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;
@@ -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;
}
}
}
@@ -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();
@@ -1,189 +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.intellij.codeInsight.editorActions;
import com.intellij.codeInsight.CodeInsightSettings;
import com.intellij.codeStyle.CodeStyleFacade;
import com.intellij.formatting.*;
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.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
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);
private boolean isApplicable;
private boolean caretWasAtLineStart;
private String precalculatedSpacing;
@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;
}
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))) {
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);
}
}
}
@Override
public boolean charDeleted(char c, PsiFile file, Editor editor) {
if (!isApplicable) {
return false;
}
Project project = file.getProject();
Document document = editor.getDocument();
CaretModel caretModel = editor.getCaretModel();
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;
}
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;
}
private static int getTabSize(@NotNull CodeStyleFacade codeStyleFacade, @NotNull Document document) {
VirtualFile file = FileDocumentManager.getInstance().getFile(document);
FileType fileType = file == null ? null : file.getFileType();
return codeStyleFacade.getTabSize(fileType);
}
private static int getWidth(@NotNull String indent, int tabSize) {
int width = 0;
for (int i = 0; i < indent.length(); i++) {
char c = indent.charAt(i);
switch (c) {
case '\t':
width = tabSize * (width / tabSize + 1);
break;
default:
LOG.error("Unexpected whitespace character: " + ((int)c));
case ' ':
width++;
}
}
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);
}
}
@@ -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;
}
}
@@ -0,0 +1,138 @@
/*
* 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.codeStyle.CodeStyleFacade;
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.*;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
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.util.text.CharArrayUtil;
import org.jetbrains.annotations.NotNull;
public class SmartIndentingBackspaceHandler extends AbstractIndentingBackspaceHandler {
private static final Logger LOG = Logger.getInstance(SmartIndentingBackspaceHandler.class);
private String myReplacement;
private int myStartOffset;
public SmartIndentingBackspaceHandler() {
super(CodeInsightSettings.AUTOINDENT);
}
@Override
protected void doBeforeCharDeleted(char c, PsiFile file, Editor editor) {
Project project = file.getProject();
Document document = editor.getDocument();
CharSequence charSequence = document.getImmutableCharSequence();
CaretModel caretModel = editor.getCaretModel();
int caretOffset = caretModel.getOffset();
LogicalPosition pos = caretModel.getLogicalPosition();
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) {
if (myReplacement == null) {
return false;
}
Document document = editor.getDocument();
CaretModel caretModel = editor.getCaretModel();
int endOffset = CharArrayUtil.shiftForward(document.getImmutableCharSequence(), caretModel.getOffset(), " \t");
document.replaceString(myStartOffset, endOffset, myReplacement);
caretModel.moveToOffset(myStartOffset + myReplacement.length());
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();
return codeStyleFacade.getTabSize(fileType);
}
private static int getWidth(@NotNull String indent, int tabSize) {
int width = 0;
for (int i = 0; i < indent.length(); i++) {
char c = indent.charAt(i);
switch (c) {
case '\t':
width = tabSize * (width / tabSize + 1);
break;
default:
LOG.error("Unexpected whitespace character: " + ((int)c));
case ' ':
width++;
}
}
return width;
}
}
@@ -59,11 +59,11 @@ public class FoldingUpdate {
private static final Key<ParameterizedCachedValue<Runnable, Couple<Boolean>>> CODE_FOLDING_KEY = Key.create("code folding");
private static final Key<String> CODE_FOLDING_FILE_EXTENSION_KEY = Key.create("code folding file extension");
private static final Comparator<PsiElement> COMPARE_BY_OFFSET = new Comparator<PsiElement>() {
private static final Comparator<PsiElement> COMPARE_BY_OFFSET_REVERSED = new Comparator<PsiElement>() {
@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<PsiElement, Collection<FoldingDescriptor>> createMap() {
return new TreeMap<PsiElement, Collection<FoldingDescriptor>>(COMPARE_BY_OFFSET);
return new TreeMap<PsiElement, Collection<FoldingDescriptor>>(COMPARE_BY_OFFSET_REVERSED);
}
@NotNull
@@ -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
@@ -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);
@@ -0,0 +1,3 @@
class A {
<caret>// comment
}
@@ -0,0 +1,4 @@
class A {
<caret>// comment
}
@@ -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(" <caret>text",
" <caret>text");
}
public void testAtLineStart() {
doTest("line1\n<caret>line2",
"line1<caret>line2");
}
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;
}
}
}
@@ -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()) {
@@ -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);
}
@@ -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<Boolean> 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) {
@@ -15,10 +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.RangeMarker;
import com.intellij.openapi.util.TextRange;
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;
@@ -30,19 +28,11 @@ import java.util.*;
* User: cdr
*/
abstract class FoldRegionsTree {
@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;
@NotNull private CachedData myCachedData = new CachedData();
//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<FoldRegion> myRegions = ContainerUtil.newArrayList();
private static final Comparator<FoldRegion> BY_END_OFFSET = new Comparator<FoldRegion>() {
@@ -60,31 +50,19 @@ abstract class FoldRegionsTree {
void clear() {
clearCachedValues();
if (myRegions != null) {
for (FoldRegion region : myRegions) {
region.dispose();
}
for (FoldRegion region : myRegions) {
region.dispose();
}
myRegions = new ArrayList<FoldRegion>();
}
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();
protected abstract boolean isBatchFoldingProcessing();
void rebuild() {
List<FoldRegion> topLevels = new ArrayList<FoldRegion>(myRegions.size() / 2);
List<FoldRegion> visible = new ArrayList<FoldRegion>(myRegions.size());
@@ -116,13 +94,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<FoldRegion> regions, FoldRegion region) {
@@ -142,93 +120,87 @@ 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) {
// 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;
}
@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 +216,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,34 +224,26 @@ 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) {
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;
}
public FoldRegion[] fetchCollapsedAt(int offset) {
if (!isFoldingEnabledAndUpToDate()) return FoldRegion.EMPTY_ARRAY;
if (myCachedData.isUnavailable()) return FoldRegion.EMPTY_ARRAY;
ArrayList<FoldRegion> allCollapsed = new ArrayList<FoldRegion>();
for (FoldRegion region : myRegions) {
if (!region.isExpanded() && contains(region, offset)) {
@@ -316,7 +267,7 @@ abstract class FoldRegionsTree {
}
FoldRegion[] fetchAllRegions() {
if (!isFoldingEnabledAndUpToDate()) return FoldRegion.EMPTY_ARRAY;
if (myCachedData.isUnavailable()) return FoldRegion.EMPTY_ARRAY;
return toFoldArray(myRegions);
}
@@ -326,14 +277,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 +309,137 @@ 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;
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;
}
}
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> T getUserData(@NotNull Key<T> key) {
throw new UnsupportedOperationException();
}
@Override
public <T> void putUserData(@NotNull Key<T> key, @Nullable T value) {
throw new UnsupportedOperationException();
}
}
}
@@ -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);
@@ -253,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) {
@@ -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) {
}
@@ -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);
@@ -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"));
}
}
@@ -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();
@@ -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) {
@@ -120,6 +120,7 @@ public final class ToolWindowManagerImpl extends ToolWindowManagerEx implements
private final FileEditorManager myFileEditorManager;
private final LafManager myLafManager;
private final Map<String, Balloon> myWindow2Balloon = new HashMap<String, Balloon>();
private Pair<String, Integer> 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);
}
@@ -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<StripeButton, WindowInfoImpl> myButton2Info;
private final HashMap<InternalDecorator, WindowInfoImpl> myDecorator2Info;
private final HashMap<String, Float> myId2SplitProportion;
private Pair<ToolWindow, Integer> 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<Component>() {
@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<Resizer, Component> 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<Resizer, Component> 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<Resizer, Component> 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);
@@ -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;
@@ -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
@@ -578,7 +578,8 @@
id="EnterBetweenBracesHandler"/>
<enterHandlerDelegate implementation="com.intellij.codeInsight.editorActions.enter.EnterAfterJavadocTagHandler"/>
<backspaceHandlerDelegate implementation="com.intellij.codeInsight.editorActions.IndentingBackspaceHandler" />
<backspaceHandlerDelegate implementation="com.intellij.codeInsight.editorActions.SimpleIndentingBackspaceHandler" />
<backspaceHandlerDelegate implementation="com.intellij.codeInsight.editorActions.SmartIndentingBackspaceHandler" />
<codeInsight.linkHandler prefix="#inspection/" handlerClass="com.intellij.codeInsight.hint.InspectionDescriptionLinkHandler"/>
<codeInsight.linkHandler prefix="#navigation/" handlerClass="com.intellij.codeInsight.hint.NavigationLinkHandler"/>
@@ -774,6 +774,9 @@
<action id="ResizeToolWindowDown">
<keyboard-shortcut first-keystroke="control shift DOWN"/>
</action>
<action id="MaximizeToolWindow">
<keyboard-shortcut first-keystroke="control shift QUOTE"/>
</action>
<action id="HideSideWindows"/>
<action id="ShowPopupMenu">
<keyboard-shortcut first-keystroke="CONTEXT_MENU"/>
@@ -355,6 +355,7 @@
<action id="HideAllWindows" class="com.intellij.ide.actions.HideAllToolWindowsAction"/>
<action id="CloseActiveTab" class="com.intellij.ide.actions.CloseActiveTabAction"/>
<action id="JumpToLastWindow" class="com.intellij.ide.actions.JumpToLastWindowAction"/>
<action id="MaximizeToolWindow" class="com.intellij.openapi.wm.impl.MaximizeToolWindowAction"/>
<separator/>
<action id="TogglePinnedMode" class="com.intellij.ide.actions.TogglePinnedModeAction"/>
<action id="ToggleDockMode" class="com.intellij.ide.actions.ToggleDockModeAction"/>
@@ -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 {
@@ -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) {
}
@@ -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
-1
View File
@@ -39,7 +39,6 @@
<lang.emacs language="Python" implementationClass="com.jetbrains.python.editor.PyEmacsHandler"/>
<annotator language="Python" implementationClass="com.jetbrains.python.validation.PyAnnotatingVisitor"/>
<annotator language="Python" implementationClass="com.jetbrains.python.validation.PyDumbAwareAnnotator"/>
<backspaceHandlerDelegate implementation="com.jetbrains.python.editor.PythonBackspaceHandler"/>
<quoteHandler fileType="Python" className="com.jetbrains.python.editor.PythonQuoteHandler"/>
<enterHandlerDelegate implementation="com.jetbrains.python.editor.PythonEnterHandler"/>
<enterHandlerDelegate implementation="com.jetbrains.python.editor.PyEnterAtIndentHandler" order="first"/>
@@ -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;
}
}