Merge remote-tracking branch 'origin/master'

This commit is contained in:
Roman Shevchenko
2016-03-21 17:03:13 +01:00
8 changed files with 74 additions and 29 deletions
@@ -19,6 +19,7 @@ import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Ref;
import com.intellij.util.ThrowableRunnable;
import org.jetbrains.annotations.NotNull;
/**
@@ -30,7 +31,9 @@ import org.jetbrains.annotations.NotNull;
* and process UI events in other ways: it's guaranteed that no one will be able to sneak in with an unexpected model change using
* {@link javax.swing.SwingUtilities#invokeLater(Runnable)} or analogs.<p/>
*
* Transactions are run on UI thread. They have read access by default. All write actions should be performed inside a transaction.<p/>
* Transactions are run on UI thread. They have read access by default. All write actions that don't happen as the result of direct user input
* should be performed inside a transaction. No write action should be performed from within an {@code invokeLater}-like call
* unless wrapped into a transaction.<p/>
*
* The recommended way to perform a transaction is to invoke {@link #submitTransaction(Runnable)}. It either runs the transaction immediately
* (if on UI thread and there's no other transaction running) or queues it to invoke at some later moment, when it becomes possible.<p/>
@@ -192,4 +195,13 @@ public abstract class TransactionGuard {
* @param errorMessage the message that will be logged if current transaction status differs from the expected one
*/
public abstract void assertInsideTransaction(boolean transactionRequired, @NotNull String errorMessage);
/**
* Executes the given code and marks it as a user activity, to allow write actions to be run without requiring transactions.
* Please note that only direct keyboard/mouse event processing can be wrapped in such calls. If you wish to invoke some actionPerformed,
* please consider using {@code ActionManager.tryToExecute()} instead, or ensure in some other way that the action is enabled
* and can be invoked in the current modality state.
*/
public abstract <T extends Throwable> void performUserActivity(ThrowableRunnable<T> activity) throws T;
}
@@ -18,7 +18,9 @@ package com.intellij.openapi.application;
import com.intellij.openapi.diagnostic.Attachment;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.psi.impl.DebugUtil;
import com.intellij.util.ThrowableRunnable;
import com.intellij.util.concurrency.Semaphore;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
@@ -37,13 +39,14 @@ public class TransactionGuardImpl extends TransactionGuard {
private final Set<TransactionKind> myMergeableKinds = ContainerUtil.newHashSet();
private String myTransactionStartTrace;
private ModalityState myTransactionModality;
private boolean myUserActivity;
@Override
@NotNull
public AccessToken startSynchronousTransaction(@NotNull TransactionKind kind) throws IllegalStateException {
ModalityState modality = ModalityState.current();
if (isInsideTransaction()) {
if (modality.equals(myTransactionModality)) {
if (modality.equals(myTransactionModality) || myUserActivity) {
return AccessToken.EMPTY_ACCESS_TOKEN;
}
@@ -200,4 +203,39 @@ public class TransactionGuardImpl extends TransactionGuard {
throw new RuntimeException(exception[0]);
}
}
@Override
public <T extends Throwable> void performUserActivity(ThrowableRunnable<T> activity) throws T {
ApplicationManager.getApplication().assertIsDispatchThread();
AccessToken token = startActivity(true);
try {
activity.run();
}
finally {
token.finish();
}
}
/**
* An absolutely guru method, only intended to be used from Swing event processing. Please consult Peter if you think you need to invoke this.
*/
@NotNull
public AccessToken startActivity(boolean userActivity) {
if (myUserActivity == userActivity) {
return AccessToken.EMPTY_ACCESS_TOKEN;
}
final boolean prev = myUserActivity;
myUserActivity = userActivity;
return new AccessToken() {
@Override
public void finish() {
myUserActivity = prev;
}
};
}
public boolean isWriteActionAllowed() {
return !Registry.is("ide.require.transaction.for.model.changes", false) || isInsideTransaction() || myUserActivity;
}
}
@@ -29,6 +29,7 @@ import com.intellij.openapi.actionSystem.ex.ActionUtil;
import com.intellij.openapi.actionSystem.ex.QuickList;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.application.TransactionGuard;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.keymap.Keymap;
import com.intellij.openapi.keymap.KeymapManager;
@@ -250,10 +251,9 @@ public class GotoActionAction extends GotoActionBase implements DumbAware {
public static void performAction(Object element, @Nullable final Component component, @Nullable final AnActionEvent e) {
// element could be AnAction (SearchEverywhere)
final AnAction action = element instanceof AnAction ? (AnAction)element : ((GotoActionModel.ActionWrapper)element).getAction();
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
if (component == null) return;
ApplicationManager.getApplication().invokeLater(() -> {
if (component == null) return;
TransactionGuard.getInstance().performUserActivity(() -> {
DataManager instance = DataManager.getInstance();
DataContext context = instance != null ? instance.getDataContext(component) : DataContext.EMPTY_CONTEXT;
InputEvent inputEvent = e == null ? null : e.getInputEvent();
@@ -270,12 +270,12 @@ public class GotoActionAction extends GotoActionBase implements DumbAware {
else {
popup.showInFocusCenter();
}
}
}
else {
ActionUtil.performActionDumbAware(action, event);
}
}
}
});
}, ModalityState.NON_MODAL);
}
@@ -21,9 +21,7 @@ import com.intellij.ide.plugins.PluginManager;
import com.intellij.ide.ui.UISettings;
import com.intellij.idea.IdeaApplication;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.application.*;
import com.intellij.openapi.application.ex.ApplicationEx;
import com.intellij.openapi.application.impl.LaterInvocator;
import com.intellij.openapi.diagnostic.FrequentEventDetector;
@@ -385,6 +383,7 @@ public class IdeEventQueue extends EventQueue {
}
AWTEvent oldEvent = myCurrentEvent;
myCurrentEvent = e;
AccessToken token = ((TransactionGuardImpl)TransactionGuard.getInstance()).startActivity(myIsInInputEvent);
try {
_dispatchEvent(e, false);
@@ -393,6 +392,7 @@ public class IdeEventQueue extends EventQueue {
processException(t);
}
finally {
token.finish();
myIsInInputEvent = wasInputEvent;
myCurrentEvent = oldEvent;
@@ -29,10 +29,7 @@ import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
import com.intellij.openapi.actionSystem.ex.ActionUtil;
import com.intellij.openapi.actionSystem.ex.AnActionListener;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationActivationListener;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.application.*;
import com.intellij.openapi.application.ex.ApplicationManagerEx;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.extensions.PluginId;
@@ -1298,15 +1295,14 @@ public final class ActionManagerImpl extends ActionManagerEx implements Disposab
private void tryToExecuteNow(final AnAction action, final InputEvent inputEvent, final Component contextComponent, final String place, final ActionCallback result) {
final Presentation presentation = action.getTemplatePresentation().clone();
IdeFocusManager.findInstanceByContext(getContextBy(contextComponent)).doWhenFocusSettlesDown(new Runnable() {
@Override
public void run() {
IdeFocusManager.findInstanceByContext(getContextBy(contextComponent)).doWhenFocusSettlesDown(
() -> TransactionGuard.getInstance().performUserActivity(() -> {
final DataContext context = getContextBy(contextComponent);
AnActionEvent event = new AnActionEvent(
inputEvent, context,
place != null ? place : ActionPlaces.UNKNOWN,
presentation, ActionManagerImpl.this,
presentation, this,
inputEvent.getModifiersEx()
);
@@ -1346,7 +1342,7 @@ public final class ActionManagerImpl extends ActionManagerEx implements Disposab
result.setDone();
queueActionPerformedEvent(action, context, event);
}
});
));
}
private class MyTimer extends Timer implements ActionListener {
@@ -1229,9 +1229,9 @@ public class ApplicationImpl extends PlatformComponentManagerImpl implements App
private void startWrite(/*@NotNull*/ Class clazz) {
assertIsDispatchThread(getStatus(), "Write access is allowed from event dispatch thread only");
HeavyProcessLatch.INSTANCE.stopThreadPrioritizing(); // let non-cancellable read actions complete faster, if present
if (Registry.is("ide.require.transaction.for.model.changes", false)) {
TransactionGuard.getInstance().assertInsideTransaction(
true, "Write access is allowed from model transactions only, see TransactionGuard documentation for details");
if (!((TransactionGuardImpl)TransactionGuard.getInstance()).isWriteActionAllowed()) {
// please assign exceptions here to Peter
LOG.error("Write access is allowed from model transactions only, see TransactionGuard documentation for details");
}
boolean writeActionPending = myWriteActionPending;
if (gatherWriteActionStatistics && myWriteActionsStack.isEmpty() && !writeActionPending) {
@@ -868,17 +868,15 @@ public final class IdeKeyEventDispatcher implements Disposable {
}
private static void invokeAction(@NotNull final AnAction action, final DataContext ctx) {
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
ApplicationManager.getApplication().invokeLater(
() -> TransactionGuard.getInstance().performUserActivity(() -> {
final AnActionEvent event =
new AnActionEvent(null, ctx, ActionPlaces.UNKNOWN, action.getTemplatePresentation().clone(),
ActionManager.getInstance(), 0);
if (ActionUtil.lastUpdateAndCheckDumb(action, event, true)) {
ActionUtil.performActionDumbAware(action, event);
}
}
});
}));
}
@Override
@@ -28,6 +28,7 @@ import com.intellij.openapi.actionSystem.DataProvider;
import com.intellij.openapi.actionSystem.PlatformDataKeys;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.application.TransactionGuard;
import com.intellij.openapi.application.ex.ApplicationManagerEx;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
@@ -1392,7 +1393,7 @@ public class AbstractPopup implements JBPopup {
//noinspection SSBasedInspection
SwingUtilities.invokeLater(() -> {
if (ModalityState.current().equals(modalityState)) {
finalRunnable.run();
TransactionGuard.getInstance().performUserActivity(finalRunnable::run);
}
// Otherwise the UI has changed unexpectedly and the action is likely not applicable.
// And we don't want finalRunnable to perform potentially destructive actions