auto-indenting Backspace

This commit is contained in:
Dmitry Batrak
2014-06-11 12:58:48 +04:00
parent 40a2fcd673
commit a6f877ac3b
41 changed files with 362 additions and 24 deletions
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2010 JetBrains s.r.o.
* 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.
@@ -21,12 +21,10 @@ import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiFile;
import com.intellij.util.ArrayUtil;
import com.intellij.util.containers.HashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Set;
@@ -225,6 +223,10 @@ public abstract class LanguageCodeStyleSettingsProvider {
return fieldCollector.getCollectedFields();
}
public boolean isIndentBasedLanguageSemantics() {
return false;
}
private final class SupportedFieldCollector implements CodeStyleSettingsCustomizable {
private final Set<String> myCollectedFields = new HashSet<String>();
private SettingsType myCurrSettingsType;
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.intellij.application.options.editor.EditorSmartKeysConfigurable">
<grid id="27dc6" binding="myRootPanel" layout-manager="GridLayoutManager" row-count="11" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="0">
<grid id="27dc6" binding="myRootPanel" layout-manager="GridLayoutManager" row-count="12" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="0">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<xy x="20" y="20" width="500" height="400"/>
@@ -10,7 +10,7 @@
<children>
<vspacer id="a9cd">
<constraints>
<grid row="10" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
<grid row="11" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
</constraints>
</vspacer>
<component id="18e81" class="javax.swing.JCheckBox" binding="myCbSmartHome">
@@ -33,7 +33,7 @@
</component>
<component id="67fc" class="javax.swing.JCheckBox" binding="myCbInsertPairBracket">
<constraints>
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
<grid row="3" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<selected value="true"/>
@@ -42,7 +42,7 @@
</component>
<component id="7263c" class="javax.swing.JCheckBox" binding="myCbInsertPairQuote">
<constraints>
<grid row="3" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
<grid row="4" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<selected value="true"/>
@@ -51,7 +51,7 @@
</component>
<component id="278d0" class="javax.swing.JCheckBox" binding="myCbCamelWords">
<constraints>
<grid row="5" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
<grid row="6" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text resource-bundle="messages/ApplicationBundle" key="checkbox.use.camelhumps.words"/>
@@ -60,7 +60,7 @@
<grid id="6ebf" layout-manager="GridLayoutManager" row-count="1" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="5" right="5"/>
<constraints>
<grid row="8" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="3" anchor="1" fill="3" indent="0" use-parent-layout="false"/>
<grid row="9" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="3" anchor="1" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
<border type="none"/>
@@ -88,7 +88,7 @@
</grid>
<grid id="b08a8" binding="myAddonPanel" layout-manager="GridBagLayout">
<constraints>
<grid row="9" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="8" fill="2" indent="0" use-parent-layout="false"/>
<grid row="10" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="8" fill="2" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
<border type="none"/>
@@ -97,7 +97,7 @@
<grid id="5344e" layout-manager="GridLayoutManager" row-count="3" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<grid row="7" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
<grid row="8" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
<clientProperties>
@@ -135,26 +135,34 @@
</grid>
<hspacer id="6cc57">
<constraints>
<grid row="7" column="1" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
<grid row="8" column="1" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
</hspacer>
<component id="158ea" class="javax.swing.JCheckBox" binding="myCbSurroundSelectionOnTyping" default-binding="true">
<constraints>
<grid row="6" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Surround selection on typing quote or brace"/>
</properties>
</component>
<component id="a64de" class="javax.swing.JCheckBox" binding="myCbReformatBlockOnTypingRBrace">
<constraints>
<grid row="4" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
<grid row="5" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<selected value="true"/>
<text resource-bundle="messages/ApplicationBundle" key="checkbox.reformat.on.typing.rbrace"/>
</properties>
</component>
<component id="158ea" class="javax.swing.JCheckBox" binding="myCbSurroundSelectionOnTyping" default-binding="true">
<constraints>
<grid row="7" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Surround selection on typing quote or brace"/>
</properties>
</component>
<component id="87366" class="javax.swing.JCheckBox" binding="myCbIndentingBackspace">
<constraints>
<grid row="2" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text resource-bundle="messages/ApplicationBundle" key="checkbox.indenting.backspace"/>
</properties>
</component>
</children>
</grid>
</form>
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2009 JetBrains s.r.o.
* 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.
@@ -63,6 +63,7 @@ public class EditorSmartKeysConfigurable extends CompositeConfigurable<UnnamedCo
private JCheckBox myCbInsertJavadocStubOnEnter;
private JCheckBox myCbSurroundSelectionOnTyping;
private JCheckBox myCbReformatBlockOnTypingRBrace;
private JCheckBox myCbIndentingBackspace;
private boolean myAddonsInitialized = false;
private static final String NO_REFORMAT = ApplicationBundle.message("combobox.paste.reformat.none");
@@ -160,6 +161,8 @@ public class EditorSmartKeysConfigurable extends CompositeConfigurable<UnnamedCo
myCbSurroundSelectionOnTyping.setSelected(codeInsightSettings.SURROUND_SELECTION_ON_QUOTE_TYPED);
myCbIndentingBackspace.setSelected(codeInsightSettings.INDENTING_BACKSPACE);
super.reset();
}
@@ -179,6 +182,7 @@ public class EditorSmartKeysConfigurable extends CompositeConfigurable<UnnamedCo
codeInsightSettings.SURROUND_SELECTION_ON_QUOTE_TYPED = myCbSurroundSelectionOnTyping.isSelected();
editorSettings.setCamelWords(myCbCamelWords.isSelected());
codeInsightSettings.REFORMAT_ON_PASTE = getReformatPastedBlockValue();
codeInsightSettings.INDENTING_BACKSPACE = myCbIndentingBackspace.isSelected();
super.apply();
}
@@ -204,6 +208,8 @@ public class EditorSmartKeysConfigurable extends CompositeConfigurable<UnnamedCo
isModified |= isModified(myCbSurroundSelectionOnTyping, codeInsightSettings.SURROUND_SELECTION_ON_QUOTE_TYPED);
isModified |= isModified(myCbIndentingBackspace, codeInsightSettings.INDENTING_BACKSPACE);
return isModified;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2012 JetBrains s.r.o.
* 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.
@@ -100,6 +100,8 @@ public class CodeInsightSettings implements PersistentStateComponent<Element>, C
public boolean SHOW_FULL_SIGNATURES_IN_PARAMETER_INFO = false;
public boolean INDENTING_BACKSPACE = false;
public boolean SMART_INDENT_ON_ENTER = true;
public boolean INSERT_BRACE_ON_ENTER = true;
public boolean INSERT_SCRIPTLET_END_ON_ENTER = true;
@@ -0,0 +1,134 @@
/*
* 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.openapi.diagnostic.Logger;
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.vfs.VirtualFile;
import com.intellij.psi.PsiFile;
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);
@Override
public void beforeCharDeleted(char c, PsiFile file, Editor editor) {
}
@Override
public boolean charDeleted(char c, PsiFile file, Editor editor) {
if (!CodeInsightSettings.getInstance().INDENTING_BACKSPACE || " \n\t".indexOf(c) == -1) {
return false;
}
LanguageCodeStyleSettingsProvider codeStyleSettingsProvider = LanguageCodeStyleSettingsProvider.forLanguage(file.getLanguage());
if (codeStyleSettingsProvider != null && codeStyleSettingsProvider.isIndentBasedLanguageSemantics()) {
return false;
}
Document document = editor.getDocument();
int offset = CharArrayUtil.shiftForward(document.getCharsSequence(), editor.getCaretModel().getOffset(), " \t");
int beforeWhitespaceOffset = CharArrayUtil.shiftBackward(document.getCharsSequence(), offset - 1, " \t") + 1;
LogicalPosition logicalPosition = editor.offsetToLogicalPosition(offset);
int lineStartOffset = document.getLineStartOffset(logicalPosition.line);
if (lineStartOffset < beforeWhitespaceOffset) {
if (c == '\n' && beforeWhitespaceOffset < offset) {
document.deleteString(beforeWhitespaceOffset, offset);
return true;
}
else {
return false;
}
}
CodeStyleFacade codeStyleFacade = CodeStyleFacade.getInstance(editor.getProject());
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) {
return false;
}
if (c == '\n' || logicalPosition.column > targetColumn) {
smartReplace(document, lineStartOffset, offset, indent);
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) {
document.deleteString(targetOffset, offset);
}
else {
smartReplace(document, prevLineStartOffset, offset, indent);
}
return true;
}
private static void smartReplace(Document document, int from, int to, String indent) {
CharSequence text = document.getCharsSequence();
int prefixLength = 0;
while ((prefixLength + from) < to && prefixLength < indent.length() && text.charAt(prefixLength + from) == indent.charAt(prefixLength)) {
prefixLength++;
}
document.replaceString(from + prefixLength, to, indent.substring(prefixLength));
}
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;
}
}
@@ -0,0 +1,3 @@
class Foo {
<caret>int field;
}
@@ -0,0 +1,4 @@
class Foo {
<caret>int field;
}
@@ -0,0 +1,4 @@
class Foo {
<caret>
int field;
}
@@ -0,0 +1,4 @@
class Foo {
<caret>
int field;
}
@@ -0,0 +1,3 @@
class Foo {<caret>
int field;
}
@@ -0,0 +1,4 @@
class Foo {
<caret>
int field;
}
@@ -0,0 +1,3 @@
class Foo {<caret>
int field;
}
@@ -0,0 +1,4 @@
class Foo {
<caret>
int field;
}
@@ -0,0 +1,3 @@
class Foo {
<caret>int field;
}
@@ -0,0 +1,3 @@
class Foo {
<caret> int field;
}
@@ -0,0 +1,3 @@
class Foo {
<caret> int field;
}
@@ -0,0 +1,3 @@
class Foo {
<caret> int field;
}
@@ -0,0 +1,3 @@
class Foo {
<caret> int field;
}
@@ -0,0 +1,4 @@
class Foo {
<caret> int field;
}
@@ -0,0 +1,3 @@
class Foo {
<caret> int field;
}
@@ -0,0 +1,4 @@
class Foo {
<caret> int field;
}
@@ -0,0 +1,4 @@
class Foo {
int fi<caret>ld;
}
@@ -0,0 +1,4 @@
class Foo {
int fie<caret>ld;
}
@@ -0,0 +1,4 @@
class Foo {
int <caret>field;
}
@@ -0,0 +1,4 @@
class Foo {
int <caret>field;
}
@@ -0,0 +1,3 @@
class Foo {
int field;
} <caret>
@@ -0,0 +1,3 @@
class Foo {
int field;
} <caret>
@@ -0,0 +1,4 @@
class Foo {
<caret>int field;
}
@@ -0,0 +1,5 @@
class Foo {
<caret>int field;
}
@@ -0,0 +1,2 @@
line1
<caret>line2
@@ -0,0 +1,2 @@
line1
<caret>line2
@@ -0,0 +1,4 @@
class Foo {
<caret>
}
@@ -0,0 +1,4 @@
class Foo {
<selection>int field;<caret></selection>
}
@@ -0,0 +1,3 @@
class Foo {<caret>
int field;
}
@@ -0,0 +1,4 @@
class Foo {
<caret>
int field;
}
@@ -0,0 +1,4 @@
<caret>class Foo {
int field;
}
@@ -0,0 +1,4 @@
<caret>class Foo {
int field;
}
@@ -0,0 +1,65 @@
/*
* 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.application.Result;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.testFramework.FileBasedTestCaseHelper;
import com.intellij.testFramework.LightPlatformCodeInsightTestCase;
import com.intellij.testFramework.TestDataPath;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@SuppressWarnings("JUnit4AnnotatedMethodInJUnit3TestCase")
@RunWith(com.intellij.testFramework.Parameterized.class)
@TestDataPath("/testData/../../../platform/lang-impl/testData/editor/indentingBackspace/")
public class IndentingBackspaceHandlerTest extends LightPlatformCodeInsightTestCase implements FileBasedTestCaseHelper {
private boolean mySavedConfigurationState;
@Before
public void enableIndentingBackspace() {
mySavedConfigurationState = CodeInsightSettings.getInstance().INDENTING_BACKSPACE;
CodeInsightSettings.getInstance().INDENTING_BACKSPACE = true;
}
@After
public void restoreSettings() {
CodeInsightSettings.getInstance().INDENTING_BACKSPACE = mySavedConfigurationState;
}
@Test
public void testAction() {
new WriteCommandAction<Void>(null) {
@Override
protected void run(@NotNull Result<Void> result) throws Throwable {
configureByFile(myFileSuffix);
backspace();
checkResultByFile(myFileSuffix.replace(".", "-after."));
}
}.execute();
}
@Nullable
@Override
public String getFileSuffix(String fileName) {
return fileName.contains("-after.") ? null : fileName;
}
}
@@ -407,6 +407,7 @@ checkbox.smart.indent=Smart indent
label.smart.enter=Smart Enter:
checkbox.smart.end.on.blank.line=End (on blank line)
checkbox.smart.home=Home
checkbox.indenting.backspace=Backspace smart indent
group.error.highlighting=Error highlighting
editbox.autoreparse.delay.ms=Autoreparse delay (ms):
editbox.error.stripe.mark.min.height.pixels=Error stripe mark min height (pixels):
@@ -594,6 +594,8 @@
id="EnterBetweenBracesHandler"/>
<enterHandlerDelegate implementation="com.intellij.codeInsight.editorActions.enter.EnterAfterJavadocTagHandler"/>
<backspaceHandlerDelegate implementation="com.intellij.codeInsight.editorActions.IndentingBackspaceHandler" />
<codeInsight.linkHandler prefix="#inspection/" handlerClass="com.intellij.codeInsight.hint.InspectionDescriptionLinkHandler"/>
<codeInsight.linkHandler prefix="#navigation/" handlerClass="com.intellij.codeInsight.hint.NavigationLinkHandler"/>
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2013 JetBrains s.r.o.
* 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.
@@ -123,6 +123,11 @@ public class PyLanguageCodeStyleSettingsProvider extends LanguageCodeStyleSettin
return PlatformUtils.isPyCharm() ? DisplayPriority.KEY_LANGUAGE_SETTINGS : DisplayPriority.LANGUAGE_SETTINGS;
}
@Override
public boolean isIndentBasedLanguageSemantics() {
return true;
}
@SuppressWarnings("FieldCanBeLocal")
private static String SPACING_SETTINGS_PREVIEW = "def settings_preview(argument, key=value):\n" +
" dict = {1:'a', 2:'b', 3:'c'}\n" +