Files
openide/java/compiler/impl/src/com/intellij/compiler/progress/CompilerTask.java
T
Bart van Helvertandintellij-monorepo-bot d73104e8fa [tests] Don't steal focus when automatically rerunning tests
Prevents the build toolwindow from stealing the focus when an automatic build is triggered by the automatic test runner when the code is uncompilable. #IDEA-335769 Fixed


(cherry picked from commit d6bd1f9fade4fab3c91b25bbe788f870aaf123bc)

IJ-CR-180263

GitOrigin-RevId: e5ff8f040d27d4a1686289af4a1158a6ee38c633
2025-10-28 14:51:35 +00:00

316 lines
10 KiB
Java

// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.compiler.progress;
import com.intellij.compiler.CompilerManagerImpl;
import com.intellij.compiler.impl.ExitStatus;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.compiler.CompilerManager;
import com.intellij.openapi.compiler.CompilerMessage;
import com.intellij.openapi.compiler.CompilerMessageCategory;
import com.intellij.openapi.compiler.JavaCompilerBundle;
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
import com.intellij.openapi.progress.*;
import com.intellij.openapi.progress.util.AbstractProgressIndicatorExBase;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.NlsContexts;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.AppIconScheme;
import com.intellij.openapi.wm.ex.ProgressIndicatorEx;
import com.intellij.pom.Navigatable;
import com.intellij.problems.WolfTheProblemSolver;
import com.intellij.ui.AppIcon;
import com.intellij.util.ModalityUiUtil;
import com.intellij.util.SystemProperties;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public final class CompilerTask extends Task.Backgroundable {
private static final String APP_ICON_ID = "compiler";
private final @NotNull Object myContentId;
private @NotNull Object mySessionId;
private final boolean myModal;
private final boolean myHeadlessMode;
private final boolean myForceAsyncExecution;
private final boolean myWaitForPreviousSession;
private int myErrorCount = 0;
private int myWarningCount = 0;
private volatile ProgressIndicator myIndicator = new EmptyProgressIndicator();
private Runnable myCompileWork;
private Runnable myRestartWork;
private final boolean myCompilationStartedAutomatically;
private final BuildViewService myBuildViewService;
private long myStartCompilationStamp;
private long myEndCompilationStamp;
private ExitStatus myExitStatus;
public CompilerTask(@NotNull Project project, @NlsContexts.TabTitle String contentName, final boolean headlessMode, boolean forceAsync,
boolean waitForPreviousSession, boolean compilationStartedAutomatically) {
this(project, contentName, headlessMode, forceAsync, waitForPreviousSession, compilationStartedAutomatically, false);
}
public CompilerTask(@NotNull Project project, @NlsContexts.TabTitle String contentName, final boolean headlessMode, boolean forceAsync,
boolean waitForPreviousSession, boolean compilationStartedAutomatically, boolean modal) {
super(project, contentName);
myHeadlessMode = headlessMode;
myForceAsyncExecution = forceAsync;
myWaitForPreviousSession = waitForPreviousSession;
myCompilationStartedAutomatically = compilationStartedAutomatically;
myModal = modal;
myContentId = new IDObject("content_id");
mySessionId = myContentId; // by default sessionID should be unique, just as content ID
if (SystemProperties.getBooleanProperty("ide.jps.use.build.tool.window", true)) {
myBuildViewService = new BuildOutputService(project, contentName, myCompilationStartedAutomatically);
} else {
myBuildViewService = new CompilerMessagesService(project, myContentId, contentName, headlessMode);
}
}
public @NotNull Object getSessionId() {
return mySessionId;
}
public void setSessionId(@NotNull Object sessionId) {
mySessionId = sessionId;
}
public @NotNull Object getContentId() {
return myContentId;
}
public void setStartCompilationStamp(long startCompilationStamp) {
myStartCompilationStamp = startCompilationStamp;
}
public void setEndCompilationStamp(ExitStatus exitStatus, long endCompilationStamp) {
myExitStatus = exitStatus;
myEndCompilationStamp = endCompilationStamp;
}
public void registerCloseAction(final Runnable onClose) {
myBuildViewService.registerCloseAction(onClose);
}
@Override
public boolean shouldStartInBackground() {
return !myModal;
}
@Override
public boolean isConditionalModal() {
return myModal;
}
public ProgressIndicator getIndicator() {
return myIndicator;
}
@Override
public @NotNull NotificationInfo getNotificationInfo() {
return new NotificationInfo(myErrorCount > 0 ? "Compiler (errors)" : "Compiler (success)",
JavaCompilerBundle.message("compilation.finished"),
JavaCompilerBundle.message("0.errors.1.warnings", myErrorCount, myWarningCount), true);
}
@Override
public void run(@NotNull ProgressIndicator indicator) {
myIndicator = indicator;
myIndicator.setIndeterminate(false);
myBuildViewService.onStart(mySessionId, myStartCompilationStamp, myRestartWork, indicator);
Semaphore semaphore = ((CompilerManagerImpl)CompilerManager.getInstance(myProject)).getCompilationSemaphore();
boolean acquired = false;
try {
if (!myCompilationStartedAutomatically || !isHeadless()) {
// Do not acquire the semaphore for the automatically launched headless tasks.
// Because of that Build UI controls will not be disabled and would allow the user to start build explicitly.
// Such builds will be queued and executed after any currently running tasks.
try {
while (!acquired) {
acquired = semaphore.tryAcquire(300, TimeUnit.MILLISECONDS);
if (!acquired && !myWaitForPreviousSession) {
return;
}
if (indicator.isCanceled()) {
// give up obtaining the semaphore,
// let compile work begin in order to stop gracefuly on cancel event
break;
}
}
}
catch (InterruptedException ignored) {
}
}
if (!isHeadless()) {
addIndicatorDelegate();
}
myCompileWork.run();
}
catch (ProcessCanceledException ignored) {
}
finally {
try {
indicator.stop();
myBuildViewService.onEnd(mySessionId, myExitStatus, myEndCompilationStamp);
}
finally {
if (acquired) {
semaphore.release();
}
}
}
}
private void addIndicatorDelegate() {
ProgressIndicator indicator = myIndicator;
if (!(indicator instanceof ProgressIndicatorEx)) {
return;
}
((ProgressIndicatorEx)indicator).addStateDelegate(new AbstractProgressIndicatorExBase() {
@Override
public void cancel() {
super.cancel();
stopAppIconProgress();
}
@Override
public void stop() {
super.stop();
stopAppIconProgress();
}
private void stopAppIconProgress() {
UIUtil.invokeLaterIfNeeded(() -> {
if (myProject != null && myProject.isDisposed()) {
return;
}
final AppIcon appIcon = AppIcon.getInstance();
if (appIcon.hideProgress(myProject, APP_ICON_ID)) {
if (myErrorCount > 0) {
appIcon.setErrorBadge(myProject, String.valueOf(myErrorCount));
appIcon.requestAttention(myProject, true);
}
else if (!myCompilationStartedAutomatically) {
appIcon.setOkBadge(myProject, true);
appIcon.requestAttention(myProject, false);
}
}
});
}
@Override
public void setFraction(final double fraction) {
super.setFraction(fraction);
ModalityUiUtil.invokeLaterIfNeeded(
ModalityState.any(), () -> AppIcon.getInstance().setProgress(myProject, APP_ICON_ID, AppIconScheme.Progress.BUILD, fraction, true)
);
}
@Override
protected void onProgressChange() {
myBuildViewService.onProgressChange(mySessionId, this);
}
});
}
public void cancel() {
if (!myIndicator.isCanceled()) {
myIndicator.cancel();
}
}
public void addMessage(final CompilerMessage message) {
final CompilerMessageCategory messageCategory = message.getCategory();
if (CompilerMessageCategory.WARNING.equals(messageCategory)) {
myWarningCount += 1;
}
else if (CompilerMessageCategory.ERROR.equals(messageCategory)) {
myErrorCount += 1;
informWolf(message);
}
myBuildViewService.addMessage(mySessionId, message);
}
private void informWolf(final CompilerMessage message) {
if (myProject == null) {
return;
}
final Runnable task = () -> {
if (myProject.isDisposed()) {
return;
}
VirtualFile file = message.getVirtualFile();
if (file == null) {
Navigatable navigatable = message.getNavigatable();
if (navigatable instanceof OpenFileDescriptor) {
file = ((OpenFileDescriptor)navigatable).getFile();
}
}
if (file != null) {
WolfTheProblemSolver.getInstance(myProject).queue(file);
}
};
final Application app = ApplicationManager.getApplication();
if (app.isDispatchThread()) {
app.executeOnPooledThread(task);
}
else {
task.run();
}
}
public static int translateCategory(CompilerMessageCategory category) {
return CompilerMessagesService.translateCategory(category);
}
public void start(Runnable compileWork, Runnable restartWork) {
myCompileWork = compileWork;
myRestartWork = restartWork;
ProgressManager.getInstance().run(this);
}
public void run(Runnable compileWork, Runnable restartWork, ProgressIndicator progressIndicator) {
myCompileWork = compileWork;
myRestartWork = restartWork;
run(progressIndicator);
}
@Override
public boolean isHeadless() {
return myHeadlessMode && !myForceAsyncExecution;
}
public static TextRange getTextRange(final CompilerMessage message) {
Navigatable navigatable = message.getNavigatable();
if (navigatable instanceof OpenFileDescriptor) {
int offset = ((OpenFileDescriptor)navigatable).getOffset();
return new TextRange(offset, offset);
}
return TextRange.EMPTY_RANGE;
}
public static final class IDObject {
private final String myDisplayName;
public IDObject(@NotNull String displayName) {
myDisplayName = displayName;
}
@Override
public String toString() {
return myDisplayName;
}
}
}