Running Multiple Reformat Gradle Tasks Gives Error

Answered

I have a plugin where we are trying to change it to run its reformat action on save on multiple files at once. 

 

We do this now by looping through all open files being saved, e.g. 

for (Document document : documents) {
var file = PsiDocumentManager.getInstance(project).getPsiFile(document);

if (file == null) {
continue;
}

new ReformatCodeProcessor(file).run();
}

The actual gradle task execution is as follows:

// Constructs execution settings, setting the gradle task and its options
ExternalSystemTaskExecutionSettings settings = constructTaskExecutionSettings(fileToProcess);

// Execute gradle task
ExternalSystemUtil.runTask(settings, DefaultRunExecutor.EXECUTOR_ID, myProject, GradleConstants.SYSTEM_ID, null,
ProgressExecutionMode.IN_BACKGROUND_ASYNC, false);

However when testing this, we get the error:

Cannot use connection to Gradle distribution xxxx as it has been stopped.

See a reproduction of the issue here

Any idea how to fix this?

0
11 comments

How is the reformatting related to executing a Gradle task? When and where is that Gradle task triggered from?

0

Hi Yahn, sorry it wasn't more clear. The 'reformatting' is done through a spotless gradle task. 

So the flow is the user kicks off an Action:

ReformatCodeAction extends AnAction

which then calls a processor:

public class ReformatCodeProcessor extends AbstractLayoutCodeProcessor

This then calls the actual gradle task as shown above. Does that help clear things up?

0

There's dedicated hook to invoke external formatter, did you consider it? https://plugins.jetbrains.com/docs/intellij/code-formatting.html#external-code-formatter Not sure why you want to call it via Gradle.

0

Yann, I think it's because a lot of people already use Spotless Gradle , which includes features listed in that README such as:

Spotless supports all of Gradle's built-in performance features (incremental build, remote and local buildcache, lazy configuration, etc), and also automatically fixes idempotence issues, infers line-endings from git, is cautious about misconfigured encoding bugs, and can use git to ratchet formatting without "format-everything" commits.

It also allows for a lot of settings as shown here

Even if this isn't necessarily the correct way to do it, using the external code formatter sounds like a new project outside the scope of spotless-intellij-gradle since we would have to implement these steps and the result would be more of a `spotless-intellij` project than a `spotless-intellij-gradle` project.

Could you help with how to fix this current problem of not being able to run multiple gradle tasks at once to unblock this format on save feature for this specific plugin which uses Spotless Gradle for the formatting? Or is that not possible?

0

Ryan Gurney what is the reason to call the formatter on per-file basis? Is not one invocation enough?
From what I see in the docs, the Gradle-Spotless plugin does not have file specific tasks, it always works per-project.
Also, Gradle does not officially support running multiple builds of same project in parallel.

As Yann Cebron already mentioned, the External Code Formatter API is a great place to call external tool to format the code. You can use same API   ExternalSystemUtil.runTask   there to invoke spotlessApply task

0

Nikita Skvortsov that's a very good point, and thanks for the response! Spotless actually does have file-specific functionality in the Gradle plugin using the ide hook, however right now it only supports one file. There is a request for that to change but I doubt it will be done in the near future. Running the gradle command without that flag would result in ALL files in the project being processed by the plugin, which would take a very long time and isn't really in the spirit of "format on save". 

Perhaps all I can do right now is wait for the ide hook to support multiple files. 

 

re: External Code Formatter -- what benefits does that give for running the gradle task as opposed to my current implementation running the task via an Action? 

0

External Code Formatter benefits are not about running the gradle task (this code will stay the same), but about integrating into IDEA's ecosystem. Your formatter will be properly called on "Reformat Code..." action in IDEA, will respect Actions on Save configuration, will be able to get text ranges to format etc.

0

Nikita Skvortsov 

Yann Cebron

I'm having some difficulty understanding exactly how this works. A couple of follow-up questions:

  1. The formatter seems to only be invoked on "Reformat file" not "Reformat code". Why is that?
  2. It seems that even though I'm using the exact same code in the FutureTask the formatter should execute it doesn't kick off the gradle build it should to format the code. It looks like this is the line to blame:
    EditorScrollingPositionKeeper.perform(document, true, () -> SlowOperations.allowSlowOperations(() -> {
    and nothing is run in that passed in lambda. Why does this not work here while it did in using the Action API instead of this External Formatter API?
  3. The code is sort of working, however it only successfully executes the task 1/20th of the time it's invoked with "reformat file". It seems maybe theres a race condition or something going on. Every subsequent call to reformat after the first one goes to the "cancel()" handler for about 20 seconds then it works once more, then repeats the process. It seems to think the process is still running even though it's not. Do I need to add some sort of special handling to my call here or to the future task .run() or change the cancel logic to make it synchronous / wait? It reliably will work on the first invocation, then seemingly randomly stops working afterwards. It won't even hit a debug statement in the `run()` method on subsequent invocations
    ExternalSystemUtil.runTask(settings, DefaultRunExecutor.EXECUTOR_ID, project, GradleConstants.SYSTEM_ID, null,
    ProgressExecutionMode.IN_BACKGROUND_ASYNC, false);

I think the problem is the task never ends. On first invocation even though the reformatting is done and the gradle task complete, the progress bar still shows.

How do I signal the background task is complete to stop the formatting task?

0

I figured out #3 -- from reading the code I found it turns out you need to call `formattingRequest.onTextReady`... 

0

For calling an exernal formatter please use AsyncDocumentFormattingServce. There's a number of flags telling when the formatter can be used, see FormattingService.Feauture. Note that due to asynchronous nature, it may not always work. For example, if there is a considerable delay during which the current document changes, nothing will happen because there's a conflict between the external formatter changes and the current document state. There's DocumentMerger extension point which can redefine this default behaviour. For example, you can simply replace the current document content with newText.

0

Hi Rustam Vishniakov, thank you for the response! 

 

I actually am using AsyncDocumentFormattingService, however I _must_ have the formatting task be a gradle task -- this is required by the third party implementing the task. That being said, they offer a flag to pass in stdIn as the input and can give output through stdOut. 

My outstanding questions are here. Any advice there would be appreciated! 

0

Please sign in to leave a comment.