package mobi.hsz.idea.vcswatch.components;
import com.intellij.concurrency.JobScheduler;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationListener;
import com.intellij.notification.NotificationType;
import com.intellij.notification.Notifications;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.ActionPlaces;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.components.ProjectComponent;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.playback.commands.ActionCommand;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vcs.changes.ui.ChangesViewContentManager;
import com.intellij.openapi.vcs.update.AbstractCommonUpdateAction;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowManager;
import com.intellij.ui.content.Content;
import com.intellij.ui.content.ContentManager;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.vcs.log.impl.VcsLogContentProvider;
import com.intellij.vcs.log.impl.VcsLogManager;
import com.intellij.vcs.log.ui.VcsLogUiImpl;
import mobi.hsz.idea.vcswatch.VcsWatchBundle;
import mobi.hsz.idea.vcswatch.core.Commit;
import mobi.hsz.idea.vcswatch.core.VcsWatchManager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.ocpsoft.prettytime.PrettyTime;
import javax.swing.event.HyperlinkEvent;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
/**
* {@link ProjectComponent} that notifies about the new commits.
*
* @author Jakub Chrzanowski <jakub@hsz.mobi>
* @since 0.1
*/
public class CommitNotificationProjectComponent implements ProjectComponent {
/**
* Delay required to wait for another commits.
*/
private static final long DELAY = 1500;
/**
* Current project.
*/
private final Project project;
/**
* Project VCS watch manager.
*/
private final VcsWatchManager vcsWatchManager;
/**
* An {@link java.util.concurrent.ExecutorService} that can schedule commands.
*/
private final ScheduledExecutorService scheduler;
/**
* Commits to notify about.
*/
private final List<Commit> commits = ContainerUtil.newArrayList();
/**
* Scheduled feature with notification event.
*/
private ScheduledFuture<?> scheduledFeature;
/**
* Action that shows new {@link Notification} with {@link Commit} messages.
*/
private final Runnable notify = new Runnable() {
@Override
public void run() {
if (commits.isEmpty()) {
return;
}
Notifications.Bus.notify(new CommitNotification(project, commits), project);
commits.clear();
}
};
/**
* Listens for the new {@link Commit} items.
*/
private final VcsWatchManager.OnCommitListener onCommitListener = new VcsWatchManager.OnCommitListener() {
@Override
public void onCommit(@NotNull Commit commit) {
if (scheduledFeature != null) {
scheduledFeature.cancel(true);
}
commits.add(commit);
scheduledFeature = scheduler.schedule(notify, DELAY, TimeUnit.MILLISECONDS);
}
};
public CommitNotificationProjectComponent(@NotNull Project project) {
this.project = project;
this.vcsWatchManager = VcsWatchManager.getInstance(project);
this.scheduler = JobScheduler.getScheduler();
}
@Override
public void projectOpened() {
this.vcsWatchManager.setOnCommitListener(onCommitListener);
}
@Override
public void projectClosed() {
this.vcsWatchManager.removeOnCommitListener(onCommitListener);
}
@Override
public void initComponent() {
}
@Override
public void disposeComponent() {
}
@NotNull
@Override
public String getComponentName() {
return "VcsWatch.CommitNotification";
}
private static class CommitNotification extends Notification {
private static final String TITLE = VcsWatchBundle.message("notification.title");
private static final String TITLE_PLURAL = VcsWatchBundle.message("notification.title.plural");
private final List<Commit> commits;
private CommitNotification(@NotNull final Project project, @NotNull final List<Commit> commits) {
super(
VcsWatchBundle.PLUGIN_NAME,
commits.size() == 1 ? TITLE : TITLE_PLURAL,
VcsWatchBundle.PLUGIN_NAME,
NotificationType.INFORMATION,
createListener(project)
);
this.commits = ContainerUtil.newArrayList(commits);
}
@NotNull
@Override
public String getContent() {
List<String> messages = ContainerUtil.newArrayList();
for (Commit commit : commits) {
String time = new PrettyTime(Locale.ENGLISH).format(commit.getDate());
messages.add(VcsWatchBundle.message("notification.content", commit.getMessage(), commit.getId(), time, commit.getUser()));
}
return VcsWatchBundle.message("notification.update", StringUtil.join(messages, "<br/>"));
}
private static NotificationListener.Adapter createListener(@NotNull final Project project) {
return new NotificationListener.Adapter() {
private static final String HASH = "HASH:";
private static final String UPDATE = "UPDATE";
private static final String UPDATE_ACTION_ID = "Vcs.UpdateProject";
@Override
protected void hyperlinkActivated(@NotNull Notification notification, @NotNull HyperlinkEvent event) {
if (StringUtil.equals(event.getDescription(), UPDATE)) {
update();
if (!notification.isExpired()) {
notification.expire();
}
} else if (StringUtil.startsWith(event.getDescription(), HASH)) {
jumpToReference(StringUtil.substringAfter(event.getDescription(), HASH));
}
}
private void update() {
final AnAction action = ActionManager.getInstance().getAction(UPDATE_ACTION_ID);
assert action instanceof AbstractCommonUpdateAction;
final AbstractCommonUpdateAction updateAction = (AbstractCommonUpdateAction) action;
ActionManager.getInstance().tryToExecute(
updateAction,
ActionCommand.getInputEvent(UPDATE_ACTION_ID),
null,
ActionPlaces.UNKNOWN,
true
);
}
private void jumpToReference(@Nullable final String hash) {
final VcsLogManager log = ServiceManager.getService(project, VcsLogManager.class);
if (log == null) {
return;
}
final ToolWindowManager windowManager = ToolWindowManager.getInstance(project);
final ToolWindow window = windowManager.getToolWindow(ChangesViewContentManager.TOOLWINDOW_ID);
ContentManager cm = window.getContentManager();
Content[] contents = cm.getContents();
for (Content content : contents) {
if (VcsLogContentProvider.TAB_NAME.equals(content.getDisplayName())) {
cm.setSelectedContent(content);
}
}
Runnable selectCommit = new Runnable() {
private boolean invokedLater = false;
@Override
public void run() {
VcsLogUiImpl logUi = log.getLogUi();
if (logUi != null) {
logUi.getVcsLog().jumpToReference(hash);
} else {
if (invokedLater) {
try {
Thread.sleep(50);
} catch (InterruptedException ignored) {
}
}
invokedLater = true;
windowManager.invokeLater(this);
}
}
};
if (!window.isVisible()) {
window.activate(selectCommit, true);
} else {
selectCommit.run();
}
}
};
}
}
}