/* * Copyright 2000-2015 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.jetbrains.lang.dart.ide.errorTreeView; import com.intellij.execution.runners.ExecutionUtil; import com.intellij.ide.projectView.ProjectView; import com.intellij.ide.projectView.impl.ProjectViewPane; import com.intellij.ide.util.PropertiesComponent; import com.intellij.notification.Notification; import com.intellij.notification.NotificationGroup; import com.intellij.notification.NotificationListener; import com.intellij.notification.NotificationType; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.DefaultActionGroup; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.components.*; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.wm.ToolWindow; import com.intellij.openapi.wm.ToolWindowAnchor; import com.intellij.openapi.wm.ToolWindowManager; import com.intellij.openapi.wm.ex.ToolWindowEx; import com.intellij.ui.content.Content; import com.intellij.ui.content.ContentFactory; import com.intellij.util.Alarm; import com.intellij.util.ui.UIUtil; import com.jetbrains.lang.dart.DartBundle; import com.jetbrains.lang.dart.analyzer.DartAnalysisServerMessages; import com.jetbrains.lang.dart.analyzer.DartAnalysisServerService; import gnu.trove.THashMap; import icons.DartIcons; import org.dartlang.analysis.server.protocol.AnalysisError; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.event.HyperlinkEvent; import java.util.ArrayList; import java.util.List; import java.util.Map; @State( name = "DartProblemsView", storages = @Storage(StoragePathMacros.WORKSPACE_FILE) ) public class DartProblemsView implements PersistentStateComponent<DartProblemsViewSettings> { public static final String TOOLWINDOW_ID = DartBundle.message("dart.analysis.tool.window"); private static final NotificationGroup NOTIFICATION_GROUP = NotificationGroup.toolWindowGroup(TOOLWINDOW_ID, TOOLWINDOW_ID, false); private static final int TABLE_REFRESH_PERIOD = 300; private final Project myProject; private final DartProblemsPresentationHelper myPresentationHelper; private DartProblemsViewPanel myPanel; private final Object myLock = new Object(); // use this lock to access myScheduledFilePathToErrors and myAlarm private final Map<String, List<AnalysisError>> myScheduledFilePathToErrors = new THashMap<>(); private final Alarm myAlarm; private ToolWindow myToolWindow; private Icon myCurrentIcon; private boolean myAnalysisIsBusy; private int myFilesWithErrorsHash; private Notification myNotification; private final Runnable myUpdateRunnable = new Runnable() { @Override public void run() { if (ProjectViewPane.ID.equals(ProjectView.getInstance(myProject).getCurrentViewId())) { final int hash = DartAnalysisServerService.getInstance(myProject).getFilePathsWithErrorsHash(); if (myFilesWithErrorsHash != hash) { // refresh red squiggles managed by com.jetbrains.lang.dart.projectView.DartNodeDecorator myFilesWithErrorsHash = hash; ProjectView.getInstance(myProject).refresh(); } } final Map<String, List<AnalysisError>> filePathToErrors; synchronized (myLock) { filePathToErrors = new THashMap<>(myScheduledFilePathToErrors); myScheduledFilePathToErrors.clear(); } myPanel.setErrors(filePathToErrors); } }; public DartProblemsView(@NotNull final Project project, @NotNull final ToolWindowManager toolWindowManager) { myProject = project; myPresentationHelper = new DartProblemsPresentationHelper(project); myAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD, project); Disposer.register(project, myAlarm); UIUtil.invokeLaterIfNeeded(() -> { if (project.isDisposed()) { return; } myPanel = new DartProblemsViewPanel(project, myPresentationHelper); myToolWindow = toolWindowManager.registerToolWindow(TOOLWINDOW_ID, false, ToolWindowAnchor.BOTTOM, project, true); myCurrentIcon = DartIcons.Dart_13; updateIcon(); final Content content = ContentFactory.SERVICE.getInstance().createContent(myPanel, "", false); myToolWindow.getContentManager().addContent(content); ToolWindowEx toolWindowEx = (ToolWindowEx)myToolWindow; toolWindowEx.setTitleActions(new AnalysisServerStatusAction()); ArrayList<AnAction> gearActions = new ArrayList<>(); gearActions.add(new AnalysisServerDiagnosticsAction()); toolWindowEx.setAdditionalGearActions(new DefaultActionGroup(gearActions)); myPanel.setToolWindowUpdater(new ToolWindowUpdater() { @Override public void setIcon(@NotNull Icon icon) { myCurrentIcon = icon; updateIcon(); } @Override public void setHeaderText(@NotNull String headerText) { content.setDisplayName(headerText); } }); if (PropertiesComponent.getInstance(project).getBoolean("dart.analysis.tool.window.force.activate", true)) { PropertiesComponent.getInstance(project).setValue("dart.analysis.tool.window.force.activate", false, true); myToolWindow.activate(null, false); } Disposer.register(project, () -> myToolWindow.getContentManager().removeAllContents(true)); }); project.getMessageBus().connect().subscribe( DartAnalysisServerMessages.DART_ANALYSIS_TOPIC, new DartAnalysisServerMessages.DartAnalysisNotifier() { @Override public void analysisStarted() { myAnalysisIsBusy = true; UIUtil.invokeLaterIfNeeded(() -> updateIcon()); } @Override public void analysisFinished() { myAnalysisIsBusy = false; UIUtil.invokeLaterIfNeeded(() -> updateIcon()); } } ); } void updateIcon() { if (myAnalysisIsBusy) { myToolWindow.setIcon(ExecutionUtil.getLiveIndicator(myCurrentIcon)); } else { myToolWindow.setIcon(myCurrentIcon); } } public static DartProblemsView getInstance(@NotNull final Project project) { return ServiceManager.getService(project, DartProblemsView.class); } @SuppressWarnings("unused") public void showWarningNotification(@NotNull String title, @NotNull String htmlContent, @Nullable Icon icon) { showNotification(NotificationType.WARNING, title, htmlContent, icon); } public void showErrorNotification(@NotNull String title, @NotNull String htmlContent, @Nullable Icon icon) { showNotification(NotificationType.ERROR, title, htmlContent, icon); } public void clearNotifications() { if (myNotification != null) { myNotification.expire(); myNotification = null; } } private void showNotification(@NotNull NotificationType notificationType, @NotNull String title, @NotNull String htmlContent, @Nullable Icon icon) { clearNotifications(); myNotification = NOTIFICATION_GROUP.createNotification( title, htmlContent + " (<a href='open.dart.analysis'>show</a>)", notificationType, new NotificationListener.Adapter() { @Override protected void hyperlinkActivated(@NotNull final Notification notification, @NotNull final HyperlinkEvent e) { if ("open.dart.analysis".equals(e.getDescription())) { notification.expire(); ToolWindowManager.getInstance(myProject).getToolWindow(TOOLWINDOW_ID).activate(null); } } } ); if (icon != null) { myNotification.setIcon(icon); } myNotification.notify(myProject); } @Override public DartProblemsViewSettings getState() { return myPresentationHelper.getSettings(); } @Override public void loadState(DartProblemsViewSettings state) { myPresentationHelper.setSettings(state); if (myPanel != null) { myPanel.fireGroupingOrFilterChanged(); } } public void setCurrentFile(@Nullable final VirtualFile file) { if (myPresentationHelper.setCurrentFile(file) && myPresentationHelper.getFileFilterMode() != DartProblemsViewSettings.FileFilterMode.All) { if (myPanel != null) { myPanel.fireGroupingOrFilterChanged(); } } } public void updateErrorsForFile(@NotNull final String filePath, @NotNull final List<AnalysisError> errors) { synchronized (myLock) { if (myScheduledFilePathToErrors.isEmpty()) { myAlarm.addRequest(myUpdateRunnable, TABLE_REFRESH_PERIOD, ModalityState.NON_MODAL); } myScheduledFilePathToErrors.put(filePath, errors); } } public void clearAll() { ApplicationManager.getApplication().assertIsDispatchThread(); ProjectView.getInstance(myProject).refresh(); // refresh red waves managed by com.jetbrains.lang.dart.projectView.DartNodeDecorator synchronized (myLock) { myAlarm.cancelAllRequests(); myScheduledFilePathToErrors.clear(); } myPanel.clearAll(); } interface ToolWindowUpdater { void setIcon(@NotNull final Icon icon); void setHeaderText(@NotNull final String headerText); } }