IDEA-87318 Backspace should delete whitespace to chosen indent level, or be configurable

This commit is contained in:
Dmitry Batrak
2014-11-17 12:32:27 +03:00
parent 440244dfca
commit 2825ea41cc
9 changed files with 196 additions and 91 deletions
@@ -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();
@@ -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;
}
}
@@ -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);
}
}
@@ -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;
}
}
}
@@ -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"/>
-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;
}
}