blob: c542757c88593318e1c1f712838a6ca2a92d202e [file] [log] [blame]
/*
* Copyright 2000-2010 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.intellij.openapi.vcs.actions;
import com.intellij.icons.AllIcons;
import com.intellij.idea.ActionsBundle;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.diff.DiffNavigationContext;
import com.intellij.openapi.localVcs.UpToDateLineNumberProvider;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.ui.MessageType;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vcs.*;
import com.intellij.openapi.vcs.annotate.FileAnnotation;
import com.intellij.openapi.vcs.annotate.LineNumberListener;
import com.intellij.openapi.vcs.changes.BackgroundFromStartOption;
import com.intellij.openapi.vcs.changes.Change;
import com.intellij.openapi.vcs.changes.ChangeListManager;
import com.intellij.openapi.vcs.changes.actions.ShowDiffAction;
import com.intellij.openapi.vcs.changes.actions.ShowDiffUIContext;
import com.intellij.openapi.vcs.changes.ui.ChangesComparator;
import com.intellij.openapi.vcs.history.VcsRevisionNumber;
import com.intellij.openapi.vcs.ui.VcsBalloonProblemNotifier;
import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.containers.CacheOneStepIterator;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
/**
* @author Konstantin Bulenkov
*/
class ShowDiffFromAnnotation extends AnAction implements LineNumberListener {
private final UpToDateLineNumberProvider myLineNumberProvider;
private final FileAnnotation myFileAnnotation;
private final AbstractVcs myVcs;
private final VirtualFile myFile;
private int currentLine;
private boolean myEnabled;
ShowDiffFromAnnotation(final UpToDateLineNumberProvider lineNumberProvider,
final FileAnnotation fileAnnotation, final AbstractVcs vcs, final VirtualFile file) {
super(ActionsBundle.message("action.Diff.UpdatedFiles.text"),
ActionsBundle.message("action.Diff.UpdatedFiles.description"),
AllIcons.Actions.Diff);
myLineNumberProvider = lineNumberProvider;
myFileAnnotation = fileAnnotation;
myVcs = vcs;
myFile = file;
currentLine = -1;
myEnabled = ProjectLevelVcsManager.getInstance(vcs.getProject()).getVcsFor(myFile) != null;
}
@Override
public void consume(Integer integer) {
currentLine = integer;
}
@Override
public void update(AnActionEvent e) {
final int number = getActualLineNumber();
e.getPresentation().setVisible(myEnabled);
e.getPresentation().setEnabled(myEnabled && number >= 0 && number < myFileAnnotation.getLineCount());
}
private int getActualLineNumber() {
if (currentLine < 0) return -1;
return myLineNumberProvider.getLineNumber(currentLine);
}
@Override
public void actionPerformed(AnActionEvent e) {
final int actualNumber = getActualLineNumber();
if (actualNumber < 0) return;
final VcsRevisionNumber revisionNumber = myFileAnnotation.getLineRevisionNumber(actualNumber);
if (revisionNumber != null) {
final VcsException[] exc = new VcsException[1];
final List<Change> changes = new LinkedList<Change>();
final FilePath[] targetPath = new FilePath[1];
ProgressManager.getInstance().run(new Task.Backgroundable(myVcs.getProject(),
"Loading revision " + revisionNumber.asString() + " contents", true,
BackgroundFromStartOption.getInstance()) {
@Override
public void run(@NotNull ProgressIndicator indicator) {
final CommittedChangesProvider provider = myVcs.getCommittedChangesProvider();
try {
final Pair<CommittedChangeList, FilePath> pair = provider.getOneList(myFile, revisionNumber);
if (pair == null || pair.getFirst() == null) {
VcsBalloonProblemNotifier.showOverChangesView(myVcs.getProject(), "Can not load data for show diff", MessageType.ERROR);
return;
}
targetPath[0] = pair.getSecond() == null ? new FilePathImpl(myFile) : pair.getSecond();
final CommittedChangeList cl = pair.getFirst();
changes.addAll(cl.getChanges());
Collections.sort(changes, ChangesComparator.getInstance(true));
}
catch (VcsException e1) {
exc[0] = e1;
}
}
@Override
public void onSuccess() {
if (exc[0] != null) {
VcsBalloonProblemNotifier
.showOverChangesView(myVcs.getProject(), "Can not show diff: " + exc[0].getMessage(), MessageType.ERROR);
}
else if (!changes.isEmpty()) {
int idx = findSelfInList(changes, targetPath[0]);
final ShowDiffUIContext context = new ShowDiffUIContext(true);
context.setDiffNavigationContext(createDiffNavigationContext(actualNumber));
if (ChangeListManager.getInstance(myVcs.getProject()).isFreezedWithNotification(null)) return;
ShowDiffAction.showDiffForChange(changes.toArray(new Change[changes.size()]), idx, myVcs.getProject(), context);
}
}
});
}
}
private static int findSelfInList(List<Change> changes, final FilePath filePath) {
int idx = -1;
final File ioFile = filePath.getIOFile();
for (int i = 0; i < changes.size(); i++) {
final Change change = changes.get(i);
if ((change.getAfterRevision() != null) && (change.getAfterRevision().getFile().getIOFile().equals(ioFile))) {
idx = i;
break;
}
}
if (idx >= 0) return idx;
idx = 0;
// try to use name only
final String name = ioFile.getName();
for (int i = 0; i < changes.size(); i++) {
final Change change = changes.get(i);
if ((change.getAfterRevision() != null) && (change.getAfterRevision().getFile().getName().equals(name))) {
idx = i;
break;
}
}
return idx;
}
// for current line number
private DiffNavigationContext createDiffNavigationContext(final int actualLine) {
final ContentsLines contentsLines = new ContentsLines(myFileAnnotation.getAnnotatedContent());
final Pair<Integer, String> pair = correctActualLineIfTextEmpty(contentsLines, actualLine);
return new DiffNavigationContext(new Iterable<String>() {
@Override
public Iterator<String> iterator() {
return new CacheOneStepIterator<String>(new ContextLineIterator(contentsLines, myFileAnnotation, pair.getFirst()));
}
}, pair.getSecond());
}
private final static int ourVicinity = 5;
private Pair<Integer, String> correctActualLineIfTextEmpty(final ContentsLines contentsLines, final int actualLine) {
final VcsRevisionNumber revision = myFileAnnotation.getLineRevisionNumber(actualLine);
for (int i = actualLine; (i < (actualLine + ourVicinity)) && (!contentsLines.isLineEndsFinished()); i++) {
if (!revision.equals(myFileAnnotation.getLineRevisionNumber(i))) continue;
final String lineContents = contentsLines.getLineContents(i);
if (!StringUtil.isEmptyOrSpaces(lineContents)) {
return new Pair<Integer, String>(i, lineContents);
}
}
int bound = Math.max(actualLine - ourVicinity, 0);
for (int i = actualLine - 1; (i >= bound); --i) {
if (!revision.equals(myFileAnnotation.getLineRevisionNumber(i))) continue;
final String lineContents = contentsLines.getLineContents(i);
if (!StringUtil.isEmptyOrSpaces(lineContents)) {
return new Pair<Integer, String>(i, lineContents);
}
}
return new Pair<Integer, String>(actualLine, contentsLines.getLineContents(actualLine));
}
/**
* Slightly break the contract: can return null from next() while had claimed hasNext()
*/
private static class ContextLineIterator implements Iterator<String> {
private final ContentsLines myContentsLines;
private final VcsRevisionNumber myRevisionNumber;
private final FileAnnotation myAnnotation;
private final int myStopAtLine;
// we assume file has at least one line ;)
private int myCurrentLine; // to start looking for next line with revision from
private ContextLineIterator(final ContentsLines contentLines, final FileAnnotation annotation, final int stopAtLine) {
myAnnotation = annotation;
myRevisionNumber = myAnnotation.originalRevision(stopAtLine);
myStopAtLine = stopAtLine;
myContentsLines = contentLines;
}
@Override
public boolean hasNext() {
return lineNumberInBounds();
}
private boolean lineNumberInBounds() {
final int knownLinesNumber = myContentsLines.getKnownLinesNumber();
return ((knownLinesNumber == -1) || (myCurrentLine < knownLinesNumber)) && (myCurrentLine < myStopAtLine);
}
@Override
public String next() {
int nextLine;
while (lineNumberInBounds()) {
final VcsRevisionNumber vcsRevisionNumber = myAnnotation.originalRevision(myCurrentLine);
if (myRevisionNumber.equals(vcsRevisionNumber)) {
nextLine = myCurrentLine;
final String text = myContentsLines.getLineContents(nextLine);
if (!StringUtil.isEmptyOrSpaces(text)) {
++myCurrentLine;
return text;
}
}
++myCurrentLine;
}
return null;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
}