API to execute typed handler postprocessing in a separate command

This allows to undo the formatting separately from the typing which
has triggered the formatting (IDEA-126243)
This commit is contained in:
Dmitry Jemerov
2018-04-18 19:06:08 +02:00
parent ca12e6fc73
commit da2d0959ea
11 changed files with 137 additions and 129 deletions
@@ -0,0 +1,7 @@
class Foo {
void m() {
if (true) {
System.out.println();
}<caret>
}
}
@@ -0,0 +1,7 @@
class Foo {
void m() {
if (true) {
System.out.println();
}<caret>
}
}
@@ -0,0 +1,7 @@
class Foo {
void m() {
if (true) {
System.out.println();
<caret>
}
}
@@ -1,21 +1,10 @@
/*
* Copyright 2000-2017 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.
*/
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.java.codeInsight;
import com.intellij.openapi.command.undo.UndoManager;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.fileEditor.TextEditor;
import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.testFramework.PlatformTestUtil;
import com.intellij.testFramework.fixtures.LightPlatformCodeInsightFixtureTestCase;
@@ -29,6 +18,11 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class JavaTypingTest extends LightPlatformCodeInsightFixtureTestCase {
public void testIndentRBrace() {
doTest('}');
doTestUndo();
}
public void testMulticaretIndentLBrace() {
doTest('{');
}
@@ -144,6 +138,12 @@ public class JavaTypingTest extends LightPlatformCodeInsightFixtureTestCase {
myFixture.checkResultByFile(getTestName(true) + "_after.java");
}
private void doTestUndo() {
TextEditor fileEditor = TextEditorProvider.getInstance().getTextEditor(myFixture.getEditor());
UndoManager.getInstance(getProject()).undo(fileEditor);
myFixture.checkResultByFile(getTestName(true) + "_afterUndo.java");
}
private void doMultiTypeTest(char c) {
myFixture.configureByFile(getTestName(true) + "_before.java");
List<Integer> whereToType = findWhereToType(myFixture.getEditor().getDocument().getImmutableCharSequence());
@@ -1,18 +1,4 @@
/*
* 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.
*/
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.openapi.command;
import com.intellij.openapi.project.Project;
@@ -28,7 +14,7 @@ public abstract class CommandProcessorEx extends CommandProcessor {
public abstract void leaveModal();
@Nullable
public abstract Object startCommand(@NotNull Project project, @Nls String name, Object groupId, @NotNull UndoConfirmationPolicy undoConfirmationPolicy);
public abstract Object startCommand(@Nullable Project project, @Nls String name, @Nullable Object groupId, @NotNull UndoConfirmationPolicy undoConfirmationPolicy);
public abstract void finishCommand(Project project, final Object command, Throwable throwable);
public abstract void finishCommand(@Nullable Project project, @NotNull final Object command, @Nullable Throwable throwable);
}
@@ -147,12 +147,12 @@ public class CoreCommandProcessor extends CommandProcessorEx {
@Override
@Nullable
public Object startCommand(@NotNull final Project project,
public Object startCommand(@Nullable final Project project,
@Nls final String name,
final Object groupId,
@Nullable final Object groupId,
@NotNull final UndoConfirmationPolicy undoConfirmationPolicy) {
ApplicationManager.getApplication().assertIsDispatchThread();
if (project.isDisposed()) return null;
if (project != null && project.isDisposed()) return null;
if (CommandLog.LOG.isDebugEnabled()) {
CommandLog.LOG.debug("startCommand: name = " + name + ", groupId = " + groupId);
@@ -169,7 +169,7 @@ public class CoreCommandProcessor extends CommandProcessorEx {
}
@Override
public void finishCommand(final Project project, final Object command, final Throwable throwable) {
public void finishCommand(@Nullable final Project project, @NotNull final Object command, @Nullable Throwable throwable) {
ApplicationManager.getApplication().assertIsDispatchThread();
CommandLog.LOG.assertTrue(myCurrentCommand != null, "no current command in progress");
fireCommandFinished();
@@ -19,10 +19,13 @@ import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.*;
import com.intellij.openapi.editor.actionSystem.ActionPlan;
import com.intellij.openapi.editor.actionSystem.EditorActionManager;
import com.intellij.openapi.editor.actionSystem.TypedActionHandler;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.highlighter.EditorHighlighter;
import com.intellij.openapi.editor.highlighter.HighlighterIterator;
import com.intellij.openapi.editor.impl.DefaultRawTypedHandler;
import com.intellij.openapi.editor.impl.EditorActionManagerImpl;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.fileTypes.FileTypes;
@@ -626,6 +629,9 @@ public class TypedHandler extends TypedActionHandlerBase {
);
}
if (element.getNode() != null && isBrace) {
DefaultRawTypedHandler handler = ((EditorActionManagerImpl) EditorActionManager.getInstance()).getDefaultRawTypedHandler();
handler.beginUndoablePostProcessing(project);
final int finalLBraceOffset = lBraceOffset;
ApplicationManager.getApplication().runWriteAction(() -> {
try{
@@ -1,26 +1,9 @@
/*
* 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.
*/
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.openapi.editor.actionSystem;
import com.intellij.codeInsight.hint.HintManager;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.command.UndoConfirmationPolicy;
import com.intellij.openapi.editor.*;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.project.Project;
@@ -34,14 +17,12 @@ import org.jetbrains.annotations.Nullable;
* @see EditorActionManager#getTypedAction()
*/
public class TypedAction {
@NotNull
private TypedActionHandler myRawHandler;
private TypedActionHandler myHandler;
private boolean myHandlersLoaded;
public TypedAction() {
myHandler = new Handler();
myRawHandler = new DefaultRawHandler();
}
private void ensureHandlersLoaded() {
@@ -123,7 +104,6 @@ public class TypedAction {
* @see #getHandler()
* @see #setupHandler(TypedActionHandler)
*/
@NotNull
public TypedActionHandler setupRawHandler(@NotNull TypedActionHandler handler) {
TypedActionHandler tmp = myRawHandler;
myRawHandler = handler;
@@ -141,45 +121,4 @@ public class TypedAction {
Project project = CommonDataKeys.PROJECT.getData(dataContext);
FreezeLogger.getInstance().runUnderPerformanceMonitor(project, () -> myRawHandler.execute(editor, charTyped, dataContext));
}
private class DefaultRawHandler implements TypedActionHandlerEx {
@Override
public void beforeExecute(@NotNull Editor editor, char c, @NotNull DataContext context, @NotNull ActionPlan plan) {
if (editor.isViewer() || !editor.getDocument().isWritable()) return;
TypedActionHandler handler = getHandler();
if (handler instanceof TypedActionHandlerEx) {
((TypedActionHandlerEx)handler).beforeExecute(editor, c, context, plan);
}
}
@Override
public void execute(@NotNull final Editor editor, final char charTyped, @NotNull final DataContext dataContext) {
CommandProcessor.getInstance().executeCommand(
CommonDataKeys.PROJECT.getData(dataContext), () -> {
if (!EditorModificationUtil.requestWriting(editor)) {
HintManager.getInstance().showInformationHint(editor, "File is not writable");
return;
}
ApplicationManager.getApplication().runWriteAction(new DocumentRunnable(editor.getDocument(), editor.getProject()) {
@Override
public void run() {
Document doc = editor.getDocument();
doc.startGuardedBlockChecking();
try {
getHandler().execute(editor, charTyped, dataContext);
}
catch (ReadOnlyFragmentModificationException e) {
EditorActionManager.getInstance().getReadonlyFragmentModificationHandler(doc).handle(e);
}
finally {
doc.stopGuardedBlockChecking();
}
}
});
},
"", editor.getDocument(), UndoConfirmationPolicy.DEFAULT, editor.getDocument());
}
}
}
@@ -1,18 +1,4 @@
/*
* 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.
*/
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.openapi.command.impl;
import com.intellij.openapi.command.undo.UndoManager;
@@ -23,10 +9,11 @@ import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.ExceptionUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
class CommandProcessorImpl extends CoreCommandProcessor {
@Override
public void finishCommand(final Project project, final Object command, final Throwable throwable) {
public void finishCommand(@Nullable Project project, @NotNull final Object command, @Nullable final Throwable throwable) {
if (myCurrentCommand != command) return;
final boolean failed;
try {
@@ -0,0 +1,76 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.openapi.editor.impl;
import com.intellij.codeInsight.hint.HintManager;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.command.CommandProcessorEx;
import com.intellij.openapi.command.UndoConfirmationPolicy;
import com.intellij.openapi.editor.*;
import com.intellij.openapi.editor.actionSystem.*;
import com.intellij.openapi.project.Project;
import org.jetbrains.annotations.NotNull;
public class DefaultRawTypedHandler implements TypedActionHandlerEx {
private final TypedAction myAction;
private Object myCurrentCommandToken;
public DefaultRawTypedHandler(TypedAction action) {
myAction = action;
}
@Override
public void beforeExecute(@NotNull Editor editor, char c, @NotNull DataContext context, @NotNull ActionPlan plan) {
if (editor.isViewer() || !editor.getDocument().isWritable()) return;
TypedActionHandler handler = myAction.getHandler();
if (handler instanceof TypedActionHandlerEx) {
((TypedActionHandlerEx)handler).beforeExecute(editor, c, context, plan);
}
}
@Override
public void execute(@NotNull final Editor editor, final char charTyped, @NotNull final DataContext dataContext) {
CommandProcessorEx commandProcessorEx = (CommandProcessorEx)CommandProcessor.getInstance();
Project project = CommonDataKeys.PROJECT.getData(dataContext);
myCurrentCommandToken = commandProcessorEx.startCommand(project, "", editor.getDocument(), UndoConfirmationPolicy.DEFAULT);
try {
if (!EditorModificationUtil.requestWriting(editor)) {
HintManager.getInstance().showInformationHint(editor, "File is not writable");
return;
}
ApplicationManager.getApplication().runWriteAction(new DocumentRunnable(editor.getDocument(), editor.getProject()) {
@Override
public void run() {
Document doc = editor.getDocument();
doc.startGuardedBlockChecking();
try {
myAction.getHandler().execute(editor, charTyped, dataContext);
}
catch (ReadOnlyFragmentModificationException e) {
EditorActionManager.getInstance().getReadonlyFragmentModificationHandler(doc).handle(e);
}
finally {
doc.stopGuardedBlockChecking();
}
}
});
}
finally {
commandProcessorEx.finishCommand(project, myCurrentCommandToken, null);
myCurrentCommandToken = null;
}
}
public void beginUndoablePostProcessing(Project project) {
if (myCurrentCommandToken == null) {
throw new IllegalStateException("Not in a typed action at this time");
}
CommandProcessorEx commandProcessorEx = (CommandProcessorEx)CommandProcessor.getInstance();
commandProcessorEx.finishCommand(project, myCurrentCommandToken, null);
myCurrentCommandToken = commandProcessorEx.startCommand(project, "", null, UndoConfirmationPolicy.DEFAULT);
}
}
@@ -1,18 +1,4 @@
/*
* Copyright 2000-2009 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.
*/
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.openapi.editor.impl;
import com.intellij.openapi.actionSystem.ActionManager;
@@ -26,11 +12,14 @@ import org.jetbrains.annotations.NotNull;
public class EditorActionManagerImpl extends EditorActionManager {
private final TypedAction myTypedAction = new TypedAction();
private final DefaultRawTypedHandler myDefaultRawTypedHandler;
private ReadonlyFragmentModificationHandler myReadonlyFragmentsHandler = new DefaultReadOnlyFragmentModificationHandler();
private final ActionManager myActionManager;
public EditorActionManagerImpl(ActionManager actionManager) {
myActionManager = actionManager;
myDefaultRawTypedHandler = new DefaultRawTypedHandler(myTypedAction);
myTypedAction.setupRawHandler(myDefaultRawTypedHandler);
}
@Override
@@ -86,5 +75,9 @@ public class EditorActionManagerImpl extends EditorActionManager {
EditorBundle.message("guarded.block.modification.attempt.error.title"));
}
}
public DefaultRawTypedHandler getDefaultRawTypedHandler() {
return myDefaultRawTypedHandler;
}
}