Fine-grained write actions in "Extract Method" refactoring for Python
authorMikhail Golubev <mikhail.golubev@jetbrains.com>
Wed, 1 Jun 2016 14:27:02 +0000 (17:27 +0300)
committerMikhail Golubev <mikhail.golubev@jetbrains.com>
Thu, 2 Jun 2016 09:38:24 +0000 (12:38 +0300)
This way it's safer to launch time-consuming dependent tasks
(e.g. searching for code duplicates) synchonously since under
WriteAction they might cause deadlock.

python/src/com/jetbrains/python/refactoring/extractmethod/PyExtractMethodUtil.java

index 0adf0708bac3675e3fac9c939ff6fd641fcfe050..ab38386321bf94bd4cc4cca2cfa664fe5581c202 100644 (file)
@@ -20,6 +20,7 @@ import com.intellij.codeInsight.codeFragment.CodeFragment;
 import com.intellij.lang.LanguageNamesValidation;
 import com.intellij.lang.refactoring.NamesValidator;
 import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.WriteAction;
 import com.intellij.openapi.command.CommandProcessor;
 import com.intellij.openapi.editor.Editor;
 import com.intellij.openapi.project.Project;
@@ -107,7 +108,7 @@ public class PyExtractMethodUtil {
 
     final SimpleDuplicatesFinder finder = new SimpleDuplicatesFinder(statement1, statement2, fragment.getOutputVariables(), variableData);
 
-    CommandProcessor.getInstance().executeCommand(project, () -> ApplicationManager.getApplication().runWriteAction(() -> {
+    CommandProcessor.getInstance().executeCommand(project, () ->  {
       final RefactoringEventData beforeData = new RefactoringEventData();
       beforeData.addElements(new PsiElement[]{statement1, statement2});
       project.getMessageBus().syncPublisher(RefactoringEventListener.REFACTORING_EVENT_TOPIC)
@@ -127,7 +128,7 @@ public class PyExtractMethodUtil {
       if (hasOutputVariables) {
         // Generate return modified variables statements
         final String outputVariables = StringUtil.join(fragment.getOutputVariables(), ", ");
-        String newMethodText = builder + "return " + outputVariables;
+        final String newMethodText = builder + "return " + outputVariables;
         builder.append(outputVariables);
 
         final PyFunction function1 = generator.createFromText(languageLevel, PyFunction.class, newMethodText);
@@ -136,15 +137,17 @@ public class PyExtractMethodUtil {
       }
 
       // Generate method
-      PyFunction generatedMethod = generateMethodFromElements(project, methodName, variableData, newMethodElements, flags, isAsync);
-      generatedMethod = insertGeneratedMethod(statement1, generatedMethod);
+      final PyFunction generatedMethod = generateMethodFromElements(project, methodName, variableData, newMethodElements, flags, isAsync);
+      final PyFunction insertedMethod = WriteAction.compute(() -> insertGeneratedMethod(statement1, generatedMethod));
 
       // Process parameters
       final PsiElement firstElement = elementsRange.get(0);
       final boolean isMethod = PyPsiUtils.isMethodContext(firstElement);
-      processParameters(project, generatedMethod, variableData, isMethod, isClassMethod, isStaticMethod);
-      processGlobalWrites(generatedMethod, fragment);
-      processNonlocalWrites(generatedMethod, fragment);
+      WriteAction.run(() -> {
+        processParameters(project, insertedMethod, variableData, isMethod, isClassMethod, isStaticMethod);
+        processGlobalWrites(insertedMethod, fragment);
+        processNonlocalWrites(insertedMethod, fragment);
+      });
 
       // Generate call element
       if (hasOutputVariables) {
@@ -165,29 +168,29 @@ public class PyExtractMethodUtil {
       builder.append(methodName).append("(");
       builder.append(createCallArgsString(variableData)).append(")");
       final PyFunction function1 = generator.createFromText(languageLevel, PyFunction.class, builder.toString());
-      PsiElement callElement = function1.getStatementList().getStatements()[0];
+      final PsiElement callElement = function1.getStatementList().getStatements()[0];
 
       // Both statements are used in finder, so should be valid at this moment
       PyPsiUtils.assertValid(statement1);
       PyPsiUtils.assertValid(statement2);
-      final List<SimpleMatch> duplicates = collectDuplicates(finder, statement1, generatedMethod);
+      final List<SimpleMatch> duplicates = collectDuplicates(finder, statement1, insertedMethod);
       
       // replace statements with call
-      callElement = replaceElements(elementsRange, callElement);
-      callElement = CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(callElement);
-      
-      if (callElement != null) {
-        processDuplicates(duplicates, callElement, editor);
+      PsiElement insertedCallElement = WriteAction.compute(() -> replaceElements(elementsRange, callElement));
+      insertedCallElement = CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(insertedCallElement);
+
+      if (insertedCallElement != null) {
+        processDuplicates(duplicates, insertedCallElement, editor);
       }
 
       // Set editor
-      setSelectionAndCaret(editor, callElement);
+      setSelectionAndCaret(editor, insertedCallElement);
 
       final RefactoringEventData afterData = new RefactoringEventData();
-      afterData.addElement(generatedMethod);
+      afterData.addElement(insertedMethod);
       project.getMessageBus().syncPublisher(RefactoringEventListener.REFACTORING_EVENT_TOPIC)
         .refactoringDone(getRefactoringId(), afterData);
-    }), PyBundle.message("refactoring.extract.method"), null);
+    }, PyBundle.message("refactoring.extract.method"), null);
   }
 
   @NotNull
@@ -307,15 +310,15 @@ public class PyExtractMethodUtil {
 
     final SimpleDuplicatesFinder finder = new SimpleDuplicatesFinder(expression, expression, fragment.getOutputVariables(), variableData);
     if (fragment.getOutputVariables().isEmpty()) {
-      CommandProcessor.getInstance().executeCommand(project, () -> ApplicationManager.getApplication().runWriteAction(() -> {
+      CommandProcessor.getInstance().executeCommand(project, () -> {
         // Generate method
         final boolean isAsync = fragment.isAsync();
-        PyFunction generatedMethod = generateMethodFromExpression(project, methodName, variableData, expression, flags, isAsync);
-        generatedMethod = insertGeneratedMethod(expression, generatedMethod);
+        final PyFunction generatedMethod = generateMethodFromExpression(project, methodName, variableData, expression, flags, isAsync);
+        final PyFunction insertedMethod = WriteAction.compute(() -> insertGeneratedMethod(expression, generatedMethod));
 
         // Process parameters
         final boolean isMethod = PyPsiUtils.isMethodContext(expression);
-        processParameters(project, generatedMethod, variableData, isMethod, isClassMethod, isStaticMethod);
+        WriteAction.run(() -> processParameters(project, insertedMethod, variableData, isMethod, isClassMethod, isStaticMethod));
 
         // Generating call element
         final StringBuilder builder = new StringBuilder();
@@ -341,26 +344,31 @@ public class PyExtractMethodUtil {
         final PyFunction function1 = generator.createFromText(LanguageLevel.forElement(expression), PyFunction.class,
                                                               builder.toString());
         final PyElement generated = function1.getStatementList().getStatements()[0];
-        PsiElement callElement = null;
+        final PsiElement callElement;
         if (generated instanceof PyReturnStatement) {
           callElement = ((PyReturnStatement)generated).getExpression();
         }
         else if (generated instanceof PyExpressionStatement) {
           callElement = ((PyExpressionStatement)generated).getExpression();
         }
+        else {
+          callElement = null;
+        }
 
         PyPsiUtils.assertValid(expression);
-        final List<SimpleMatch> duplicates = collectDuplicates(finder, expression, generatedMethod);
+        final List<SimpleMatch> duplicates = collectDuplicates(finder, expression, insertedMethod);
+        
         // replace statements with call
+        PsiElement insertedCallElement = null;
         if (callElement != null) {
-          callElement = PyReplaceExpressionUtil.replaceExpression(expression, callElement);
-        }
-        if (callElement != null) {
-          processDuplicates(duplicates, callElement, editor);
+          insertedCallElement = WriteAction.compute(() -> PyReplaceExpressionUtil.replaceExpression(expression, callElement));
+          if (insertedCallElement != null) {
+            processDuplicates(duplicates, insertedCallElement, editor);
+          }
         }
+        setSelectionAndCaret(editor, insertedCallElement);
         // Set editor
-        setSelectionAndCaret(editor, callElement);
-      }), PyBundle.message("refactoring.extract.method"), null);
+      }, PyBundle.message("refactoring.extract.method"), null);
     }
   }