package org.infernus.idea.checkstyle.handlers; import com.intellij.CommonBundle; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.vcs.CheckinProjectPanel; import com.intellij.openapi.vcs.changes.CommitExecutor; import com.intellij.openapi.vcs.checkin.CheckinHandler; import com.intellij.openapi.vcs.ui.RefreshableOnComponent; import com.intellij.psi.PsiFile; import com.intellij.util.PairConsumer; import com.intellij.util.ui.UIUtil; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.infernus.idea.checkstyle.CheckStyleConfiguration; import org.infernus.idea.checkstyle.CheckStylePlugin; import org.infernus.idea.checkstyle.checker.Problem; import org.infernus.idea.checkstyle.csapi.SeverityLevel; import org.infernus.idea.checkstyle.toolwindow.CheckStyleToolWindowPanel; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; import java.util.*; import java.util.List; import static com.intellij.openapi.vcs.checkin.CheckinHandler.ReturnResult.*; import static java.util.Optional.empty; import static java.util.Optional.ofNullable; import static java.util.stream.Collectors.toList; import static org.infernus.idea.checkstyle.CheckStyleBundle.message; public class ScanFilesBeforeCheckinHandler extends CheckinHandler { private static final Log LOG = LogFactory.getLog(ScanFilesBeforeCheckinHandler.class); private final CheckinProjectPanel checkinPanel; public ScanFilesBeforeCheckinHandler(@NotNull final CheckinProjectPanel myCheckinPanel) { this.checkinPanel = myCheckinPanel; } @Nullable public RefreshableOnComponent getBeforeCheckinConfigurationPanel() { final JCheckBox checkBox = new JCheckBox(message("handler.before.checkin.checkbox")); return new RefreshableOnComponent() { public JComponent getComponent() { final JPanel panel = new JPanel(new BorderLayout()); panel.add(checkBox); return panel; } public void refresh() { } public void saveState() { settings().ifPresent(settings -> settings.setScanFilesBeforeCheckin(checkBox.isSelected())); } public void restoreState() { checkBox.setSelected(settings() .map(CheckStyleConfiguration::isScanFilesBeforeCheckin) .orElseGet(() -> Boolean.FALSE)); } }; } @Override public ReturnResult beforeCheckin(@Nullable final CommitExecutor executor, final PairConsumer<Object, Object> additionalDataConsumer) { final Project project = checkinPanel.getProject(); if (project == null) { LOG.error("Could not get project for check-in panel, skipping"); return COMMIT; } final CheckStylePlugin plugin = project.getComponent(CheckStylePlugin.class); if (plugin == null) { LOG.error("Could not get CheckStyle Plug-in, skipping"); return COMMIT; } if (plugin.getConfiguration().isScanFilesBeforeCheckin()) { try { final Map<PsiFile, List<Problem>> scanResults = new HashMap<>(); new Task.Modal(project, message("handler.before.checkin.scan.text"), false) { public void run(@NotNull final ProgressIndicator progressIndicator) { progressIndicator.setText(message("handler.before.checkin.scan.in-progress")); progressIndicator.setIndeterminate(true); scanResults.putAll(plugin.scanFiles(new ArrayList<>(checkinPanel.getVirtualFiles()))); } }.queue(); return processScanResults(scanResults, executor, plugin); } catch (ProcessCanceledException e) { return CANCEL; } } else { return COMMIT; } } private Optional<CheckStyleConfiguration> settings() { final Project project = checkinPanel.getProject(); if (project == null) { LOG.error("Could not get project for check-in panel"); return empty(); } final CheckStylePlugin plugin = project.getComponent(CheckStylePlugin.class); if (plugin == null) { LOG.error("Could not get CheckStyle Plug-in, skipping"); return empty(); } return ofNullable(plugin.getConfiguration()); } private ReturnResult processScanResults(final Map<PsiFile, List<Problem>> results, final CommitExecutor executor, final CheckStylePlugin plugin) { final int errorCount = errorCountOf(results); if (errorCount == 0) { return COMMIT; } final int answer = promptUser(plugin, errorCount, executor); if (answer == Messages.OK) { showResultsInToolWindow(results, plugin); return CLOSE_WINDOW; } else if (answer == Messages.CANCEL || answer < 0) { return CANCEL; } return COMMIT; } private int errorCountOf(final Map<PsiFile, List<Problem>> results) { return results.entrySet().stream() .filter(this::hasProblemsThatAreNotIgnored) .collect(toList()) .size(); } private boolean hasProblemsThatAreNotIgnored(final Map.Entry<PsiFile, List<Problem>> entry) { return entry.getValue() .stream() .filter(problem -> problem.severityLevel() != SeverityLevel.Ignore) .collect(toList()) .size() > 0; } private int promptUser(final CheckStylePlugin plugin, final int errorCount, final CommitExecutor executor) { String commitButtonText; if (executor != null) { commitButtonText = executor.getActionText(); } else { commitButtonText = checkinPanel.getCommitActionName(); } if (commitButtonText.endsWith("...")) { commitButtonText = commitButtonText.substring(0, commitButtonText.length() - 3); } final String[] buttons = new String[] { message("handler.before.checkin.error.review"), commitButtonText, CommonBundle.getCancelButtonText()}; return Messages.showDialog(plugin.getProject(), message("handler.before.checkin.error.text", errorCount), message("handler.before.checkin.error.title"), buttons, 0, UIUtil.getWarningIcon()); } private void showResultsInToolWindow(final Map<PsiFile, List<Problem>> results, final CheckStylePlugin plugin) { final CheckStyleToolWindowPanel toolWindowPanel = CheckStyleToolWindowPanel.panelFor(plugin.getProject()); if (toolWindowPanel != null) { toolWindowPanel.displayResults(results); toolWindowPanel.showToolWindow(); } } }