diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/JavaReformatOnTypingTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/JavaReformatOnTypingTest.java new file mode 100644 index 000000000000..1c0f2258dc5c --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/codeInsight/JavaReformatOnTypingTest.java @@ -0,0 +1,95 @@ +/* + * Copyright 2000-2016 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; + +import com.intellij.codeInsight.editorActions.AutoFormatTypedHandler; +import com.intellij.ide.highlighter.JavaFileType; +import com.intellij.lang.Language; +import com.intellij.lang.java.JavaLanguage; +import com.intellij.openapi.project.Project; +import com.intellij.psi.codeStyle.CodeStyleSettings; +import com.intellij.psi.codeStyle.CodeStyleSettingsManager; +import com.intellij.psi.codeStyle.CommonCodeStyleSettings; +import com.intellij.testFramework.fixtures.LightPlatformCodeInsightFixtureTestCase; + +public class JavaReformatOnTypingTest extends LightPlatformCodeInsightFixtureTestCase { + + @Override + public void setUp() throws Exception { + super.setUp(); + AutoFormatTypedHandler.setEnabledInTests(true); + } + + @Override + public void tearDown() throws Exception { + AutoFormatTypedHandler.setEnabledInTests(false); + //noinspection SuperTearDownInFinally + super.tearDown(); + } + + private static void useSpacesAroundAssignmentOperator(boolean value, Project project, Language language) { + CodeStyleSettings settings = CodeStyleSettingsManager.getInstance(project).getCurrentSettings(); + CommonCodeStyleSettings common = settings.getCommonSettings(language); + common.SPACE_AROUND_ASSIGNMENT_OPERATORS = value; + } + + public void test_AddSpacesAroundAssignmentOperator() throws Exception { + doTest("class T { int }", "=", "class T { int = }"); + } + + public void test_IgnoreSpacePressedAfterAssignmentOperator() { + useSpacesAroundAssignmentOperator(true, myFixture.getProject(), JavaLanguage.INSTANCE); + myFixture.configureByText(JavaFileType.INSTANCE, "class T { int }"); + myFixture.type('='); + myFixture.type(' '); + myFixture.checkResult("class T { int = }"); + } + + public void test_DoNotInsertDoubleSpaceBeforeAssignment() { + doTest("class T { int }", + "=", + "class T { int = }"); + } + + public void test_DoNotInsertDoubleSpaceAnywhere() { + useSpacesAroundAssignmentOperator(true, myFixture.getProject(), JavaLanguage.INSTANCE); + myFixture.configureByText(JavaFileType.INSTANCE, "class T { int }"); + myFixture.type('='); + myFixture.type(' '); + myFixture.checkResult("class T { int = }"); + } + + public void test_DoNotInsertSpaceIfNotAssignment() { + doTest("1 ", "!=", "1 != "); + doTest("c ", ">=", "c >= "); + doTest("c ", "<=", "c <= "); + doTest("c ", "+=", "c += "); + doTest("c ", "-=", "c -= "); + doTest("c ", "*=", "c *= "); + doTest("c ", "/=", "c /= "); + doTest("c ", "&=", "c &= "); + doTest("c ", "%=", "c %= "); + doTest("c ", "^=", "c ^= "); + doTest("c ", "|=", "c |= "); + } + + private void doTest(String before, String typing, String after) { + useSpacesAroundAssignmentOperator(true, myFixture.getProject(), JavaLanguage.INSTANCE); + myFixture.configureByText(JavaFileType.INSTANCE, before); + myFixture.type(typing); + myFixture.checkResult(after); + } +} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/AutoFormatTypedHandler.java b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/AutoFormatTypedHandler.java new file mode 100644 index 000000000000..6697def3dd13 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/AutoFormatTypedHandler.java @@ -0,0 +1,125 @@ +/* + * Copyright 2000-2016 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.template.impl.editorActions.TypedActionHandlerBase; +import com.intellij.lang.Language; +import com.intellij.openapi.actionSystem.CommonDataKeys; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.editor.CaretModel; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.EditorModificationUtil; +import com.intellij.openapi.editor.actionSystem.TypedActionHandler; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.registry.Registry; +import com.intellij.psi.PsiFile; +import com.intellij.psi.codeStyle.CodeStyleSettings; +import com.intellij.psi.codeStyle.CodeStyleSettingsManager; +import com.intellij.psi.codeStyle.CommonCodeStyleSettings; +import com.intellij.psi.util.PsiUtilBase; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.TestOnly; + +public class AutoFormatTypedHandler extends TypedActionHandlerBase { + private static boolean myIsEnabledInTests = false; + + private static char[] NO_SPACE_AFTER = { '+', '-', '*', '/', '%', '&', '^', '|', '<', '>', '!', '=', ' ' }; + + private boolean myIgnoreNextSpace = false; + private Document myLastModifiedDocument = null; + private int myLastOffset = -1; + + public AutoFormatTypedHandler(@Nullable TypedActionHandler originalHandler) { + super(originalHandler); + } + + private static boolean isEnabled() { + return Registry.is("editor.reformat.on.typing") + || myIsEnabledInTests && ApplicationManager.getApplication().isUnitTestMode(); + } + + @TestOnly + public static void setEnabledInTests(boolean value) { + myIsEnabledInTests = value; + } + + @Override + public void execute(@NotNull Editor editor, char charTyped, @NotNull DataContext dataContext) { + if (isEnabled() && charTyped == ' ' && shouldIgnoreSpace(editor)) { + myIgnoreNextSpace = false; + return; + } + + Document document = editor.getDocument(); + boolean addSpaces = isEnabled() && charTyped == '=' && shouldInsertSpaces(editor, dataContext); + + int caretOffset = editor.getCaretModel().getOffset(); + CharSequence text = document.getImmutableCharSequence(); + if (addSpaces && shouldInsertBefore(caretOffset, text)) { + EditorModificationUtil.insertStringAtCaret(editor, " "); + } + + if (myOriginalHandler != null) myOriginalHandler.execute(editor, charTyped, dataContext); + + if (addSpaces) { + EditorModificationUtil.insertStringAtCaret(editor, " "); + myIgnoreNextSpace = true; + myLastModifiedDocument = document; + myLastOffset = editor.getCaretModel().getOffset(); + } + else { + myIgnoreNextSpace = false; + } + } + + private static boolean shouldInsertBefore(int caretOffset, CharSequence text) { + if (caretOffset == 0) return false; + char charBefore = text.charAt(caretOffset - 1); + + for (char c : NO_SPACE_AFTER) { + if (c == charBefore) { + return false; + } + } + + return true; + } + + private static boolean shouldInsertSpaces(Editor editor, DataContext dataContext) { + final Project project = CommonDataKeys.PROJECT.getData(dataContext); + PsiFile file = project == null ? null : PsiUtilBase.getPsiFileInEditor(editor, project); + if (file != null) { + Language language = file.getLanguage(); + CodeStyleSettings settings = CodeStyleSettingsManager.getInstance(project).getCurrentSettings(); + CommonCodeStyleSettings common = settings.getCommonSettings(language); + return common.SPACE_AROUND_ASSIGNMENT_OPERATORS; + } + return false; + } + + private boolean shouldIgnoreSpace(@NotNull Editor editor) { + if (!myIgnoreNextSpace) { + return false; + } + Document document = editor.getDocument(); + CaretModel caretModel = editor.getCaretModel(); + return myLastModifiedDocument == document && myLastOffset == caretModel.getOffset(); + } + +} diff --git a/platform/platform-resources/src/META-INF/LangExtensions.xml b/platform/platform-resources/src/META-INF/LangExtensions.xml index 9ba616080ea8..35a13d58eec3 100644 --- a/platform/platform-resources/src/META-INF/LangExtensions.xml +++ b/platform/platform-resources/src/META-INF/LangExtensions.xml @@ -727,6 +727,7 @@ order="before hide-hints"/> + diff --git a/platform/util/resources/misc/registry.properties b/platform/util/resources/misc/registry.properties index 20827b259ea1..0ebaa02a7a97 100644 --- a/platform/util/resources/misc/registry.properties +++ b/platform/util/resources/misc/registry.properties @@ -149,6 +149,8 @@ editor.navigation.history.stack.size.description=Stack size limit for back/forwa editor.mac.smooth.scrolling=false editor.mac.smooth.scrolling.description=Enable precise (with sub-line resolution) scrolling on Mac with mouse or trackpad +editor.reformat.on.typing=false + ide.showIndexRebuildMessage=false ide.tabbedPane.bufferedPaint=true