diff --git a/common/src/main/java/org/sonarlint/intellij/common/util/SonarLintUtils.java b/common/src/main/java/org/sonarlint/intellij/common/util/SonarLintUtils.java index 60b30df874..861d7effee 100644 --- a/common/src/main/java/org/sonarlint/intellij/common/util/SonarLintUtils.java +++ b/common/src/main/java/org/sonarlint/intellij/common/util/SonarLintUtils.java @@ -119,6 +119,7 @@ public static VirtualFile getSelectedFile(Project project) { if (project.isDisposed()) { return null; } + ApplicationManager.getApplication().assertIsDispatchThread(); var editorManager = FileEditorManager.getInstance(project); var editor = editorManager.getSelectedTextEditor(); diff --git a/src/main/java/org/sonarlint/intellij/SonarLintIntelliJClient.kt b/src/main/java/org/sonarlint/intellij/SonarLintIntelliJClient.kt index 1f37d6743c..0dcc705243 100644 --- a/src/main/java/org/sonarlint/intellij/SonarLintIntelliJClient.kt +++ b/src/main/java/org/sonarlint/intellij/SonarLintIntelliJClient.kt @@ -594,19 +594,12 @@ object SonarLintIntelliJClient : SonarLintRpcClientDelegate { if (project == null || project.isDisposed) return@forEach getService(project, AnalysisReadinessCache::class.java).isReady = areReadyForAnalysis if (areReadyForAnalysis) { - val findingToShow = getService(project, OpenInIdeFindingCache::class.java).finding - if (findingToShow != null && !getService(project, OpenInIdeFindingCache::class.java).analysisQueued) { - getService(project, AnalysisSubmitter::class.java).analyzeFileAndTrySelectFinding(findingToShow) - } - - if (ApplicationManager.getApplication().isUnitTestMode) { - runOnPooledThread(project) { - getService(project, AnalysisSubmitter::class.java).autoAnalyzeOpenFilesForModule( - TriggerType.BINDING_UPDATE, - module - ) + runOnPooledThread(project) { + val findingToShow = getService(project, OpenInIdeFindingCache::class.java).finding + if (findingToShow != null && !getService(project, OpenInIdeFindingCache::class.java).analysisQueued) { + getService(project, AnalysisSubmitter::class.java).analyzeFileAndTrySelectFinding(findingToShow) } - } else { + getService(project, AnalysisSubmitter::class.java).autoAnalyzeOpenFilesForModule( TriggerType.BINDING_UPDATE, module diff --git a/src/main/java/org/sonarlint/intellij/StartServicesOnProjectOpened.java b/src/main/java/org/sonarlint/intellij/StartServicesOnProjectOpened.java index 21897e6895..1b774577c9 100644 --- a/src/main/java/org/sonarlint/intellij/StartServicesOnProjectOpened.java +++ b/src/main/java/org/sonarlint/intellij/StartServicesOnProjectOpened.java @@ -30,6 +30,7 @@ import org.sonarlint.intellij.trigger.EditorChangeTrigger; import static org.sonarlint.intellij.common.util.SonarLintUtils.getService; +import static org.sonarlint.intellij.util.ThreadUtilsKt.runOnPooledThread; public class StartServicesOnProjectOpened implements StartupActivity { @@ -38,10 +39,12 @@ public void runActivity(@NotNull Project project) { if (ApplicationManager.getApplication().isUnitTestMode()) { return; } - getService(EditorFileChangeListener.class).startListening(); - getService(project, EditorChangeTrigger.class).onProjectOpened(); - getService(BackendService.class).projectOpened(project); - getService(project, SecurityHotspotsRefreshTrigger.class).subscribeToTriggeringEvents(); - getService(project, PromotionProvider.class).subscribeToTriggeringEvents(); + runOnPooledThread(project, () -> { + getService(EditorFileChangeListener.class).startListening(); + getService(project, EditorChangeTrigger.class).onProjectOpened(); + getService(BackendService.class).projectOpened(project); + getService(project, SecurityHotspotsRefreshTrigger.class).subscribeToTriggeringEvents(); + getService(project, PromotionProvider.class).subscribeToTriggeringEvents(); + }); } } diff --git a/src/main/java/org/sonarlint/intellij/actions/ClearCurrentFileIssuesAction.java b/src/main/java/org/sonarlint/intellij/actions/ClearCurrentFileIssuesAction.java index 9199f1be18..39ae1c177c 100644 --- a/src/main/java/org/sonarlint/intellij/actions/ClearCurrentFileIssuesAction.java +++ b/src/main/java/org/sonarlint/intellij/actions/ClearCurrentFileIssuesAction.java @@ -64,6 +64,7 @@ public void actionPerformed(AnActionEvent e) { } public Collection<PsiFile> findFiles(Project project, VirtualFile[] files) { + ApplicationManager.getApplication().assertReadAccessAllowed(); var psiManager = PsiManager.getInstance(project); var psiFiles = new ArrayList<PsiFile>(files.length); diff --git a/src/main/java/org/sonarlint/intellij/actions/DisableRuleAction.java b/src/main/java/org/sonarlint/intellij/actions/DisableRuleAction.java index 156a0533e5..1ef9b2a813 100644 --- a/src/main/java/org/sonarlint/intellij/actions/DisableRuleAction.java +++ b/src/main/java/org/sonarlint/intellij/actions/DisableRuleAction.java @@ -24,7 +24,6 @@ import com.intellij.openapi.project.Project; import org.sonarlint.intellij.analysis.AnalysisStatus; import org.sonarlint.intellij.analysis.AnalysisSubmitter; -import org.sonarlint.intellij.common.util.SonarLintUtils; import org.sonarlint.intellij.core.BackendService; import org.sonarlint.intellij.trigger.TriggerType; @@ -32,6 +31,7 @@ import static org.sonarlint.intellij.config.Settings.getGlobalSettings; import static org.sonarlint.intellij.config.Settings.getSettingsFor; import static org.sonarlint.intellij.util.DataKeys.ISSUE_DATA_KEY; +import static org.sonarlint.intellij.util.ThreadUtilsKt.runOnPooledThread; public class DisableRuleAction extends AbstractSonarAction { @@ -62,7 +62,7 @@ public void actionPerformed(AnActionEvent e) { var issue = e.getData(ISSUE_DATA_KEY); if (issue != null) { disableRule(issue.getRuleKey()); - SonarLintUtils.getService(project, AnalysisSubmitter.class).autoAnalyzeOpenFiles(TriggerType.BINDING_UPDATE); + runOnPooledThread(project, () -> getService(project, AnalysisSubmitter.class).autoAnalyzeOpenFiles(TriggerType.BINDING_UPDATE)); } } diff --git a/src/main/java/org/sonarlint/intellij/actions/ExcludeFileAction.kt b/src/main/java/org/sonarlint/intellij/actions/ExcludeFileAction.kt index e17546fd85..efb7f788b1 100644 --- a/src/main/java/org/sonarlint/intellij/actions/ExcludeFileAction.kt +++ b/src/main/java/org/sonarlint/intellij/actions/ExcludeFileAction.kt @@ -27,12 +27,11 @@ import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import org.sonarlint.intellij.analysis.AnalysisStatus import org.sonarlint.intellij.analysis.AnalysisSubmitter -import org.sonarlint.intellij.common.util.SonarLintUtils +import org.sonarlint.intellij.common.util.SonarLintUtils.getService import org.sonarlint.intellij.config.Settings import org.sonarlint.intellij.config.project.ExclusionItem import org.sonarlint.intellij.messages.ProjectConfigurationListener import org.sonarlint.intellij.trigger.TriggerType -import org.sonarlint.intellij.ui.UiUtils.Companion.runOnUiThread import org.sonarlint.intellij.util.SonarLintAppUtils import org.sonarlint.intellij.util.runOnPooledThread @@ -64,11 +63,9 @@ class ExcludeFileAction : AbstractSonarAction { if (newExclusions.isNotEmpty()) { exclusions.addAll(newExclusions) settings.fileExclusions = exclusions - SonarLintUtils.getService(project, AnalysisSubmitter::class.java).autoAnalyzeOpenFiles(TriggerType.CONFIG_CHANGE) - runOnUiThread(project) { - val projectListener = project.messageBus.syncPublisher(ProjectConfigurationListener.TOPIC) - projectListener.changed(settings) - } + getService(project, AnalysisSubmitter::class.java).autoAnalyzeOpenFiles(TriggerType.CONFIG_CHANGE) + val projectListener = project.messageBus.syncPublisher(ProjectConfigurationListener.TOPIC) + projectListener.changed(settings) } } } diff --git a/src/main/java/org/sonarlint/intellij/actions/MarkAsResolvedAction.kt b/src/main/java/org/sonarlint/intellij/actions/MarkAsResolvedAction.kt index 102bb32feb..9be2fe978c 100644 --- a/src/main/java/org/sonarlint/intellij/actions/MarkAsResolvedAction.kt +++ b/src/main/java/org/sonarlint/intellij/actions/MarkAsResolvedAction.kt @@ -46,6 +46,7 @@ import org.sonarlint.intellij.ui.resolve.MarkAsResolvedDialog import org.sonarlint.intellij.util.DataKeys.Companion.ISSUE_DATA_KEY import org.sonarlint.intellij.util.DataKeys.Companion.TAINT_VULNERABILITY_DATA_KEY import org.sonarlint.intellij.util.SonarLintAppUtils.findModuleForFile +import org.sonarlint.intellij.util.computeOnPooledThread import org.sonarlint.intellij.util.runOnPooledThread import org.sonarsource.sonarlint.core.client.utils.IssueResolutionStatus import org.sonarsource.sonarlint.core.rpc.protocol.backend.issue.CheckStatusChangePermittedResponse @@ -86,7 +87,9 @@ class MarkAsResolvedAction( project, "No module could be found for this file" ) val serverKey = issue.getServerKey() ?: issue.getId().toString() - val response = checkPermission(project, connection, serverKey) ?: return + val response = computeOnPooledThread(project, "Checking permission to mark issue as resolved") { + checkPermission(project, connection, serverKey) + } ?: return if (response.isPermitted) { runOnUiThread(project) { @@ -96,7 +99,9 @@ class MarkAsResolvedAction( response, ).chooseResolution() ?: return@runOnUiThread if (confirm(project, connection.productName, resolution.newStatus)) { - markAsResolved(project, module, issue, resolution, serverKey) + runOnPooledThread(project) { + markAsResolved(project, module, issue, resolution, serverKey) + } } } } else { diff --git a/src/main/java/org/sonarlint/intellij/actions/OpenSecurityHotspotInBrowserAction.kt b/src/main/java/org/sonarlint/intellij/actions/OpenSecurityHotspotInBrowserAction.kt index 97548a783c..1d9edfbb00 100644 --- a/src/main/java/org/sonarlint/intellij/actions/OpenSecurityHotspotInBrowserAction.kt +++ b/src/main/java/org/sonarlint/intellij/actions/OpenSecurityHotspotInBrowserAction.kt @@ -29,6 +29,7 @@ import org.sonarlint.intellij.core.BackendService import org.sonarlint.intellij.core.ProjectBindingManager import org.sonarlint.intellij.finding.hotspot.LiveSecurityHotspot import org.sonarlint.intellij.util.SonarLintAppUtils.findModuleForFile +import org.sonarlint.intellij.util.runOnPooledThread class OpenSecurityHotspotInBrowserAction : AbstractSonarAction( "Open In Browser", @@ -57,7 +58,9 @@ class OpenSecurityHotspotInBrowserAction : AbstractSonarAction( val key = securityHotspot?.getServerKey() ?: return val localFile = securityHotspot.file() val localFileModule = findModuleForFile(localFile, project) ?: return - getService(BackendService::class.java).openHotspotInBrowser(localFileModule, key) + runOnPooledThread(project) { + getService(BackendService::class.java).openHotspotInBrowser(localFileModule, key) + } } private fun serverConnection(project: Project): ServerConnection? = getService(project, ProjectBindingManager::class.java).tryGetServerConnection().orElse(null) diff --git a/src/main/java/org/sonarlint/intellij/actions/RefreshTaintVulnerabilitiesAction.kt b/src/main/java/org/sonarlint/intellij/actions/RefreshTaintVulnerabilitiesAction.kt index e9d9a8881f..e442dced12 100644 --- a/src/main/java/org/sonarlint/intellij/actions/RefreshTaintVulnerabilitiesAction.kt +++ b/src/main/java/org/sonarlint/intellij/actions/RefreshTaintVulnerabilitiesAction.kt @@ -26,12 +26,16 @@ import org.sonarlint.intellij.analysis.AnalysisStatus import org.sonarlint.intellij.common.util.SonarLintUtils.getService import org.sonarlint.intellij.core.BackendService import org.sonarlint.intellij.core.ProjectBindingManager +import org.sonarlint.intellij.util.runOnPooledThread class RefreshTaintVulnerabilitiesAction(text: String = "Refresh") : AbstractSonarAction(text, "Refresh taint vulnerabilities for open files", AllIcons.Actions.Refresh) { override fun isEnabled(e: AnActionEvent, project: Project, status: AnalysisStatus) = getService(project, ProjectBindingManager::class.java).isBindingValid override fun actionPerformed(e: AnActionEvent) { val project = e.project ?: return - getService(BackendService::class.java).refreshTaintVulnerabilities(project) + + runOnPooledThread { + getService(BackendService::class.java).refreshTaintVulnerabilities(project) + } } } diff --git a/src/main/java/org/sonarlint/intellij/actions/ReopenIssueAction.kt b/src/main/java/org/sonarlint/intellij/actions/ReopenIssueAction.kt index a3ff3db42e..215d611abf 100644 --- a/src/main/java/org/sonarlint/intellij/actions/ReopenIssueAction.kt +++ b/src/main/java/org/sonarlint/intellij/actions/ReopenIssueAction.kt @@ -43,6 +43,7 @@ import org.sonarlint.intellij.finding.issue.vulnerabilities.LocalTaintVulnerabil import org.sonarlint.intellij.notifications.SonarLintProjectNotifications import org.sonarlint.intellij.util.DataKeys import org.sonarlint.intellij.util.SonarLintAppUtils.findModuleForFile +import org.sonarlint.intellij.util.runOnPooledThread private const val SKIP_CONFIRM_REOPEN_DIALOG_PROPERTY = "SonarLint.reopenIssue.hideConfirmation" @@ -73,7 +74,7 @@ class ReopenIssueAction(private var issue: LiveIssue? = null) : AbstractSonarAct serverKey ?: return displayNotificationError(project, "The issue key could not be found") if (confirm(project, connection.productName)) { - reopenFinding(project, module, issue, serverKey) + runOnPooledThread(project) { reopenFinding(project, module, issue, serverKey) } } } diff --git a/src/main/java/org/sonarlint/intellij/actions/ReviewSecurityHotspotAction.kt b/src/main/java/org/sonarlint/intellij/actions/ReviewSecurityHotspotAction.kt index dcfc93fead..a0bb616549 100644 --- a/src/main/java/org/sonarlint/intellij/actions/ReviewSecurityHotspotAction.kt +++ b/src/main/java/org/sonarlint/intellij/actions/ReviewSecurityHotspotAction.kt @@ -42,6 +42,7 @@ import org.sonarlint.intellij.tasks.FutureAwaitingTask import org.sonarlint.intellij.ui.UiUtils.Companion.runOnUiThread import org.sonarlint.intellij.ui.review.ReviewSecurityHotspotDialog import org.sonarlint.intellij.util.SonarLintAppUtils.findModuleForFile +import org.sonarlint.intellij.util.computeOnPooledThread import org.sonarlint.intellij.util.runOnPooledThread import org.sonarsource.sonarlint.core.commons.HotspotReviewStatus import org.sonarsource.sonarlint.core.rpc.protocol.backend.hotspot.CheckStatusChangePermittedResponse @@ -103,7 +104,10 @@ class ReviewSecurityHotspotAction(private var serverFindingKey: String? = null, "No module could be found for this file" ) - val response = checkPermission(project, connection, hotspotKey) ?: return + val response = computeOnPooledThread(project, "Checking permission to mark issue as resolved") { + checkPermission(project, connection, hotspotKey) + } ?: return + val newStatus = HotspotStatus.valueOf(currentStatus.name) runOnUiThread(project) { if (ReviewSecurityHotspotDialog(project, connection.productName, module, hotspotKey, response, newStatus).showAndGet()) { diff --git a/src/main/java/org/sonarlint/intellij/actions/SonarAnalyzeAllFilesAction.java b/src/main/java/org/sonarlint/intellij/actions/SonarAnalyzeAllFilesAction.java index a6b39fc63f..c507fff8ce 100644 --- a/src/main/java/org/sonarlint/intellij/actions/SonarAnalyzeAllFilesAction.java +++ b/src/main/java/org/sonarlint/intellij/actions/SonarAnalyzeAllFilesAction.java @@ -37,6 +37,7 @@ import static org.sonarlint.intellij.common.util.SonarLintUtils.getService; import static org.sonarlint.intellij.util.ProjectUtils.hasFiles; +import static org.sonarlint.intellij.util.ThreadUtilsKt.runOnPooledThread; public class SonarAnalyzeAllFilesAction extends AbstractSonarAction { private static final String HIDE_WARNING_PROPERTY = "SonarLint.analyzeAllFiles.hideWarning"; @@ -70,7 +71,7 @@ public void actionPerformed(AnActionEvent e) { return; } - SonarLintUtils.getService(project, AnalysisSubmitter.class).analyzeAllFiles(); + runOnPooledThread(project, () -> SonarLintUtils.getService(project, AnalysisSubmitter.class).analyzeAllFiles()); } static boolean userConfirmed(Project project) { diff --git a/src/main/java/org/sonarlint/intellij/actions/SonarAnalyzeChangedFilesAction.java b/src/main/java/org/sonarlint/intellij/actions/SonarAnalyzeChangedFilesAction.java index 78b15b1813..b541d136f2 100644 --- a/src/main/java/org/sonarlint/intellij/actions/SonarAnalyzeChangedFilesAction.java +++ b/src/main/java/org/sonarlint/intellij/actions/SonarAnalyzeChangedFilesAction.java @@ -25,12 +25,13 @@ import com.intellij.openapi.vcs.changes.ChangeListManager; import javax.swing.Icon; import org.jetbrains.annotations.Nullable; -import org.sonarlint.intellij.analysis.AnalysisSubmitter; import org.sonarlint.intellij.analysis.AnalysisStatus; +import org.sonarlint.intellij.analysis.AnalysisSubmitter; import org.sonarlint.intellij.common.util.SonarLintUtils; import org.sonarlint.intellij.core.BackendService; import static org.sonarlint.intellij.common.util.SonarLintUtils.getService; +import static org.sonarlint.intellij.util.ThreadUtilsKt.runOnPooledThread; public class SonarAnalyzeChangedFilesAction extends AbstractSonarAction { public SonarAnalyzeChangedFilesAction() { @@ -62,6 +63,6 @@ protected boolean isVisible(AnActionEvent e) { return; } - SonarLintUtils.getService(project, AnalysisSubmitter.class).analyzeVcsChangedFiles(); + runOnPooledThread(project, () -> SonarLintUtils.getService(project, AnalysisSubmitter.class).analyzeVcsChangedFiles()); } } diff --git a/src/main/java/org/sonarlint/intellij/actions/SonarAnalyzeFilesAction.java b/src/main/java/org/sonarlint/intellij/actions/SonarAnalyzeFilesAction.java index bfba9b119e..707b46a796 100644 --- a/src/main/java/org/sonarlint/intellij/actions/SonarAnalyzeFilesAction.java +++ b/src/main/java/org/sonarlint/intellij/actions/SonarAnalyzeFilesAction.java @@ -37,6 +37,7 @@ import org.sonarlint.intellij.core.BackendService; import static org.sonarlint.intellij.common.util.SonarLintUtils.getService; +import static org.sonarlint.intellij.util.ThreadUtilsKt.runOnPooledThread; public class SonarAnalyzeFilesAction extends AbstractSonarAction { @@ -88,7 +89,7 @@ public void actionPerformed(AnActionEvent e) { }) .collect(Collectors.toSet()); - getService(project, AnalysisSubmitter.class).analyzeFilesOnUserAction(fileSet, e); + runOnPooledThread(project, () -> getService(project, AnalysisSubmitter.class).analyzeFilesOnUserAction(fileSet, e)); } private static class CollectFilesVisitor extends VirtualFileVisitor { diff --git a/src/main/java/org/sonarlint/intellij/actions/SonarFocusOnNewCode.kt b/src/main/java/org/sonarlint/intellij/actions/SonarFocusOnNewCode.kt index 7b4f83a234..163180715d 100644 --- a/src/main/java/org/sonarlint/intellij/actions/SonarFocusOnNewCode.kt +++ b/src/main/java/org/sonarlint/intellij/actions/SonarFocusOnNewCode.kt @@ -24,7 +24,7 @@ import com.intellij.openapi.actionSystem.Presentation import com.intellij.openapi.project.Project import org.sonarlint.intellij.cayc.CleanAsYouCodeService import org.sonarlint.intellij.common.util.SonarLintUtils.getService -import org.sonarlint.intellij.config.Settings +import org.sonarlint.intellij.util.runOnPooledThread class SonarFocusOnNewCode : AbstractSonarToggleAction() { @@ -32,7 +32,7 @@ class SonarFocusOnNewCode : AbstractSonarToggleAction() { ?: false override fun setSelected(e: AnActionEvent, isSelected: Boolean) { - getService(CleanAsYouCodeService::class.java).setFocusOnNewCode(isSelected) + e.project?.let { runOnPooledThread(it) { getService(CleanAsYouCodeService::class.java).setFocusOnNewCode(isSelected) } } } override fun updatePresentation(project: Project, presentation: Presentation) { diff --git a/src/main/java/org/sonarlint/intellij/actions/SonarLintToolWindow.java b/src/main/java/org/sonarlint/intellij/actions/SonarLintToolWindow.java index acb3572131..72ca79bfe9 100644 --- a/src/main/java/org/sonarlint/intellij/actions/SonarLintToolWindow.java +++ b/src/main/java/org/sonarlint/intellij/actions/SonarLintToolWindow.java @@ -64,6 +64,7 @@ import static org.sonarlint.intellij.common.util.SonarLintUtils.getService; import static org.sonarlint.intellij.ui.UiUtils.runOnUiThread; +import static org.sonarlint.intellij.util.ThreadUtilsKt.runOnPooledThread; @Service(Service.Level.PROJECT) public final class SonarLintToolWindow implements ContentManagerListener, ProjectBindingListener { @@ -81,6 +82,7 @@ public SonarLintToolWindow(Project project) { * Must run in EDT */ public void openReportTab(AnalysisResult analysisResult) { + ApplicationManager.getApplication().assertIsDispatchThread(); this.<ReportPanel>openTab(SonarLintToolWindowFactory.REPORT_TAB_TITLE, panel -> panel.updateFindings(analysisResult)); } @@ -123,13 +125,13 @@ public void filterSecurityHotspotTab(boolean isResolved) { } private <T> void openTab(String displayName, Consumer<T> tabPanelConsumer) { - var toolWindow = updateTab(displayName, tabPanelConsumer); + var toolWindow = updateTabAndGet(displayName, tabPanelConsumer); if (toolWindow != null) { toolWindow.show(() -> selectTab(toolWindow, displayName)); } } - private <T> ToolWindow updateTab(String displayName, Consumer<T> tabPanelConsumer) { + private <T> ToolWindow updateTabAndGet(String displayName, Consumer<T> tabPanelConsumer) { ApplicationManager.getApplication().assertIsDispatchThread(); var toolWindow = getToolWindow(); if (toolWindow != null) { @@ -137,12 +139,26 @@ private <T> ToolWindow updateTab(String displayName, Consumer<T> tabPanelConsume var content = contentManager.findContent(displayName); if (content != null) { var panel = (T) content.getComponent(); - tabPanelConsumer.accept(panel); + runOnPooledThread(project, () -> tabPanelConsumer.accept(panel)); } } return toolWindow; } + private <T> void updateTab(String displayName, Consumer<T> tabPanelConsumer) { + var toolWindow = getToolWindow(); + if (toolWindow != null) { + runOnUiThread(project, () -> { + var contentManager = toolWindow.getContentManager(); + var content = contentManager.findContent(displayName); + if (content != null) { + var panel = (T) content.getComponent(); + runOnPooledThread(project, () -> tabPanelConsumer.accept(panel)); + } + }); + } + } + public void filterCurrentFileTab(boolean isResolved) { this.<CurrentFilePanel>updateTab(SonarLintToolWindowFactory.CURRENT_FILE_TAB_TITLE, panel -> panel.allowResolvedIssues(isResolved)); } @@ -159,6 +175,7 @@ public void filterTaintVulnerabilityTab(boolean isResolved) { * Must run in EDT */ public void openCurrentFileTab() { + ApplicationManager.getApplication().assertIsDispatchThread(); openTab(SonarLintToolWindowFactory.CURRENT_FILE_TAB_TITLE); } @@ -207,7 +224,7 @@ public void refreshViews() { var hotspotContent = getSecurityHotspotContent(); if (hotspotContent != null) { var hotspotsPanel = (SecurityHotspotsPanel) hotspotContent.getComponent(); - hotspotContent.setDisplayName(buildTabName(hotspotsPanel.refreshView(), SonarLintToolWindowFactory.SECURITY_HOTSPOTS_TAB_TITLE)); + runOnUiThread(project, () -> hotspotContent.setDisplayName(buildTabName(hotspotsPanel.refreshView(), SonarLintToolWindowFactory.SECURITY_HOTSPOTS_TAB_TITLE))); } var taintContent = getTaintVulnerabilitiesContent(); @@ -289,7 +306,8 @@ private static void selectTab(ToolWindow toolWindow, String tabId) { } public void updateCurrentFileTab(@Nullable VirtualFile selectedFile, @Nullable Collection<LiveIssue> issues) { - this.<CurrentFilePanel>updateTab(SonarLintToolWindowFactory.CURRENT_FILE_TAB_TITLE, panel -> panel.update(selectedFile, issues)); + this.<CurrentFilePanel>updateTab(SonarLintToolWindowFactory.CURRENT_FILE_TAB_TITLE, + panel -> runOnUiThread(project, () -> panel.update(selectedFile, issues))); } private void showIssue(LiveIssue liveIssue, Consumer<CurrentFilePanel> selectTab) { @@ -401,8 +419,10 @@ public void updateOnTheFlySecurityHotspots(@NotNull Map<VirtualFile, Collection< var content = getSecurityHotspotContent(); if (content != null) { var hotspotsPanel = (SecurityHotspotsPanel) content.getComponent(); - var count = hotspotsPanel.updateHotspots(currentSecurityHotspotsPerOpenFile); - content.setDisplayName(buildTabName(count, SonarLintToolWindowFactory.SECURITY_HOTSPOTS_TAB_TITLE)); + runOnUiThread(project, () -> { + var count = hotspotsPanel.updateHotspots(currentSecurityHotspotsPerOpenFile); + content.setDisplayName(buildTabName(count, SonarLintToolWindowFactory.SECURITY_HOTSPOTS_TAB_TITLE)); + }); } } @@ -441,6 +461,6 @@ private Content getTaintVulnerabilitiesContent() { @Override public void bindingChanged() { - runOnUiThread(project, this::refreshViews); + runOnPooledThread(project, this::refreshViews); } } diff --git a/src/main/java/org/sonarlint/intellij/analysis/OnTheFlyFindingsHolder.kt b/src/main/java/org/sonarlint/intellij/analysis/OnTheFlyFindingsHolder.kt index 945d2527d4..24c9d85e67 100644 --- a/src/main/java/org/sonarlint/intellij/analysis/OnTheFlyFindingsHolder.kt +++ b/src/main/java/org/sonarlint/intellij/analysis/OnTheFlyFindingsHolder.kt @@ -58,8 +58,8 @@ class OnTheFlyFindingsHolder(private val project: Project) : FileEditorManagerLi updateViewsWithNewFindings(intermediateResult.findings) private fun updateViewsWithNewFindings(findings: LiveFindings) { - runOnUiThread(project) { - if (selectedFile == null) { + if (selectedFile == null) { + runOnUiThread(project) { selectedFile = SonarLintUtils.getSelectedFile(project) } } @@ -69,11 +69,9 @@ class OnTheFlyFindingsHolder(private val project: Project) : FileEditorManagerLi currentIssuesPerOpenFile.putAll(issuesPerFile) currentSecurityHotspotsPerOpenFile.putAll(securityHotspotsPerFile) } - runOnUiThread(project) { - updateCurrentFileTab() - updateSecurityHotspots() - getService(project, CodeAnalyzerRestarter::class.java).refreshFiles(findings.onlyFor(openedFiles).filesInvolved) - } + updateCurrentFileTab() + updateSecurityHotspots() + getService(project, CodeAnalyzerRestarter::class.java).refreshFiles(findings.onlyFor(openedFiles).filesInvolved) } fun updateViewsWithNewIssues(module: Module, raisedIssues: Map<URI, List<RaisedIssueDto>>) { @@ -85,13 +83,13 @@ class OnTheFlyFindingsHolder(private val project: Project) : FileEditorManagerLi virtualFile to liveIssues }.toMap() currentIssuesPerOpenFile.putAll(issues) - runOnUiThread(project) { - if (selectedFile == null) { + if (selectedFile == null) { + runOnUiThread(project) { selectedFile = SonarLintUtils.getSelectedFile(project) } - updateCurrentFileTab() - getService(project, CodeAnalyzerRestarter::class.java).refreshFiles(issues.keys) } + updateCurrentFileTab() + getService(project, CodeAnalyzerRestarter::class.java).refreshFiles(issues.keys) } fun updateViewsWithNewSecurityHotspots(module: Module, raisedSecurityHotspots: Map<URI, List<RaisedHotspotDto>>) { @@ -103,13 +101,13 @@ class OnTheFlyFindingsHolder(private val project: Project) : FileEditorManagerLi virtualFile to liveIssues }.toMap().filterKeys { openFiles.contains(it) } currentSecurityHotspotsPerOpenFile.putAll(securityHotspots) - runOnUiThread(project) { - if (selectedFile == null) { + if (selectedFile == null) { + runOnUiThread(project) { selectedFile = SonarLintUtils.getSelectedFile(project) } - updateSecurityHotspots() - getService(project, CodeAnalyzerRestarter::class.java).refreshFiles(securityHotspots.keys) } + updateSecurityHotspots() + getService(project, CodeAnalyzerRestarter::class.java).refreshFiles(securityHotspots.keys) } override fun selectionChanged(event: FileEditorManagerEvent) { diff --git a/src/main/java/org/sonarlint/intellij/cayc/CleanAsYouCodeService.kt b/src/main/java/org/sonarlint/intellij/cayc/CleanAsYouCodeService.kt index d86f3cd698..c3fb94ac01 100644 --- a/src/main/java/org/sonarlint/intellij/cayc/CleanAsYouCodeService.kt +++ b/src/main/java/org/sonarlint/intellij/cayc/CleanAsYouCodeService.kt @@ -28,6 +28,7 @@ import org.sonarlint.intellij.common.util.SonarLintUtils.getService import org.sonarlint.intellij.config.Settings.getGlobalSettings import org.sonarlint.intellij.config.global.SonarLintGlobalSettings import org.sonarlint.intellij.core.BackendService +import org.sonarlint.intellij.util.runOnPooledThread @Service(Service.Level.APP) class CleanAsYouCodeService { @@ -46,11 +47,13 @@ class CleanAsYouCodeService { private fun refresh(settings: SonarLintGlobalSettings, isFocusOnNewCode: Boolean) { if (settings.isFocusOnNewCode != isFocusOnNewCode) { settings.isFocusOnNewCode = isFocusOnNewCode - getService(BackendService::class.java).triggerTelemetryForFocusOnNewCode() - ProjectManager.getInstance().openProjects.forEach { project -> - if (!project.isDisposed) { - getService(project, SonarLintToolWindow::class.java).refreshViews() - DaemonCodeAnalyzer.getInstance(project).restart() + runOnPooledThread { + getService(BackendService::class.java).triggerTelemetryForFocusOnNewCode() + ProjectManager.getInstance().openProjects.forEach { project -> + if (!project.isDisposed) { + getService(project, SonarLintToolWindow::class.java).refreshViews() + DaemonCodeAnalyzer.getInstance(project).restart() + } } } } diff --git a/src/main/java/org/sonarlint/intellij/cayc/NewCodePeriodCache.kt b/src/main/java/org/sonarlint/intellij/cayc/NewCodePeriodCache.kt index 1936276aaf..546b46ce67 100644 --- a/src/main/java/org/sonarlint/intellij/cayc/NewCodePeriodCache.kt +++ b/src/main/java/org/sonarlint/intellij/cayc/NewCodePeriodCache.kt @@ -24,19 +24,22 @@ import com.intellij.openapi.project.Project import java.util.Locale import org.sonarlint.intellij.common.util.SonarLintUtils.getService import org.sonarlint.intellij.core.BackendService +import org.sonarlint.intellij.util.runOnPooledThread @Service(Service.Level.PROJECT) class NewCodePeriodCache(private val project: Project) { var periodAsString: String = "(unknown code period)" fun refreshAsync() { - getService(BackendService::class.java).getNewCodePeriodText(project) - .thenAccept { period -> - periodAsString = period.replaceFirstChar { char -> - char.lowercase( - Locale.getDefault() - ) + runOnPooledThread(project) { + getService(BackendService::class.java).getNewCodePeriodText(project) + .thenAccept { period -> + periodAsString = period.replaceFirstChar { char -> + char.lowercase( + Locale.getDefault() + ) + } } - } + } } } diff --git a/src/main/java/org/sonarlint/intellij/config/global/SonarLintAboutPanel.java b/src/main/java/org/sonarlint/intellij/config/global/SonarLintAboutPanel.java index c4c1bd46d3..a6d5e0127a 100644 --- a/src/main/java/org/sonarlint/intellij/config/global/SonarLintAboutPanel.java +++ b/src/main/java/org/sonarlint/intellij/config/global/SonarLintAboutPanel.java @@ -49,6 +49,7 @@ import static org.sonarlint.intellij.documentation.SonarLintDocumentation.Intellij.BASE_DOCS_URL; import static org.sonarlint.intellij.ui.UiUtils.runOnUiThread; +import static org.sonarlint.intellij.util.ThreadUtilsKt.runOnPooledThread; public class SonarLintAboutPanel implements ConfigurationPanel<SonarLintTelemetry> { private final JPanel panel; @@ -234,10 +235,10 @@ public JComponent getComponent() { @Override public void load(SonarLintTelemetry telemetry) { // we could show a loader while getting the value - telemetry.enabled().thenAccept(enabled -> { + runOnPooledThread(() -> telemetry.enabled().thenAccept(enabled -> { telemetryInitiallyEnabled = enabled; - runOnUiThread(() -> enableTelemetryCheckBox.setSelected(enabled), ModalityState.stateForComponent(getComponent())); - }); + runOnUiThread(ModalityState.defaultModalityState(), () -> enableTelemetryCheckBox.setSelected(enabled)); + })); } @Override diff --git a/src/main/java/org/sonarlint/intellij/config/global/SonarLintGlobalConfigurable.java b/src/main/java/org/sonarlint/intellij/config/global/SonarLintGlobalConfigurable.java index d2e6bf5bb2..411533da5c 100644 --- a/src/main/java/org/sonarlint/intellij/config/global/SonarLintGlobalConfigurable.java +++ b/src/main/java/org/sonarlint/intellij/config/global/SonarLintGlobalConfigurable.java @@ -40,6 +40,7 @@ import static org.sonarlint.intellij.common.util.SonarLintUtils.getService; import static org.sonarlint.intellij.config.Settings.getGlobalSettings; import static org.sonarlint.intellij.config.Settings.getSettingsFor; +import static org.sonarlint.intellij.util.ThreadUtilsKt.runOnPooledThread; public class SonarLintGlobalConfigurable implements Configurable, Configurable.NoScroll { private static final int SETTINGS_TAB_INDEX = 0; @@ -98,11 +99,13 @@ public void apply() { // Force reload of the node version and rules in case the nodejs path has been changed reset(); - if (exclusionsModified || globalSettingsModified) { - analyzeOpenFiles(false); - } else if (rulesModified) { - analyzeOpenFiles(true); - } + runOnPooledThread(() -> { + if (exclusionsModified || globalSettingsModified) { + analyzeOpenFiles(false); + } else if (rulesModified) { + analyzeOpenFiles(true); + } + }); } public static void analyzeOpenFiles(boolean unboundOnly) { diff --git a/src/main/java/org/sonarlint/intellij/config/global/SonarLintGlobalOptionsPanel.java b/src/main/java/org/sonarlint/intellij/config/global/SonarLintGlobalOptionsPanel.java index 4209e8480a..f9d2d43f93 100644 --- a/src/main/java/org/sonarlint/intellij/config/global/SonarLintGlobalOptionsPanel.java +++ b/src/main/java/org/sonarlint/intellij/config/global/SonarLintGlobalOptionsPanel.java @@ -140,15 +140,16 @@ public void load(SonarLintGlobalSettings model) { } private void loadNodeJsSettings() { - getService(BackendService.class).getAutoDetectedNodeJs().thenAccept(settings -> { - if (settings == null) { - this.nodeJsPath.getEmptyText().setText("Node.js not found"); - this.nodeJsVersion.setText("N/A"); - } else { - this.nodeJsPath.getEmptyText().setText(settings.getPath().toString()); - this.nodeJsVersion.setText(settings.getVersion()); + getService(BackendService.class).getAutoDetectedNodeJs().thenAccept(settings -> { + if (settings == null) { + this.nodeJsPath.getEmptyText().setText("Node.js not found"); + this.nodeJsVersion.setText("N/A"); + } else { + this.nodeJsPath.getEmptyText().setText(settings.getPath().toString()); + this.nodeJsVersion.setText(settings.getVersion()); + } } - }); + ); } @Override diff --git a/src/main/java/org/sonarlint/intellij/config/global/rules/RuleConfigurationPanel.java b/src/main/java/org/sonarlint/intellij/config/global/rules/RuleConfigurationPanel.java index 87ee7cae27..3f038b12a5 100644 --- a/src/main/java/org/sonarlint/intellij/config/global/rules/RuleConfigurationPanel.java +++ b/src/main/java/org/sonarlint/intellij/config/global/rules/RuleConfigurationPanel.java @@ -114,6 +114,7 @@ import static org.sonarlint.intellij.config.Settings.getGlobalSettings; import static org.sonarlint.intellij.telemetry.LinkTelemetry.RULE_SELECTION_PAGE; import static org.sonarlint.intellij.ui.UiUtils.runOnUiThread; +import static org.sonarlint.intellij.util.ThreadUtilsKt.runOnPooledThread; public class RuleConfigurationPanel implements Disposable, ConfigurationPanel<SonarLintGlobalSettings> { private static final String MAIN_SPLITTER_KEY = "sonarlint_rule_configuration_splitter"; @@ -236,7 +237,7 @@ public boolean isModified(SonarLintGlobalSettings settings) { } private void recomputeDirtyState() { - getService(BackendService.class).getListAllStandaloneRulesDefinitions() + runOnPooledThread(project, () -> getService(BackendService.class).getListAllStandaloneRulesDefinitions() .thenAcceptAsync(response -> { var persistedRules = response.getRulesByKey().values().stream() .map(ruleDefinitionDto -> new RulesTreeNode.Rule(ruleDefinitionDto, @@ -256,7 +257,7 @@ private void recomputeDirtyState() { .exceptionally(error -> { GlobalLogOutput.get().log("Could not recompute rules: " + error.getMessage(), ClientLogOutput.Level.ERROR); return null; - }); + })); } @Override @@ -270,22 +271,21 @@ public void save(SonarLintGlobalSettings settings) { return rule; })); settings.setRulesByKey(nonDefaultRulesConfigurationByKey); - getService(BackendService.class).updateStandaloneRulesConfiguration(nonDefaultRulesConfigurationByKey); + runOnPooledThread(project, () -> getService(BackendService.class).updateStandaloneRulesConfiguration(nonDefaultRulesConfigurationByKey)); } @Override public void load(SonarLintGlobalSettings settings) { panel.startLoading(); selectedRuleKey = null; - getService(BackendService.class).getListAllStandaloneRulesDefinitions() + runOnPooledThread(project, () -> getService(BackendService.class).getListAllStandaloneRulesDefinitions() .thenAcceptAsync(response -> { allRulesStateByKey.clear(); - - var ruleNodes = response.getRulesByKey().values().stream() - .map(ruleDefinitionDto -> new RulesTreeNode.Rule(ruleDefinitionDto, - loadRuleActivation(settings, ruleDefinitionDto), - loadNonDefaultRuleParams(settings, ruleDefinitionDto))) - .collect(Collectors.toMap(RulesTreeNode.Rule::getKey, r -> r)); + var ruleNodes = response.getRulesByKey().values().stream() + .map(ruleDefinitionDto -> new RulesTreeNode.Rule(ruleDefinitionDto, + loadRuleActivation(settings, ruleDefinitionDto), + loadNonDefaultRuleParams(settings, ruleDefinitionDto))) + .collect(Collectors.toMap(RulesTreeNode.Rule::getKey, r -> r)); allRulesStateByKey.putAll(ruleNodes); @@ -299,7 +299,7 @@ public void load(SonarLintGlobalSettings settings) { .exceptionally(error -> { GlobalLogOutput.get().log("Could not load rules: " + error.getMessage(), ClientLogOutput.Level.ERROR); return null; - }); + })); } private void restoreDefaults() { @@ -507,42 +507,47 @@ private void updateParamsAndDescriptionPanel() { } private void updateParamsAndDescriptionPanel(RulesTreeNode.Rule singleNode) { - ruleHeaderPanel.updateForRuleConfiguration(singleNode.getKey(), singleNode.type(), singleNode.severity(), singleNode.attribute(), singleNode.impacts()); + ruleHeaderPanel.updateForRuleConfiguration(singleNode.getKey(), singleNode.type(), singleNode.severity(), singleNode.attribute(), + singleNode.impacts()); var fileType = RuleLanguages.Companion.findFileTypeByRuleLanguage(singleNode.language()); - getService(BackendService.class).getStandaloneRuleDetails(new GetStandaloneRuleDescriptionParams(singleNode.getKey())) - .thenAcceptAsync(details -> runOnUiThread(project, ModalityState.stateForComponent(getComponent()), () -> { - details.getDescription().map( - monolithDescription -> { - ruleDescription.addMonolith(monolithDescription, fileType); - return null; - }, - withSections -> { - ruleDescription.addSections(withSections, fileType); - return null; - }); - - myParamsPanel.removeAll(); - final var configPanelAnchor = new JBPanel<>(new GridLayout()); - setConfigPanel(configPanelAnchor, singleNode, details.getRuleDefinition().getParamsByKey()); - if (configPanelAnchor.getComponentCount() != 0) { - rulesParamsSeparator = new RulesParamsSeparator(); - myParamsPanel.add(rulesParamsSeparator, - new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, - JBUI.emptyInsets(), 0, 0)); - myParamsPanel.add(configPanelAnchor, new GridBagConstraints(0, 1, 1, 1, 1.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.BOTH, - JBUI.insetsLeft(2), 0, 0)); - } else { - myParamsPanel.add(configPanelAnchor, new GridBagConstraints(0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.BOTH, - JBUI.insetsLeft(2), 0, 0)); - } - myParamsPanel.revalidate(); - myParamsPanel.repaint(); - })) - .exceptionally(error -> { - GlobalLogOutput.get().log("Could not retrieve rule description", ClientLogOutput.Level.ERROR); - return null; - }); + runOnPooledThread(project, () -> + getService(BackendService.class).getStandaloneRuleDetails(new GetStandaloneRuleDescriptionParams(singleNode.getKey())) + .thenAcceptAsync(details -> runOnUiThread(project, ModalityState.stateForComponent(getComponent()), () -> { + details.getDescription().map( + monolithDescription -> { + ruleDescription.addMonolith(monolithDescription, fileType); + return null; + }, + withSections -> { + ruleDescription.addSections(withSections, fileType); + return null; + }); + + myParamsPanel.removeAll(); + final var configPanelAnchor = new JBPanel<>(new GridLayout()); + setConfigPanel(configPanelAnchor, singleNode, details.getRuleDefinition().getParamsByKey()); + if (configPanelAnchor.getComponentCount() != 0) { + rulesParamsSeparator = new RulesParamsSeparator(); + myParamsPanel.add(rulesParamsSeparator, + new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, + JBUI.emptyInsets(), 0, 0)); + myParamsPanel.add(configPanelAnchor, new GridBagConstraints(0, 1, 1, 1, 1.0, 1.0, GridBagConstraints.WEST, + GridBagConstraints.BOTH, + JBUI.insetsLeft(2), 0, 0)); + } else { + myParamsPanel.add(configPanelAnchor, new GridBagConstraints(0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.WEST, + GridBagConstraints.BOTH, + JBUI.insetsLeft(2), 0, 0)); + } + myParamsPanel.revalidate(); + myParamsPanel.repaint(); + })) + .exceptionally(error -> { + GlobalLogOutput.get().log("Could not retrieve rule description", ClientLogOutput.Level.ERROR); + return null; + }) + ); } private void setConfigPanel(final JPanel configPanelAnchor, RulesTreeNode.Rule rule, Map<String, RuleParamDefinitionDto> paramsByKey) { @@ -663,6 +668,7 @@ public void textChanged(DocumentEvent e) { rule.getCustomParams().remove(param.getKey()); } recomputeDirtyState(); + rulesParamsSeparator.updateDefaultLinkVisibility(); } }); diff --git a/src/main/java/org/sonarlint/intellij/config/global/wizard/AuthStep.java b/src/main/java/org/sonarlint/intellij/config/global/wizard/AuthStep.java index 2a6181b3b5..2a1b88c1a5 100644 --- a/src/main/java/org/sonarlint/intellij/config/global/wizard/AuthStep.java +++ b/src/main/java/org/sonarlint/intellij/config/global/wizard/AuthStep.java @@ -234,6 +234,7 @@ private void checkConnection() throws CommitStepException { ConnectionTestTask test = new ConnectionTestTask(tmpServer); var msg = "Failed to connect to the server. Please check the configuration."; ValidateConnectionResponse result; + try { result = ProgressManager.getInstance().run(test); } catch (Exception e) { diff --git a/src/main/java/org/sonarlint/intellij/config/project/SonarLintProjectConfigurable.java b/src/main/java/org/sonarlint/intellij/config/project/SonarLintProjectConfigurable.java index 026f17b2f6..8e2838c053 100644 --- a/src/main/java/org/sonarlint/intellij/config/project/SonarLintProjectConfigurable.java +++ b/src/main/java/org/sonarlint/intellij/config/project/SonarLintProjectConfigurable.java @@ -45,6 +45,7 @@ import static org.sonarlint.intellij.config.Settings.getGlobalSettings; import static org.sonarlint.intellij.config.Settings.getSettingsFor; import static org.sonarlint.intellij.util.ThreadUtilsKt.computeOnPooledThread; +import static org.sonarlint.intellij.util.ThreadUtilsKt.runOnPooledThread; /** * Coordinates creation of models and visual components from persisted settings. @@ -96,11 +97,13 @@ public void apply() throws ConfigurationException { var projectSettings = getSettingsFor(project); boolean exclusionsModified = panel.areExclusionsModified(projectSettings); panel.save(project, projectSettings); - project.getMessageBus().syncPublisher(ProjectConfigurationListener.TOPIC).changed(projectSettings); + runOnPooledThread(project, () -> { + project.getMessageBus().syncPublisher(ProjectConfigurationListener.TOPIC).changed(projectSettings); - if (exclusionsModified) { - getService(project, AnalysisSubmitter.class).autoAnalyzeOpenFiles(TriggerType.CONFIG_CHANGE); - } + if (exclusionsModified) { + getService(project, AnalysisSubmitter.class).autoAnalyzeOpenFiles(TriggerType.CONFIG_CHANGE); + } + }); } } diff --git a/src/main/java/org/sonarlint/intellij/core/BackendService.kt b/src/main/java/org/sonarlint/intellij/core/BackendService.kt index b20891d382..378e433195 100644 --- a/src/main/java/org/sonarlint/intellij/core/BackendService.kt +++ b/src/main/java/org/sonarlint/intellij/core/BackendService.kt @@ -179,22 +179,24 @@ class BackendService : Disposable { ApplicationManager.getApplication().messageBus.connect() .subscribe(GlobalConfigurationListener.TOPIC, object : GlobalConfigurationListener.Adapter() { override fun applied(previousSettings: SonarLintGlobalSettings, newSettings: SonarLintGlobalSettings) { - connectionsUpdated(newSettings.serverConnections) - val changedConnections = newSettings.serverConnections.filter { connection -> - val previousConnection = previousSettings.getServerConnectionByName(connection.name) - previousConnection.isPresent && !connection.hasSameCredentials(previousConnection.get()) + runOnPooledThread { + connectionsUpdated(newSettings.serverConnections) + val changedConnections = newSettings.serverConnections.filter { connection -> + val previousConnection = previousSettings.getServerConnectionByName(connection.name) + previousConnection.isPresent && !connection.hasSameCredentials(previousConnection.get()) + } + credentialsChanged(changedConnections) } - credentialsChanged(changedConnections) } override fun changed(serverList: List<ServerConnection>) { - connectionsUpdated(serverList) + runOnPooledThread { connectionsUpdated(serverList) } } }) ApplicationManager.getApplication().messageBus.connect() .subscribe(ProjectManager.TOPIC, object : ProjectManagerListener { override fun projectClosing(project: Project) { - this@BackendService.projectClosed(project) + runOnPooledThread { this@BackendService.projectClosed(project) } } }) @@ -280,9 +282,7 @@ class BackendService : Disposable { private fun handleSloopExited() { ProjectManager.getInstance().openProjects.forEach { project -> - runOnUiThread(project) { - getService(project, SonarLintToolWindow::class.java).refreshViews() - } + getService(project, SonarLintToolWindow::class.java).refreshViews() } projectLessNotification( null, @@ -505,7 +505,9 @@ class BackendService : Disposable { ) ) } - refreshTaintVulnerabilities(project) + runOnPooledThread { + refreshTaintVulnerabilities(project) + } } internal fun projectClosed(project: Project) { @@ -518,31 +520,32 @@ class BackendService : Disposable { ConfigurationScopeDto(projectId(project), null, true, project.name, BindingConfigurationDto(binding?.connectionName, binding?.projectKey, areBindingSuggestionsDisabledFor(project))) - fun projectBound(project: Project, newBinding: ProjectBinding) { - notifyBackend { - it.configurationService.didUpdateBinding( - DidUpdateBindingParams( - projectId(project), BindingConfigurationDto( - newBinding.connectionName, newBinding.projectKey, areBindingSuggestionsDisabledFor(project) - ) - ) - ) - } - newBinding.moduleBindingsOverrides.forEach { (module, projectKey) -> - val moduleId = moduleId(module) + runOnPooledThread(project) { notifyBackend { it.configurationService.didUpdateBinding( DidUpdateBindingParams( - moduleId, BindingConfigurationDto( - // we don't want binding suggestions for modules - newBinding.connectionName, projectKey, true - ) + projectId(project), BindingConfigurationDto( + newBinding.connectionName, newBinding.projectKey, areBindingSuggestionsDisabledFor(project) + ) ) ) } + newBinding.moduleBindingsOverrides.forEach { (module, projectKey) -> + val moduleId = moduleId(module) + notifyBackend { + it.configurationService.didUpdateBinding( + DidUpdateBindingParams( + moduleId, BindingConfigurationDto( + // we don't want binding suggestions for modules + newBinding.connectionName, projectKey, true + ) + ) + ) + } + } + refreshTaintVulnerabilities(project) } - refreshTaintVulnerabilities(project) } fun projectUnbound(project: Project) { @@ -553,7 +556,9 @@ class BackendService : Disposable { ) ) } - refreshTaintVulnerabilities(project) + runOnPooledThread { + refreshTaintVulnerabilities(project) + } } fun modulesAdded(project: Project, modules: List<Module>) { @@ -773,20 +778,20 @@ class BackendService : Disposable { } fun restartBackendService() { - if (isAlive()) { - return + runOnPooledThread { + if (isAlive()) { + return@runOnPooledThread + } + initializationTriedOnce.set(false) + backendFuture = CompletableFuture() + sloop = null + ensureBackendInitialized().thenAcceptAsync { catchUpWithBackend(it) } } - initializationTriedOnce.set(false) - backendFuture = CompletableFuture() - sloop = null - ensureBackendInitialized().thenAcceptAsync { catchUpWithBackend(it) } } private fun catchUpWithBackend(rpcServer: SonarLintRpcServer) { ProjectManager.getInstance().openProjects.forEach { project -> - runOnUiThread(project) { - getService(project, SonarLintToolWindow::class.java).refreshViews() - } + getService(project, SonarLintToolWindow::class.java).refreshViews() val binding = getService(project, ProjectBindingManager::class.java).binding rpcServer.configurationService.didAddConfigurationScopes( diff --git a/src/main/java/org/sonarlint/intellij/core/ProjectBindingManager.java b/src/main/java/org/sonarlint/intellij/core/ProjectBindingManager.java index 6c3b6e09e4..3db43821cd 100644 --- a/src/main/java/org/sonarlint/intellij/core/ProjectBindingManager.java +++ b/src/main/java/org/sonarlint/intellij/core/ProjectBindingManager.java @@ -158,8 +158,10 @@ public void unbind() { SonarLintProjectNotifications.Companion.get(myProject).reset(); if (previousBinding != null) { - myProject.getMessageBus().syncPublisher(ProjectBindingListenerKt.getPROJECT_BINDING_TOPIC()).bindingChanged(); - getService(BackendService.class).projectUnbound(myProject); + runOnPooledThread(myProject, () -> { + myProject.getMessageBus().syncPublisher(ProjectBindingListenerKt.getPROJECT_BINDING_TOPIC()).bindingChanged(); + getService(BackendService.class).projectUnbound(myProject); + }); } } diff --git a/src/main/java/org/sonarlint/intellij/editor/CodeAnalyzerRestarter.kt b/src/main/java/org/sonarlint/intellij/editor/CodeAnalyzerRestarter.kt index 5d0eba14aa..8c7330291c 100644 --- a/src/main/java/org/sonarlint/intellij/editor/CodeAnalyzerRestarter.kt +++ b/src/main/java/org/sonarlint/intellij/editor/CodeAnalyzerRestarter.kt @@ -20,6 +20,7 @@ package org.sonarlint.intellij.editor import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer +import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.components.Service import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.project.Project @@ -55,6 +56,7 @@ class CodeAnalyzerRestarter @NonInjectable internal constructor(private val myPr if (!virtualFile.isValid) { return null } + ApplicationManager.getApplication().assertReadAccessAllowed() return PsiManager.getInstance(myProject).findFile(virtualFile) } } diff --git a/src/main/java/org/sonarlint/intellij/editor/DisableRuleIntentionAction.java b/src/main/java/org/sonarlint/intellij/editor/DisableRuleIntentionAction.java index 90aa3c0fbc..76b2e06ba0 100644 --- a/src/main/java/org/sonarlint/intellij/editor/DisableRuleIntentionAction.java +++ b/src/main/java/org/sonarlint/intellij/editor/DisableRuleIntentionAction.java @@ -30,13 +30,13 @@ import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; import org.sonarlint.intellij.analysis.AnalysisSubmitter; -import org.sonarlint.intellij.common.util.SonarLintUtils; import org.sonarlint.intellij.core.BackendService; import org.sonarlint.intellij.trigger.TriggerType; import static org.sonarlint.intellij.common.util.SonarLintUtils.getService; import static org.sonarlint.intellij.config.Settings.getGlobalSettings; import static org.sonarlint.intellij.config.Settings.getSettingsFor; +import static org.sonarlint.intellij.util.ThreadUtilsKt.runOnPooledThread; public class DisableRuleIntentionAction implements IntentionAction, LowPriorityAction, Iconable { private final String ruleKey; @@ -60,8 +60,10 @@ public class DisableRuleIntentionAction implements IntentionAction, LowPriorityA @Override public void invoke(@NotNull Project project, Editor editor, PsiFile file) { getGlobalSettings().disableRule(ruleKey); var rulesByKey = getGlobalSettings().getRulesByKey(); - getService(BackendService.class).updateStandaloneRulesConfiguration(rulesByKey); - SonarLintUtils.getService(project, AnalysisSubmitter.class).autoAnalyzeOpenFiles(TriggerType.BINDING_UPDATE); + runOnPooledThread(project, () -> { + getService(BackendService.class).updateStandaloneRulesConfiguration(rulesByKey); + getService(project, AnalysisSubmitter.class).autoAnalyzeOpenFiles(TriggerType.BINDING_UPDATE); + }); } @Override public boolean startInWriteAction() { diff --git a/src/main/java/org/sonarlint/intellij/editor/EditorDecorator.kt b/src/main/java/org/sonarlint/intellij/editor/EditorDecorator.kt index 4722d2305a..a2b4c59b50 100644 --- a/src/main/java/org/sonarlint/intellij/editor/EditorDecorator.kt +++ b/src/main/java/org/sonarlint/intellij/editor/EditorDecorator.kt @@ -33,14 +33,15 @@ import com.intellij.openapi.editor.markup.TextAttributes import com.intellij.openapi.project.Project import com.intellij.openapi.util.Disposer import com.intellij.ui.JBColor -import java.awt.Font -import java.util.function.Consumer import org.sonarlint.intellij.common.ui.ReadActionUtils.Companion.computeReadActionSafely import org.sonarlint.intellij.config.SonarLintTextAttributes import org.sonarlint.intellij.finding.Flow import org.sonarlint.intellij.finding.LiveFinding import org.sonarlint.intellij.finding.Location import org.sonarlint.intellij.finding.issue.vulnerabilities.LocalTaintVulnerability +import org.sonarlint.intellij.ui.UiUtils.Companion.runOnUiThread +import java.awt.Font +import java.util.function.Consumer private const val HIGHLIGHT_GROUP_ID = 1001 @@ -50,12 +51,22 @@ class EditorDecorator(private val project: Project) { private var blinker: RangeBlinker? = null fun removeHighlights() { - currentHighlightedDoc.forEach { - clearSecondaryLocationNumbers(it) - UpdateHighlightersUtil.setHighlightersToEditor(project, it, 0, it.textLength, emptyList(), null, HIGHLIGHT_GROUP_ID) + runOnUiThread(project) { + currentHighlightedDoc.forEach { + clearSecondaryLocationNumbers(it) + UpdateHighlightersUtil.setHighlightersToEditor( + project, + it, + 0, + it.textLength, + emptyList(), + null, + HIGHLIGHT_GROUP_ID + ) + } + currentHighlightedDoc.clear() + stopBlinking() } - currentHighlightedDoc.clear() - stopBlinking() } private fun stopBlinking() { diff --git a/src/main/java/org/sonarlint/intellij/editor/ShowLocationsIntentionAction.java b/src/main/java/org/sonarlint/intellij/editor/ShowLocationsIntentionAction.java index e655e75385..6db2dfa5ac 100644 --- a/src/main/java/org/sonarlint/intellij/editor/ShowLocationsIntentionAction.java +++ b/src/main/java/org/sonarlint/intellij/editor/ShowLocationsIntentionAction.java @@ -34,8 +34,6 @@ import org.sonarlint.intellij.finding.FindingContext; import org.sonarlint.intellij.finding.LiveFinding; -import static org.sonarlint.intellij.ui.UiUtils.runOnUiThread; - public class ShowLocationsIntentionAction implements IntentionAction, PriorityAction, Iconable { private final LiveFinding finding; private final FindingContext context; @@ -60,7 +58,7 @@ public ShowLocationsIntentionAction(LiveFinding finding, FindingContext context) @Override public void invoke(@NotNull Project project, Editor editor, PsiFile file) { SonarLintUtils.getService(project, EditorDecorator.class).highlightFinding(finding); var sonarLintToolWindow = SonarLintUtils.getService(project, SonarLintToolWindow.class); - runOnUiThread(project, () -> sonarLintToolWindow.showFindingLocations(finding)); + sonarLintToolWindow.showFindingLocations(finding); } @Override public boolean startInWriteAction() { diff --git a/src/main/java/org/sonarlint/intellij/fix/ShowFixSuggestion.kt b/src/main/java/org/sonarlint/intellij/fix/ShowFixSuggestion.kt index 2dfe16ddc0..4e1b48d157 100644 --- a/src/main/java/org/sonarlint/intellij/fix/ShowFixSuggestion.kt +++ b/src/main/java/org/sonarlint/intellij/fix/ShowFixSuggestion.kt @@ -41,14 +41,12 @@ class ShowFixSuggestion(private val project: Project, private val file: VirtualF fun show() { val fileEditorManager = FileEditorManager.getInstance(project) - val psiFile = PsiManager.getInstance(project).findFile(file) ?: return + val psiFile = computeReadActionSafely(project) { PsiManager.getInstance(project).findFile(file) } ?: return val document = computeReadActionSafely(project) { file.getDocument() } ?: return if (!isWithinBounds(document)) { get(project).simpleNotification( - null, - "Unable to open the fix suggestion, your file has probably changed", - NotificationType.WARNING + null, "Unable to open the fix suggestion, your file has probably changed", NotificationType.WARNING ) return } diff --git a/src/main/java/org/sonarlint/intellij/module/ModuleChangeListener.kt b/src/main/java/org/sonarlint/intellij/module/ModuleChangeListener.kt index cdc2eb4e40..96849d577c 100644 --- a/src/main/java/org/sonarlint/intellij/module/ModuleChangeListener.kt +++ b/src/main/java/org/sonarlint/intellij/module/ModuleChangeListener.kt @@ -26,15 +26,16 @@ import com.intellij.util.Function import org.sonarlint.intellij.common.util.SonarLintUtils.getService import org.sonarlint.intellij.config.Settings.getSettingsFor import org.sonarlint.intellij.core.BackendService +import org.sonarlint.intellij.util.runOnPooledThread class ModuleChangeListener(val project: Project) : ModuleListener { override fun modulesAdded(project: Project, modules: List<Module>) { - getService(BackendService::class.java).modulesAdded(project, modules) + runOnPooledThread(project) { getService(BackendService::class.java).modulesAdded(project, modules) } } override fun moduleRemoved(project: Project, module: Module) { - getService(BackendService::class.java).moduleRemoved(module) + runOnPooledThread(project) { getService(BackendService::class.java).moduleRemoved(module) } } override fun modulesRenamed(project: Project, modules: MutableList<out Module>, oldNameProvider: Function<in Module, String>) { diff --git a/src/main/java/org/sonarlint/intellij/sharing/ConfigurationSharing.kt b/src/main/java/org/sonarlint/intellij/sharing/ConfigurationSharing.kt index 27d3b74b2d..103a195abc 100644 --- a/src/main/java/org/sonarlint/intellij/sharing/ConfigurationSharing.kt +++ b/src/main/java/org/sonarlint/intellij/sharing/ConfigurationSharing.kt @@ -39,6 +39,7 @@ import org.sonarlint.intellij.documentation.SonarLintDocumentation import org.sonarlint.intellij.notifications.SonarLintProjectNotifications.Companion.get import org.sonarlint.intellij.sharing.SonarLintSharedFolderUtils.Companion.findSharedFolder import org.sonarlint.intellij.ui.UiUtils.Companion.runOnUiThread +import org.sonarlint.intellij.util.runOnPooledThread import org.sonarsource.sonarlint.core.rpc.protocol.backend.binding.GetSharedConnectedModeConfigFileResponse import org.sonarsource.sonarlint.core.rpc.protocol.client.connection.ConnectionSuggestionDto @@ -50,7 +51,7 @@ class ConfigurationSharing { if (project == null || project.isDisposed) return if (confirm(project)) { - createFile(project, modalityState) + runOnPooledThread(project) { createFile(project, modalityState) } } } diff --git a/src/main/java/org/sonarlint/intellij/tasks/GetOrganizationTask.java b/src/main/java/org/sonarlint/intellij/tasks/GetOrganizationTask.java index 9e94502fad..059daab3f1 100644 --- a/src/main/java/org/sonarlint/intellij/tasks/GetOrganizationTask.java +++ b/src/main/java/org/sonarlint/intellij/tasks/GetOrganizationTask.java @@ -29,6 +29,7 @@ import org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.org.OrganizationDto; import static org.sonarlint.intellij.util.ProgressUtils.waitForFuture; +import static org.sonarlint.intellij.util.ThreadUtilsKt.computeOnPooledThread; public class GetOrganizationTask extends Task.Modal { private final ServerConnection server; @@ -50,7 +51,8 @@ public void run(@NotNull ProgressIndicator indicator) { try { indicator.setText("Searching organization"); - organization = waitForFuture(indicator, SonarLintUtils.getService(BackendService.class).getOrganization(server, organizationKey)).getOrganization(); + organization = computeOnPooledThread("Get User Organizations", () -> + waitForFuture(indicator, SonarLintUtils.getService(BackendService.class).getOrganization(server, organizationKey)).getOrganization()); } catch (Exception e) { SonarLintConsole.get(myProject).error("Failed to fetch organizations", e); exception = e; diff --git a/src/main/java/org/sonarlint/intellij/tasks/GetOrganizationsTask.java b/src/main/java/org/sonarlint/intellij/tasks/GetOrganizationsTask.java index d375335e7d..2ef2f9304a 100644 --- a/src/main/java/org/sonarlint/intellij/tasks/GetOrganizationsTask.java +++ b/src/main/java/org/sonarlint/intellij/tasks/GetOrganizationsTask.java @@ -30,6 +30,7 @@ import org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.org.OrganizationDto; import static org.sonarlint.intellij.util.ProgressUtils.waitForFuture; +import static org.sonarlint.intellij.util.ThreadUtilsKt.computeOnPooledThread; /** * Only useful for SonarCloud @@ -50,7 +51,8 @@ public void run(@NotNull ProgressIndicator indicator) { indicator.setIndeterminate(false); try { - organizations = waitForFuture(indicator, SonarLintUtils.getService(BackendService.class).listUserOrganizations(connection)).getUserOrganizations(); + organizations = computeOnPooledThread("Get User Organizations",() -> + waitForFuture(indicator, SonarLintUtils.getService(BackendService.class).listUserOrganizations(connection)).getUserOrganizations()); } catch (Exception e) { SonarLintConsole.get(myProject).error("Failed to fetch organizations", e); exception = e; diff --git a/src/main/java/org/sonarlint/intellij/telemetry/SonarLintTelemetryImpl.kt b/src/main/java/org/sonarlint/intellij/telemetry/SonarLintTelemetryImpl.kt index d42de07493..2b1a8044c7 100644 --- a/src/main/java/org/sonarlint/intellij/telemetry/SonarLintTelemetryImpl.kt +++ b/src/main/java/org/sonarlint/intellij/telemetry/SonarLintTelemetryImpl.kt @@ -22,6 +22,7 @@ package org.sonarlint.intellij.telemetry import java.util.concurrent.CompletableFuture import org.sonarlint.intellij.common.util.SonarLintUtils.getService import org.sonarlint.intellij.core.BackendService +import org.sonarlint.intellij.util.runOnPooledThread import org.sonarsource.sonarlint.core.rpc.protocol.backend.telemetry.TelemetryRpcService import org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.AddQuickFixAppliedForRuleParams import org.sonarsource.sonarlint.core.rpc.protocol.client.telemetry.DevNotificationsClickedParams @@ -80,7 +81,7 @@ class SonarLintTelemetryImpl : SonarLintTelemetry { companion object { private fun notifyTelemetry(action: (TelemetryRpcService) -> Unit) { - getService(BackendService::class.java).notifyTelemetry(action) + runOnPooledThread { getService(BackendService::class.java).notifyTelemetry(action) } } } } diff --git a/src/main/java/org/sonarlint/intellij/trigger/BuildFinishedAnalysisTrigger.java b/src/main/java/org/sonarlint/intellij/trigger/BuildFinishedAnalysisTrigger.java index 2916dbc937..90435f0564 100644 --- a/src/main/java/org/sonarlint/intellij/trigger/BuildFinishedAnalysisTrigger.java +++ b/src/main/java/org/sonarlint/intellij/trigger/BuildFinishedAnalysisTrigger.java @@ -24,7 +24,9 @@ import java.util.UUID; import org.sonarlint.intellij.analysis.AnalysisSubmitter; import org.sonarlint.intellij.common.ui.SonarLintConsole; -import org.sonarlint.intellij.common.util.SonarLintUtils; + +import static org.sonarlint.intellij.common.util.SonarLintUtils.getService; +import static org.sonarlint.intellij.util.ThreadUtilsKt.runOnPooledThread; public class BuildFinishedAnalysisTrigger implements BuildManagerListener { @@ -34,7 +36,9 @@ public class BuildFinishedAnalysisTrigger implements BuildManagerListener { return; } - SonarLintUtils.getService(project, SonarLintConsole.class).debug("build finished"); - SonarLintUtils.getService(project, AnalysisSubmitter.class).autoAnalyzeOpenFiles(TriggerType.COMPILATION); + runOnPooledThread(project, () -> { + getService(project, SonarLintConsole.class).debug("build finished"); + getService(project, AnalysisSubmitter.class).autoAnalyzeOpenFiles(TriggerType.COMPILATION); + }); } } diff --git a/src/main/java/org/sonarlint/intellij/trigger/CompilationFinishedAnalysisTrigger.java b/src/main/java/org/sonarlint/intellij/trigger/CompilationFinishedAnalysisTrigger.java index 10dcd57b18..4eb0ff2787 100644 --- a/src/main/java/org/sonarlint/intellij/trigger/CompilationFinishedAnalysisTrigger.java +++ b/src/main/java/org/sonarlint/intellij/trigger/CompilationFinishedAnalysisTrigger.java @@ -23,7 +23,9 @@ import com.intellij.openapi.compiler.CompileContext; import org.sonarlint.intellij.analysis.AnalysisSubmitter; import org.sonarlint.intellij.common.ui.SonarLintConsole; -import org.sonarlint.intellij.common.util.SonarLintUtils; + +import static org.sonarlint.intellij.common.util.SonarLintUtils.getService; +import static org.sonarlint.intellij.util.ThreadUtilsKt.runOnPooledThread; public class CompilationFinishedAnalysisTrigger implements CompilationStatusListener { @@ -34,7 +36,7 @@ public class CompilationFinishedAnalysisTrigger implements CompilationStatusList @Override public void compilationFinished(boolean aborted, int errors, int warnings, CompileContext compileContext) { var compiledProject = compileContext.getProject(); - SonarLintUtils.getService(compiledProject, SonarLintConsole.class).debug("compilation finished"); - SonarLintUtils.getService(compiledProject, AnalysisSubmitter.class).autoAnalyzeOpenFiles(TriggerType.COMPILATION); + getService(compiledProject, SonarLintConsole.class).debug("compilation finished"); + runOnPooledThread(compiledProject, () -> getService(compiledProject, AnalysisSubmitter.class).autoAnalyzeOpenFiles(TriggerType.COMPILATION)); } } diff --git a/src/main/java/org/sonarlint/intellij/trigger/EditorChangeTrigger.java b/src/main/java/org/sonarlint/intellij/trigger/EditorChangeTrigger.java index c7e34f95de..4364f2535d 100644 --- a/src/main/java/org/sonarlint/intellij/trigger/EditorChangeTrigger.java +++ b/src/main/java/org/sonarlint/intellij/trigger/EditorChangeTrigger.java @@ -36,11 +36,12 @@ import javax.annotation.concurrent.ThreadSafe; import org.sonarlint.intellij.analysis.AnalysisSubmitter; import org.sonarlint.intellij.analysis.Cancelable; -import org.sonarlint.intellij.common.util.SonarLintUtils; import org.sonarlint.intellij.messages.AnalysisListener; import org.sonarlint.intellij.util.SonarLintAppUtils; +import static org.sonarlint.intellij.common.util.SonarLintUtils.getService; import static org.sonarlint.intellij.config.Settings.getGlobalSettings; +import static org.sonarlint.intellij.util.ThreadUtilsKt.runOnPooledThread; @ThreadSafe @Service(Service.Level.PROJECT) @@ -141,7 +142,7 @@ private void triggerFiles(List<VirtualFile> files) { return; } files.forEach(eventMap::remove); - task = SonarLintUtils.getService(myProject, AnalysisSubmitter.class).autoAnalyzeFiles(openFilesToAnalyze, TriggerType.EDITOR_CHANGE); + task = getService(myProject, AnalysisSubmitter.class).autoAnalyzeFiles(openFilesToAnalyze, TriggerType.EDITOR_CHANGE); } } } @@ -164,7 +165,7 @@ private void checkTimers() { filesToTrigger.add(event.getKey()); } } - triggerFiles(filesToTrigger); + runOnPooledThread(myProject, () -> triggerFiles(filesToTrigger)); } } diff --git a/src/main/java/org/sonarlint/intellij/trigger/EditorOpenTrigger.java b/src/main/java/org/sonarlint/intellij/trigger/EditorOpenTrigger.java index d103e54379..da72314c00 100644 --- a/src/main/java/org/sonarlint/intellij/trigger/EditorOpenTrigger.java +++ b/src/main/java/org/sonarlint/intellij/trigger/EditorOpenTrigger.java @@ -26,11 +26,12 @@ import org.sonarlint.intellij.analysis.AnalysisSubmitter; import static org.sonarlint.intellij.common.util.SonarLintUtils.getService; +import static org.sonarlint.intellij.util.ThreadUtilsKt.runOnPooledThread; public class EditorOpenTrigger implements FileEditorManagerListener { @Override public void fileOpened(@NotNull FileEditorManager source, @NotNull VirtualFile file) { - getService(source.getProject(), AnalysisSubmitter.class).autoAnalyzeFile(file, TriggerType.EDITOR_OPEN); + runOnPooledThread(source.getProject(), () -> getService(source.getProject(), AnalysisSubmitter.class).autoAnalyzeFile(file, TriggerType.EDITOR_OPEN)); } } diff --git a/src/main/java/org/sonarlint/intellij/trigger/SonarLintCheckinHandler.java b/src/main/java/org/sonarlint/intellij/trigger/SonarLintCheckinHandler.java index 4d3ee22446..2050c477df 100644 --- a/src/main/java/org/sonarlint/intellij/trigger/SonarLintCheckinHandler.java +++ b/src/main/java/org/sonarlint/intellij/trigger/SonarLintCheckinHandler.java @@ -20,6 +20,7 @@ package org.sonarlint.intellij.trigger; import com.intellij.ide.util.PropertiesComponent; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.options.UnnamedConfigurable; import com.intellij.openapi.progress.ProgressIndicator; @@ -64,6 +65,7 @@ import static org.sonarlint.intellij.common.util.SonarLintUtils.getService; import static org.sonarlint.intellij.config.Settings.getGlobalSettings; +import static org.sonarlint.intellij.ui.UiUtils.runOnUiThread; public class SonarLintCheckinHandler extends CheckinHandler { private static final Logger LOGGER = Logger.getInstance(SonarLintCheckinHandler.class); @@ -95,7 +97,7 @@ public ReturnResult beforeCheckin(@Nullable CommitExecutor executor, PairConsume var affectedFiles = new HashSet<>(checkinPanel.getVirtualFiles()); // this will block EDT (modal) try { - var analysisIdsByCallback = SonarLintUtils.getService(project, AnalysisSubmitter.class).analyzeFilesPreCommit(affectedFiles); + var analysisIdsByCallback = getService(project, AnalysisSubmitter.class).analyzeFilesPreCommit(affectedFiles); if (analysisIdsByCallback == null) { return ReturnResult.CANCEL; } @@ -240,7 +242,11 @@ private ReturnResult showYesNoCancel(String resultStr) { } private void showChangedFilesTab(AnalysisResult analysisResult) { - SonarLintUtils.getService(project, SonarLintToolWindow.class).openReportTab(analysisResult); + if (ApplicationManager.getApplication().isUnitTestMode()) { + getService(project, SonarLintToolWindow.class).openReportTab(analysisResult); + } else { + runOnUiThread(project, () -> getService(project, SonarLintToolWindow.class).openReportTab(analysisResult)); + } } private class MyRefreshableOnComponent implements RefreshableOnComponent, UnnamedConfigurable { diff --git a/src/main/java/org/sonarlint/intellij/ui/AbstractIssuesPanel.java b/src/main/java/org/sonarlint/intellij/ui/AbstractIssuesPanel.java index 964d2cc86d..2f769bdccf 100644 --- a/src/main/java/org/sonarlint/intellij/ui/AbstractIssuesPanel.java +++ b/src/main/java/org/sonarlint/intellij/ui/AbstractIssuesPanel.java @@ -66,7 +66,7 @@ public abstract class AbstractIssuesPanel extends SimpleToolWindowPanel implemen protected FindingDetailsPanel findingDetailsPanel; protected AbstractIssuesPanel(Project project) { - super(false, true); + super(false, false); this.project = project; createIssuesTree(); @@ -242,8 +242,8 @@ public <T extends Finding> void updateOnSelect(@Nullable LiveFinding issue, Show return; } - runOnUiThread(project, () -> - findingDetailsPanel.showServerOnlyIssue(showFinding.getModule(), showFinding.getFile(), showFinding.getRuleKey(), rangeMarker, showFinding.getFlows(), + runOnUiThread(project, + () -> findingDetailsPanel.showServerOnlyIssue(showFinding.getModule(), showFinding.getFile(), showFinding.getRuleKey(), rangeMarker, showFinding.getFlows(), showFinding.getFlowMessage())); }); } diff --git a/src/main/java/org/sonarlint/intellij/ui/AutoTriggerStatusPanel.java b/src/main/java/org/sonarlint/intellij/ui/AutoTriggerStatusPanel.java index 2834c7bed3..98c4914a86 100644 --- a/src/main/java/org/sonarlint/intellij/ui/AutoTriggerStatusPanel.java +++ b/src/main/java/org/sonarlint/intellij/ui/AutoTriggerStatusPanel.java @@ -72,8 +72,8 @@ public class AutoTriggerStatusPanel { public AutoTriggerStatusPanel(Project project) { this.project = project; createPanel(); - switchCards(); - CurrentFileStatusPanel.subscribeToEventsThatAffectCurrentFile(project, this::switchCards); + runOnUiThread(project, this::switchCards); + CurrentFileStatusPanel.subscribeToEventsThatAffectCurrentFile(project, () -> runOnUiThread(project, this::switchCards)); } public JPanel getPanel() { diff --git a/src/main/java/org/sonarlint/intellij/ui/CurrentFileConnectedModePanel.java b/src/main/java/org/sonarlint/intellij/ui/CurrentFileConnectedModePanel.java index a5aa214521..c0ef36a8d4 100644 --- a/src/main/java/org/sonarlint/intellij/ui/CurrentFileConnectedModePanel.java +++ b/src/main/java/org/sonarlint/intellij/ui/CurrentFileConnectedModePanel.java @@ -21,7 +21,6 @@ import com.intellij.ide.IdeTooltipManager; import com.intellij.openapi.actionSystem.ActionManager; -import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; import com.intellij.openapi.wm.ToolWindowManager; @@ -158,32 +157,32 @@ private <T> T illegalStateIfNull(@Nullable T checkForNull, String messageIfNull) } private void switchCards() { - ApplicationManager.getApplication().assertIsDispatchThread(); - - var selectedFile = SonarLintUtils.getSelectedFile(project); - if (selectedFile != null) { - // Checking connected mode state may take time, so lets move from EDT to pooled thread - runOnPooledThread(project, () -> { - var projectBindingManager = getService(project, ProjectBindingManager.class); - projectBindingManager.tryGetServerConnection().ifPresentOrElse(serverConnection -> { - try { - var module = SonarLintAppUtils.findModuleForFile(selectedFile, project); - if (module == null) { - switchCard(EMPTY); - } else { - connectedCard.updateTooltip(module, serverConnection); - switchCard(CONNECTED); + runOnUiThread(project, () -> { + var selectedFile = SonarLintUtils.getSelectedFile(project); + if (selectedFile != null) { + // Checking connected mode state may take time, so lets move from EDT to pooled thread + runOnPooledThread(project, () -> { + var projectBindingManager = getService(project, ProjectBindingManager.class); + projectBindingManager.tryGetServerConnection().ifPresentOrElse(serverConnection -> { + try { + var module = SonarLintAppUtils.findModuleForFile(selectedFile, project); + if (module == null) { + switchCard(EMPTY); + } else { + connectedCard.updateTooltip(module, serverConnection); + switchCard(CONNECTED); + } + } catch (IllegalStateException e) { + switchCard(ERROR); } - } catch (IllegalStateException e) { - switchCard(ERROR); - } - }, - // No connection settings for project - () -> switchCard(NOT_CONNECTED)); - }); - } else { - switchCard(EMPTY); - } + }, + // No connection settings for project + () -> switchCard(NOT_CONNECTED)); + }); + } else { + switchCard(EMPTY); + } + }); } private void updateBranchTooltip() { diff --git a/src/main/java/org/sonarlint/intellij/ui/CurrentFilePanel.java b/src/main/java/org/sonarlint/intellij/ui/CurrentFilePanel.java index 77fb2b7773..d1ff95ed74 100644 --- a/src/main/java/org/sonarlint/intellij/ui/CurrentFilePanel.java +++ b/src/main/java/org/sonarlint/intellij/ui/CurrentFilePanel.java @@ -23,6 +23,7 @@ import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.CommonDataKeys; import com.intellij.openapi.actionSystem.ex.ActionUtil; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.VerticalFlowLayout; import com.intellij.openapi.vfs.VirtualFile; @@ -84,7 +85,7 @@ public CurrentFilePanel(Project project) { treePanel.add(tree); treePanel.add(oldTree); - treeScrollPane = ScrollPaneFactory.createScrollPane(treePanel); + treeScrollPane = ScrollPaneFactory.createScrollPane(treePanel, true); issuesPanel = new JBPanelWithEmptyText(new BorderLayout()); var statusText = issuesPanel.getEmptyText(); @@ -204,6 +205,7 @@ private void updateIcon(@Nullable VirtualFile file, Collection<LiveIssue> issues } private static void doUpdateIcon(@Nullable VirtualFile file, Collection<LiveIssue> issues, ToolWindow toolWindow) { + ApplicationManager.getApplication().assertIsDispatchThread(); boolean empty = file == null || issues.isEmpty(); toolWindow.setIcon(empty ? SonarLintIcons.SONARLINT_TOOLWINDOW_EMPTY : SonarLintIcons.SONARLINT_TOOLWINDOW); } @@ -222,17 +224,17 @@ public Object getData(@NonNls String dataId) { if (CommonDataKeys.VIRTUAL_FILE.is(dataId)) { return SonarLintUtils.getSelectedFile(project); } - + return null; } public void refreshModel() { treeBuilder.refreshModel(project); oldTreeBuilder.refreshModel(project); - expandTree(); + runOnUiThread(project, this::expandTree); } public void refreshView() { - update(currentFile, currentIssues); + runOnUiThread(project, () -> update(currentFile, currentIssues)); } } diff --git a/src/main/java/org/sonarlint/intellij/ui/ReportPanel.java b/src/main/java/org/sonarlint/intellij/ui/ReportPanel.java index 11fa7accc7..dcc7f0d82d 100644 --- a/src/main/java/org/sonarlint/intellij/ui/ReportPanel.java +++ b/src/main/java/org/sonarlint/intellij/ui/ReportPanel.java @@ -72,6 +72,7 @@ import static org.sonarlint.intellij.common.util.SonarLintUtils.getService; import static org.sonarlint.intellij.ui.SonarLintToolWindowFactory.createSplitter; import static org.sonarlint.intellij.ui.UiUtils.runOnUiThread; +import static org.sonarlint.intellij.util.ThreadUtilsKt.runOnPooledThread; public class ReportPanel extends SimpleToolWindowPanel implements Disposable { private static final String SPLIT_PROPORTION_PROPERTY = "SONARLINT_ANALYSIS_RESULTS_SPLIT_PROPORTION"; @@ -95,7 +96,7 @@ public class ReportPanel extends SimpleToolWindowPanel implements Disposable { private JBPanelWithEmptyText findingsPanel; public ReportPanel(Project project) { - super(false, true); + super(false, false); this.project = project; lastAnalysisPanel = new LastAnalysisPanel(); whatsNewPanel = new ReportTabStatusPanel(project); @@ -143,15 +144,19 @@ public void updateFindings(AnalysisResult analysisResult) { .filter(e -> !e.getValue().isEmpty()) .collect(Collectors.toMap(Map.Entry::getKey, e -> (Collection<LiveIssue>) e.getValue())); - treeBuilder.updateModel(newIssues); - oldTreeBuilder.updateModel(oldIssues); - securityHotspotTreeBuilder.updateModel(newHotspots); - oldSecurityHotspotTreeBuilder.updateModel(oldHotspots); + runOnUiThread(project, () -> { + treeBuilder.updateModel(newIssues); + oldTreeBuilder.updateModel(oldIssues); + securityHotspotTreeBuilder.updateModel(newHotspots); + oldSecurityHotspotTreeBuilder.updateModel(oldHotspots); + }); } else { - securityHotspotTreeBuilder.updateModel(findings.getSecurityHotspotsPerFile()); - oldSecurityHotspotTreeBuilder.updateModel(Collections.emptyMap()); - treeBuilder.updateModel(findings.getIssuesPerFile()); - oldTreeBuilder.updateModel(Collections.emptyMap()); + runOnUiThread(project, () -> { + securityHotspotTreeBuilder.updateModel(findings.getSecurityHotspotsPerFile()); + oldSecurityHotspotTreeBuilder.updateModel(Collections.emptyMap()); + treeBuilder.updateModel(findings.getIssuesPerFile()); + oldTreeBuilder.updateModel(Collections.emptyMap()); + }); } disableEmptyDisplay(true); @@ -184,7 +189,7 @@ private void initPanel() { treePanel.add(securityHotspotTree); treePanel.add(oldTree); treePanel.add(oldSecurityHotspotTree); - findingsTreePane = ScrollPaneFactory.createScrollPane(treePanel); + findingsTreePane = ScrollPaneFactory.createScrollPane(treePanel, true); findingsPanel.add(findingsTreePane, BorderLayout.CENTER); findingsPanel.add(lastAnalysisPanel, BorderLayout.SOUTH); whatsNewPanel.add(lastAnalysisPanel, BorderLayout.WEST); @@ -229,14 +234,16 @@ private void refreshToolbar() { } private void setTrees(boolean isFocusOnNewCode) { - tree.setShowsRootHandles(true); - oldTree.setShowsRootHandles(true); - securityHotspotTree.setShowsRootHandles(true); - oldSecurityHotspotTree.setShowsRootHandles(true); - tree.setVisible(true); - oldTree.setVisible(isFocusOnNewCode); - securityHotspotTree.setVisible(true); - oldSecurityHotspotTree.setVisible(isFocusOnNewCode); + runOnUiThread(project, () -> { + tree.setShowsRootHandles(true); + oldTree.setShowsRootHandles(true); + securityHotspotTree.setShowsRootHandles(true); + oldSecurityHotspotTree.setShowsRootHandles(true); + tree.setVisible(true); + oldTree.setVisible(isFocusOnNewCode); + securityHotspotTree.setVisible(true); + oldSecurityHotspotTree.setVisible(isFocusOnNewCode); + }); } private void issueTreeSelectionChanged(TreeSelectionEvent e) { @@ -408,22 +415,24 @@ public void clear() { } private void expandTree() { - if (treeBuilder.numberIssues() < 30) { - TreeUtil.expandAll(tree); - } else { - tree.expandRow(0); - } + runOnUiThread(project, () -> { + if (treeBuilder.numberIssues() < 30) { + TreeUtil.expandAll(tree); + } else { + tree.expandRow(0); + } - if (securityHotspotTreeBuilder.numberHotspots() < 30) { - TreeUtil.expandAll(securityHotspotTree); - } else { - securityHotspotTree.expandRow(0); - } + if (securityHotspotTreeBuilder.numberHotspots() < 30) { + TreeUtil.expandAll(securityHotspotTree); + } else { + securityHotspotTree.expandRow(0); + } + }); } private void disableEmptyDisplay(Boolean state) { if (Boolean.FALSE.equals(state)) { - handleEmptyView(); + runOnPooledThread(project, this::handleEmptyView); } findingsTreePane.setVisible(state); lastAnalysisPanel.setVisible(state); diff --git a/src/main/java/org/sonarlint/intellij/ui/SecurityHotspotsPanel.java b/src/main/java/org/sonarlint/intellij/ui/SecurityHotspotsPanel.java index b05b293335..8e5b89dcfa 100644 --- a/src/main/java/org/sonarlint/intellij/ui/SecurityHotspotsPanel.java +++ b/src/main/java/org/sonarlint/intellij/ui/SecurityHotspotsPanel.java @@ -101,7 +101,7 @@ public class SecurityHotspotsPanel extends SimpleToolWindowPanel implements Disp private FindingDetailsPanel findingDetailsPanel; public SecurityHotspotsPanel(Project project) { - super(false, true); + super(false, false); this.project = project; securityHotspotCount = 0; oldSecurityHotspotCount = 0; @@ -124,7 +124,7 @@ private void initPanel() { findingDetailsPanel.setMinimumSize(new Dimension(350, 200)); var findingsPanel = new JPanel(new BorderLayout()); findingsPanel.add(createSplitter(project, this, this, - ScrollPaneFactory.createScrollPane(treePanel), findingDetailsPanel, SPLIT_PROPORTION_PROPERTY, 0.5f)); + ScrollPaneFactory.createScrollPane(treePanel, true), findingDetailsPanel, SPLIT_PROPORTION_PROPERTY, 0.5f)); sonarConfigureProject = new SonarConfigureProject(); notSupportedPanel = centeredLabel("Security Hotspots are currently not supported", "Configure Binding", sonarConfigureProject); diff --git a/src/main/java/org/sonarlint/intellij/ui/SonarLintLogPanel.java b/src/main/java/org/sonarlint/intellij/ui/SonarLintLogPanel.java index 356cfffa9b..0289547364 100644 --- a/src/main/java/org/sonarlint/intellij/ui/SonarLintLogPanel.java +++ b/src/main/java/org/sonarlint/intellij/ui/SonarLintLogPanel.java @@ -28,7 +28,6 @@ import com.intellij.openapi.ui.SimpleToolWindowPanel; import com.intellij.openapi.wm.ToolWindow; import com.intellij.openapi.wm.ex.ToolWindowEx; -import com.intellij.util.messages.MessageBusConnection; import javax.swing.Box; import org.sonarlint.intellij.actions.ToolWindowLogAnalysisAction; import org.sonarlint.intellij.actions.ToolWindowVerboseModeAction; @@ -48,7 +47,7 @@ public class SonarLintLogPanel extends SimpleToolWindowPanel { private ActionToolbar mainToolbar; public SonarLintLogPanel(ToolWindow toolWindow, Project project) { - super(false, true); + super(false, false); this.toolWindow = toolWindow; this.project = project; @@ -57,7 +56,7 @@ public SonarLintLogPanel(ToolWindow toolWindow, Project project) { addConsole(); project.getMessageBus().connect().subscribe(StatusListener.SONARLINT_STATUS_TOPIC, newStatus -> - runOnUiThread(project, mainToolbar::updateActionsImmediately)); + runOnUiThread(project, mainToolbar::updateActionsImmediately)); } private void addToolbar() { diff --git a/src/main/java/org/sonarlint/intellij/ui/SonarLintRulePanel.kt b/src/main/java/org/sonarlint/intellij/ui/SonarLintRulePanel.kt index 6cb10ceeb3..c9056f46c2 100644 --- a/src/main/java/org/sonarlint/intellij/ui/SonarLintRulePanel.kt +++ b/src/main/java/org/sonarlint/intellij/ui/SonarLintRulePanel.kt @@ -63,6 +63,7 @@ import org.sonarlint.intellij.ui.UiUtils.Companion.runOnUiThread import org.sonarlint.intellij.ui.ruledescription.RuleDescriptionPanel import org.sonarlint.intellij.ui.ruledescription.RuleHeaderPanel import org.sonarlint.intellij.ui.ruledescription.RuleLanguages +import org.sonarlint.intellij.util.runOnPooledThread import org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.EffectiveRuleDetailsDto @@ -115,7 +116,9 @@ class SonarLintRulePanel(private val project: Project, parent: Disposable) : JBL clear() ApplicationManager.getApplication().messageBus.connect(parent) - .subscribe(LafManagerListener.TOPIC, LafManagerListener { updateUiComponents() }) + .subscribe( + LafManagerListener.TOPIC, + LafManagerListener { runOnUiThread(project) { updateUiComponents() } }) } private data class RuleDetailsLoaderState(val lastModule: Module?, val lastFindingRuleKey: String?, val lastContextKey: String?) @@ -145,21 +148,23 @@ class SonarLintRulePanel(private val project: Project, parent: Disposable) : JBL startLoading() ProgressManager.getInstance().run(object : Task.Backgroundable(project, "Loading rule description\u2026", false) { override fun run(progressIndicator: ProgressIndicator) { - SonarLintUtils.getService(BackendService::class.java) - .getActiveRuleDetails(module, ruleKey, ruleDescriptionContextKey) - .orTimeout(30, TimeUnit.SECONDS) - .handle { response, error -> - stopLoading() - ruleDetails = if (error != null) { - SonarLintConsole.get(project).error("Cannot get rule description", error) - null - } else { - response.details() + runOnPooledThread(project) { + SonarLintUtils.getService(BackendService::class.java) + .getActiveRuleDetails(module, ruleKey, ruleDescriptionContextKey) + .orTimeout(30, TimeUnit.SECONDS) + .handle { response, error -> + stopLoading() + ruleDetails = if (error != null) { + SonarLintConsole.get(project).error("Cannot get rule description", error) + null + } else { + response.details() + } + runOnUiThread(project) { + updateUiComponents() + } } - runOnUiThread(project) { - updateUiComponents() - } - } + } } }) } @@ -168,7 +173,7 @@ class SonarLintRulePanel(private val project: Project, parent: Disposable) : JBL fun clear() { clearValues() - updateUiComponents() + runOnUiThread(project) { updateUiComponents() } } private fun clearValues() { diff --git a/src/main/java/org/sonarlint/intellij/ui/SonarLintToolWindowFactory.java b/src/main/java/org/sonarlint/intellij/ui/SonarLintToolWindowFactory.java index c77956d7d8..ec39b31f61 100644 --- a/src/main/java/org/sonarlint/intellij/ui/SonarLintToolWindowFactory.java +++ b/src/main/java/org/sonarlint/intellij/ui/SonarLintToolWindowFactory.java @@ -39,6 +39,7 @@ import static org.sonarlint.intellij.actions.SonarLintToolWindow.buildTabName; import static org.sonarlint.intellij.common.util.SonarLintUtils.getService; +import static org.sonarlint.intellij.ui.UiUtils.runOnUiThread; /** * Factory of SonarLint tool window. @@ -54,17 +55,19 @@ public class SonarLintToolWindowFactory implements ToolWindowFactory { @Override public void createToolWindowContent(Project project, final ToolWindow toolWindow) { - var contentManager = toolWindow.getContentManager(); - addCurrentFileTab(project, contentManager); - addReportTab(project, contentManager); - var sonarLintToolWindow = getService(project, SonarLintToolWindow.class); - addSecurityHotspotsTab(project, contentManager); - if (SonarLintUtils.isTaintVulnerabilitiesEnabled()) { - addTaintVulnerabilitiesTab(project, contentManager); - } - addLogTab(project, toolWindow); - toolWindow.setType(ToolWindowType.DOCKED, null); - contentManager.addContentManagerListener(sonarLintToolWindow); + runOnUiThread(project, () -> { + var contentManager = toolWindow.getContentManager(); + addCurrentFileTab(project, contentManager); + addReportTab(project, contentManager); + var sonarLintToolWindow = getService(project, SonarLintToolWindow.class); + addSecurityHotspotsTab(project, contentManager); + if (SonarLintUtils.isTaintVulnerabilitiesEnabled()) { + addTaintVulnerabilitiesTab(project, contentManager); + } + addLogTab(project, toolWindow); + toolWindow.setType(ToolWindowType.DOCKED, null); + contentManager.addContentManagerListener(sonarLintToolWindow); + }); } public static JBSplitter createSplitter(Project project, JComponent parentComponent, Disposable parentDisposable, JComponent c1, JComponent c2, String proportionProperty, diff --git a/src/main/java/org/sonarlint/intellij/ui/UiUtils.kt b/src/main/java/org/sonarlint/intellij/ui/UiUtils.kt index af22e8258e..38752c5498 100644 --- a/src/main/java/org/sonarlint/intellij/ui/UiUtils.kt +++ b/src/main/java/org/sonarlint/intellij/ui/UiUtils.kt @@ -26,7 +26,7 @@ import com.intellij.openapi.project.Project class UiUtils { companion object { @JvmStatic - fun runOnUiThread(runnable: Runnable, modality: ModalityState) { + fun runOnUiThread(modality: ModalityState, runnable: Runnable) { ApplicationManager.getApplication().invokeLater(runnable, modality) } diff --git a/src/main/java/org/sonarlint/intellij/ui/WhatsInThisViewPanel.kt b/src/main/java/org/sonarlint/intellij/ui/WhatsInThisViewPanel.kt index d233965d74..e5872e7a62 100644 --- a/src/main/java/org/sonarlint/intellij/ui/WhatsInThisViewPanel.kt +++ b/src/main/java/org/sonarlint/intellij/ui/WhatsInThisViewPanel.kt @@ -19,7 +19,6 @@ */ package org.sonarlint.intellij.ui -import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.project.Project import com.intellij.util.ui.JBUI import java.awt.CardLayout @@ -43,7 +42,7 @@ class WhatsInThisViewPanel(val project: Project, private var helpText: String) { panel = JPanel(layout) createPanel() switchCards() - runOnUiThread(project) { subscribeToEventsThatAffectCurrentFile(project) { this.switchCards() } } + subscribeToEventsThatAffectCurrentFile(project) { this.switchCards() } } private fun createPanel() { @@ -71,20 +70,14 @@ class WhatsInThisViewPanel(val project: Project, private var helpText: String) { } private fun switchCards() { - ApplicationManager.getApplication().assertIsDispatchThread() - // Checking connected mode state may take time, so lets move from EDT to pooled thread runOnPooledThread(project) { - val projectBindingManager = - SonarLintUtils.getService(project, ProjectBindingManager::class.java) - projectBindingManager.tryGetServerConnection().ifPresentOrElse( - { - switchCard(CONNECTED) - } - ) // No connection settings for project + val projectBindingManager = SonarLintUtils.getService(project, ProjectBindingManager::class.java) + projectBindingManager.tryGetServerConnection().ifPresentOrElse({ + switchCard(CONNECTED) + }) // No connection settings for project { switchCard(NOT_CONNECTED) } } - } private fun switchCard(cardName: String) { diff --git a/src/main/java/org/sonarlint/intellij/ui/review/ReviewSecurityHotspotDialog.kt b/src/main/java/org/sonarlint/intellij/ui/review/ReviewSecurityHotspotDialog.kt index 599dc21a88..52cb991deb 100644 --- a/src/main/java/org/sonarlint/intellij/ui/review/ReviewSecurityHotspotDialog.kt +++ b/src/main/java/org/sonarlint/intellij/ui/review/ReviewSecurityHotspotDialog.kt @@ -34,6 +34,7 @@ import org.sonarlint.intellij.core.BackendService import org.sonarlint.intellij.documentation.SonarLintDocumentation.Intellij.SECURITY_HOTSPOTS_LINK import org.sonarlint.intellij.notifications.SonarLintProjectNotifications import org.sonarlint.intellij.ui.UiUtils.Companion.runOnUiThread +import org.sonarlint.intellij.util.runOnPooledThread import org.sonarsource.sonarlint.core.rpc.protocol.backend.hotspot.CheckStatusChangePermittedResponse import org.sonarsource.sonarlint.core.rpc.protocol.backend.hotspot.HotspotStatus @@ -62,7 +63,9 @@ class ReviewSecurityHotspotDialog( override fun doAction(e: ActionEvent) { val status = getStatus() - changeStatus(status, ModalityState.stateForComponent(contentPane)) + runOnPooledThread(project) { + changeStatus(status, ModalityState.stateForComponent(contentPane)) + } } private fun changeStatus(status: HotspotStatus, modalityState: ModalityState) { diff --git a/src/main/java/org/sonarlint/intellij/ui/traffic/light/SonarLintDashboardPanel.kt b/src/main/java/org/sonarlint/intellij/ui/traffic/light/SonarLintDashboardPanel.kt index 158a92d279..ada1959221 100644 --- a/src/main/java/org/sonarlint/intellij/ui/traffic/light/SonarLintDashboardPanel.kt +++ b/src/main/java/org/sonarlint/intellij/ui/traffic/light/SonarLintDashboardPanel.kt @@ -53,6 +53,7 @@ import org.sonarlint.intellij.finding.FindingType.ISSUE import org.sonarlint.intellij.finding.FindingType.SECURITY_HOTSPOT import org.sonarlint.intellij.finding.FindingType.TAINT_VULNERABILITY import org.sonarlint.intellij.util.HelpLabelUtils +import org.sonarlint.intellij.util.runOnPooledThread class SonarLintDashboardPanel(private val editor: Editor) { @@ -79,9 +80,11 @@ class SonarLintDashboardPanel(private val editor: Editor) { private val restartPanel: JPanel init { - editor.project?.let { refreshCheckbox() } - focusOnNewCodeCheckbox.addActionListener { - getService(CleanAsYouCodeService::class.java).setFocusOnNewCode(focusOnNewCodeCheckbox.isSelected) + editor.project?.let { project -> + refreshCheckbox() + focusOnNewCodeCheckbox.addActionListener { + runOnPooledThread(project) { getService(CleanAsYouCodeService::class.java).setFocusOnNewCode(focusOnNewCodeCheckbox.isSelected) } + } } focusOnNewCodeCheckbox.isOpaque = false diff --git a/src/main/java/org/sonarlint/intellij/ui/tree/FlowsTree.java b/src/main/java/org/sonarlint/intellij/ui/tree/FlowsTree.java index 6c95254831..68e1d8d88d 100644 --- a/src/main/java/org/sonarlint/intellij/ui/tree/FlowsTree.java +++ b/src/main/java/org/sonarlint/intellij/ui/tree/FlowsTree.java @@ -40,6 +40,8 @@ import org.sonarlint.intellij.ui.nodes.FlowSecondaryLocationNode; import org.sonarlint.intellij.ui.nodes.PrimaryLocationNode; +import static org.sonarlint.intellij.ui.UiUtils.runOnUiThread; + public class FlowsTree extends Tree { private final Project project; @@ -82,9 +84,11 @@ public void treeWillCollapse(TreeExpansionEvent event) throws ExpandVetoExceptio } public void expandAll() { - for (var i = 0; i < getRowCount(); i++) { - expandRow(i); - } + runOnUiThread(project, () -> { + for (var i = 0; i < getRowCount(); i++) { + expandRow(i); + } + }); } private void highlightInEditor(DefaultMutableTreeNode node) { diff --git a/src/main/java/org/sonarlint/intellij/ui/tree/IssueTreeModelBuilder.java b/src/main/java/org/sonarlint/intellij/ui/tree/IssueTreeModelBuilder.java index 9f5f05c4e6..8c2b769490 100644 --- a/src/main/java/org/sonarlint/intellij/ui/tree/IssueTreeModelBuilder.java +++ b/src/main/java/org/sonarlint/intellij/ui/tree/IssueTreeModelBuilder.java @@ -21,6 +21,7 @@ import com.google.common.collect.ComparisonChain; import com.google.common.collect.Ordering; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import java.util.Collection; @@ -46,6 +47,7 @@ import org.sonarsource.sonarlint.core.client.utils.ImpactSeverity; import org.sonarsource.sonarlint.core.rpc.protocol.common.IssueSeverity; +import static org.sonarlint.intellij.ui.UiUtils.runOnUiThread; import static org.sonarsource.sonarlint.core.client.utils.ImpactSeverity.HIGH; import static org.sonarsource.sonarlint.core.client.utils.ImpactSeverity.LOW; import static org.sonarsource.sonarlint.core.client.utils.ImpactSeverity.MEDIUM; @@ -98,13 +100,13 @@ private SummaryNode getFilesParent() { } public void clear() { - updateModel(Collections.emptyMap()); + runOnUiThread(project, () -> updateModel(Collections.emptyMap())); } public void updateModel(Map<VirtualFile, Collection<LiveIssue>> map) { latestIssues = map; - var toRemove = index.getAllFiles().stream().filter(f -> !map.containsKey(f)).toList(); + ApplicationManager.getApplication().assertIsDispatchThread(); toRemove.forEach(this::removeFile); @@ -129,7 +131,7 @@ public void allowResolvedIssues(boolean allowResolved) { } public void refreshModel(Project project) { - updateModel(latestIssues); + runOnUiThread(project, () -> updateModel(latestIssues)); var fileList = new HashSet<>(latestIssues.keySet()); SonarLintUtils.getService(project, CodeAnalyzerRestarter.class).refreshFiles(fileList); } diff --git a/src/main/java/org/sonarlint/intellij/ui/tree/SecurityHotspotTreeModelBuilder.java b/src/main/java/org/sonarlint/intellij/ui/tree/SecurityHotspotTreeModelBuilder.java index a216c53707..607478be63 100644 --- a/src/main/java/org/sonarlint/intellij/ui/tree/SecurityHotspotTreeModelBuilder.java +++ b/src/main/java/org/sonarlint/intellij/ui/tree/SecurityHotspotTreeModelBuilder.java @@ -21,6 +21,7 @@ import com.google.common.collect.ComparisonChain; import com.google.common.collect.Ordering; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import java.util.ArrayList; @@ -100,6 +101,7 @@ private SummaryNode getFilesParent() { public void updateModel(Map<VirtualFile, Collection<LiveSecurityHotspot>> map) { var toRemove = index.getAllFiles().stream().filter(f -> !map.containsKey(f)).toList(); + ApplicationManager.getApplication().assertIsDispatchThread(); nonFilteredNodes.clear(); toRemove.forEach(this::removeFile); diff --git a/src/main/java/org/sonarlint/intellij/ui/vulnerabilities/TaintVulnerabilitiesPanel.kt b/src/main/java/org/sonarlint/intellij/ui/vulnerabilities/TaintVulnerabilitiesPanel.kt index e5dd616a8e..e267e2d6f8 100644 --- a/src/main/java/org/sonarlint/intellij/ui/vulnerabilities/TaintVulnerabilitiesPanel.kt +++ b/src/main/java/org/sonarlint/intellij/ui/vulnerabilities/TaintVulnerabilitiesPanel.kt @@ -133,7 +133,7 @@ class TaintVulnerabilitiesPanel(private val project: Project) : SimpleToolWindow treePanel.add(tree) treePanel.add(oldTree) - val treeScrollPane = ScrollPaneFactory.createScrollPane(treePanel) + val treeScrollPane = ScrollPaneFactory.createScrollPane(treePanel, true) cards.add(createSplitter(project, this, this, treeScrollPane, rulePanel, SPLIT_PROPORTION_PROPERTY, DEFAULT_SPLIT_PROPORTION), TREE_CARD_ID ) @@ -291,7 +291,7 @@ class TaintVulnerabilitiesPanel(private val project: Project) : SimpleToolWindow private fun expandDefault() { if (taintVulnerabilityTreeUpdater.filteredTaintVulnerabilities.size < 30) { - TreeUtil.expand(tree, 2) + runOnPooledThread(project) { TreeUtil.expand(tree, 2) } } else { tree.expandRow(0) } diff --git a/src/main/java/org/sonarlint/intellij/ui/vulnerabilities/tree/TaintVulnerabilityTreeUpdater.kt b/src/main/java/org/sonarlint/intellij/ui/vulnerabilities/tree/TaintVulnerabilityTreeUpdater.kt index b54ba47a50..e4baf7eab4 100644 --- a/src/main/java/org/sonarlint/intellij/ui/vulnerabilities/tree/TaintVulnerabilityTreeUpdater.kt +++ b/src/main/java/org/sonarlint/intellij/ui/vulnerabilities/tree/TaintVulnerabilityTreeUpdater.kt @@ -19,11 +19,13 @@ */ package org.sonarlint.intellij.ui.vulnerabilities.tree +import com.intellij.openapi.application.ModalityState import org.sonarlint.intellij.finding.FindingType import org.sonarlint.intellij.finding.Flow import org.sonarlint.intellij.finding.FragmentLocation import org.sonarlint.intellij.finding.SameFileFlowFragment import org.sonarlint.intellij.finding.issue.vulnerabilities.LocalTaintVulnerability +import org.sonarlint.intellij.ui.UiUtils.Companion.runOnUiThread import org.sonarlint.intellij.ui.nodes.SummaryNode import org.sonarlint.intellij.ui.tree.CompactTree import org.sonarlint.intellij.ui.tree.CompactTreeModel @@ -77,7 +79,7 @@ class TaintVulnerabilityTreeUpdater(private val treeSummary: TreeSummary) { private fun applyFiltering() { val filters = findingFilters() filteredTaintVulnerabilities = taintVulnerabilities.filter { vulnerability -> filters.all { filter -> filter.filter(vulnerability) } } - model.setCompactTree(createCompactTree(filteredTaintVulnerabilities)) + runOnUiThread(ModalityState.defaultModalityState()) { model.setCompactTree(createCompactTree(filteredTaintVulnerabilities)) } treeSummary.refresh(filteredTaintVulnerabilities.filter { it.file() != null }.groupBy { it.file() }.keys.size, filteredTaintVulnerabilities.size) } diff --git a/src/main/java/org/sonarlint/intellij/util/ProjectUtils.java b/src/main/java/org/sonarlint/intellij/util/ProjectUtils.java index 4256c9c1a0..265a0beaad 100644 --- a/src/main/java/org/sonarlint/intellij/util/ProjectUtils.java +++ b/src/main/java/org/sonarlint/intellij/util/ProjectUtils.java @@ -19,6 +19,7 @@ */ package org.sonarlint.intellij.util; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectCoreUtil; import com.intellij.openapi.project.ProjectUtil; @@ -77,6 +78,7 @@ private static void iterateFilesToAnalyze(Project project, Predicate<VirtualFile } public static PsiFile toPsiFile(Project project, VirtualFile file) throws TextRangeMatcher.NoMatchException { + ApplicationManager.getApplication().assertReadAccessAllowed(); var psiManager = PsiManager.getInstance(project); var psiFile = psiManager.findFile(file); if (psiFile != null) { diff --git a/src/main/java/org/sonarlint/intellij/util/SonarLintAppUtils.java b/src/main/java/org/sonarlint/intellij/util/SonarLintAppUtils.java index 777a954934..d473401172 100644 --- a/src/main/java/org/sonarlint/intellij/util/SonarLintAppUtils.java +++ b/src/main/java/org/sonarlint/intellij/util/SonarLintAppUtils.java @@ -65,13 +65,10 @@ public static Project guessProjectForFile(VirtualFile file) { } public static List<VirtualFile> retainOpenFiles(Project project, List<VirtualFile> files) { - var openFiles = computeReadActionSafely(project, () -> { - if (!project.isOpen()) { - return Collections.<VirtualFile>emptyList(); - } - return files.stream().filter(f -> FileEditorManager.getInstance(project).isFileOpen(f)).toList(); - }); - return openFiles != null ? openFiles : Collections.emptyList(); + if (project.isDisposed() || !project.isOpen()) { + return Collections.emptyList(); + } + return files.stream().filter(f -> FileEditorManager.getInstance(project).isFileOpen(f)).toList(); } /** diff --git a/src/test/java/org/sonarlint/intellij/actions/SonarAnalyzeAllFilesActionTests.java b/src/test/java/org/sonarlint/intellij/actions/SonarAnalyzeAllFilesActionTests.java index fb3af5d6e5..77129603ae 100644 --- a/src/test/java/org/sonarlint/intellij/actions/SonarAnalyzeAllFilesActionTests.java +++ b/src/test/java/org/sonarlint/intellij/actions/SonarAnalyzeAllFilesActionTests.java @@ -30,6 +30,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; @@ -77,7 +78,7 @@ void testRun() { TestDialogManager.setTestDialog(TestDialog.OK); action.actionPerformed(event); - verify(analysisSubmitter).analyzeAllFiles(); + verify(analysisSubmitter, timeout(1000)).analyzeAllFiles(); } } diff --git a/src/test/java/org/sonarlint/intellij/actions/SonarAnalyzeChangedFilesActionTests.java b/src/test/java/org/sonarlint/intellij/actions/SonarAnalyzeChangedFilesActionTests.java index 73de771abd..d0162bdb32 100644 --- a/src/test/java/org/sonarlint/intellij/actions/SonarAnalyzeChangedFilesActionTests.java +++ b/src/test/java/org/sonarlint/intellij/actions/SonarAnalyzeChangedFilesActionTests.java @@ -27,11 +27,12 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.sonarlint.intellij.AbstractSonarLintLightTests; -import org.sonarlint.intellij.analysis.AnalysisSubmitter; import org.sonarlint.intellij.analysis.AnalysisStatus; +import org.sonarlint.intellij.analysis.AnalysisSubmitter; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; @@ -87,6 +88,6 @@ void testRun() { action.actionPerformed(event); - verify(analysisSubmitter).analyzeVcsChangedFiles(); + verify(analysisSubmitter, timeout(1000)).analyzeVcsChangedFiles(); } } diff --git a/src/test/java/org/sonarlint/intellij/trigger/BuildFinishedAnalysisTriggerTests.java b/src/test/java/org/sonarlint/intellij/trigger/BuildFinishedAnalysisTriggerTests.java index bd8ddd7d6d..2d57434d6f 100644 --- a/src/test/java/org/sonarlint/intellij/trigger/BuildFinishedAnalysisTriggerTests.java +++ b/src/test/java/org/sonarlint/intellij/trigger/BuildFinishedAnalysisTriggerTests.java @@ -26,6 +26,7 @@ import org.sonarlint.intellij.analysis.AnalysisSubmitter; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; @@ -43,7 +44,7 @@ void prepare() { @Test void should_trigger_automake() { trigger.buildFinished(getProject(), UUID.randomUUID(), true); - verify(submitter).autoAnalyzeOpenFiles(TriggerType.COMPILATION); + verify(submitter, timeout(1000)).autoAnalyzeOpenFiles(TriggerType.COMPILATION); } @Test diff --git a/src/test/java/org/sonarlint/intellij/trigger/CompilationFinishedAnalysisTriggerTests.java b/src/test/java/org/sonarlint/intellij/trigger/CompilationFinishedAnalysisTriggerTests.java index 5f977efa43..b226c2e17c 100644 --- a/src/test/java/org/sonarlint/intellij/trigger/CompilationFinishedAnalysisTriggerTests.java +++ b/src/test/java/org/sonarlint/intellij/trigger/CompilationFinishedAnalysisTriggerTests.java @@ -26,6 +26,7 @@ import org.sonarlint.intellij.analysis.AnalysisSubmitter; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; @@ -46,7 +47,7 @@ void prepare() { @Test void should_trigger_on_compilation() { trigger.compilationFinished(false, 0, 0, context); - verify(submitter).autoAnalyzeOpenFiles(TriggerType.COMPILATION); + verify(submitter, timeout(1000)).autoAnalyzeOpenFiles(TriggerType.COMPILATION); } @Test diff --git a/src/test/java/org/sonarlint/intellij/trigger/EditorOpenTriggerTests.java b/src/test/java/org/sonarlint/intellij/trigger/EditorOpenTriggerTests.java index ed5dcbc85b..b45367c548 100644 --- a/src/test/java/org/sonarlint/intellij/trigger/EditorOpenTriggerTests.java +++ b/src/test/java/org/sonarlint/intellij/trigger/EditorOpenTriggerTests.java @@ -29,6 +29,7 @@ import org.sonarlint.intellij.analysis.AnalysisSubmitter; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; @@ -55,7 +56,7 @@ void start() { void should_trigger() { editorTrigger.fileOpened(editorManager, file); - verify(analysisSubmitter).autoAnalyzeFile(file, TriggerType.EDITOR_OPEN); + verify(analysisSubmitter, timeout(1000)).autoAnalyzeFile(file, TriggerType.EDITOR_OPEN); } @Test @@ -63,6 +64,6 @@ void should_do_nothing_closed() { editorTrigger.fileClosed(editorManager, file); editorTrigger.selectionChanged(new FileEditorManagerEvent(editorManager, null, null, null, null)); - verifyNoInteractions (analysisSubmitter); + verifyNoInteractions(analysisSubmitter); } } diff --git a/src/test/java/org/sonarlint/intellij/trigger/SonarLintCheckinHandlerTests.java b/src/test/java/org/sonarlint/intellij/trigger/SonarLintCheckinHandlerTests.java index 9d776397d3..e34e02d72f 100644 --- a/src/test/java/org/sonarlint/intellij/trigger/SonarLintCheckinHandlerTests.java +++ b/src/test/java/org/sonarlint/intellij/trigger/SonarLintCheckinHandlerTests.java @@ -47,6 +47,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; @@ -88,7 +89,7 @@ void testNoUnresolvedIssues() { var result = handler.beforeCheckin(null, null); assertThat(result).isEqualTo(CheckinHandler.ReturnResult.COMMIT); - verify(analysisSubmitter).analyzeFilesPreCommit(Collections.singleton(file)); + verify(analysisSubmitter, timeout(1000)).analyzeFilesPreCommit(Collections.singleton(file)); verifyNoInteractions(toolWindow); } @@ -117,10 +118,10 @@ void testIssues() { assertThat(result).isEqualTo(CheckinHandler.ReturnResult.CLOSE_WINDOW); assertThat(messages).containsExactly("SonarLint analysis on 1 file found 1 issue"); ArgumentCaptor<AnalysisResult> analysisResultCaptor = ArgumentCaptor.forClass(AnalysisResult.class); - verify(toolWindow).openReportTab(analysisResultCaptor.capture()); + verify(toolWindow, timeout(1000)).openReportTab(analysisResultCaptor.capture()); var analysisResult = analysisResultCaptor.getValue(); assertThat(analysisResult.getFindings().getIssuesPerFile()).containsEntry(file, Set.of(issue)); - verify(analysisSubmitter).analyzeFilesPreCommit(Collections.singleton(file)); + verify(analysisSubmitter, timeout(1000)).analyzeFilesPreCommit(Collections.singleton(file)); } @@ -151,9 +152,9 @@ void testSecretsIssues() { "\n" + "SonarLint analysis found 1 secret. Committed secrets may lead to unauthorized system access."); ArgumentCaptor<AnalysisResult> analysisResultCaptor = ArgumentCaptor.forClass(AnalysisResult.class); - verify(toolWindow).openReportTab(analysisResultCaptor.capture()); + verify(toolWindow, timeout(1000)).openReportTab(analysisResultCaptor.capture()); var analysisResult = analysisResultCaptor.getValue(); assertThat(analysisResult.getFindings().getIssuesPerFile()).containsEntry(file, Set.of(issue)); - verify(analysisSubmitter).analyzeFilesPreCommit(Collections.singleton(file)); + verify(analysisSubmitter, timeout(1000)).analyzeFilesPreCommit(Collections.singleton(file)); } }