blob: ce6ca72ab99619b8261c73a4b0a13d62d138479b [file] [log] [blame]
/*
* Copyright 2000-2011 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.changes;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.diff.impl.ComparisonPolicy;
import com.intellij.openapi.diff.impl.external.DiffManagerImpl;
import com.intellij.openapi.diff.impl.fragments.LineFragment;
import com.intellij.openapi.diff.impl.highlighting.FragmentSide;
import com.intellij.openapi.diff.impl.processing.TextCompareProcessor;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.impl.DocumentImpl;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.UnfairTextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vcs.FilePath;
import com.intellij.openapi.vcs.FileStatus;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.changes.actions.ShowDiffAction;
import com.intellij.openapi.vcs.ex.Range;
import com.intellij.openapi.vcs.impl.LineStatusTrackerManager;
import com.intellij.openapi.vcs.impl.LineStatusTrackerManagerI;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.BeforeAfter;
import com.intellij.util.containers.SLRUMap;
import com.intellij.util.diff.FilesTooBigForDiffException;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @author irengrig
* Date: 6/15/11
* Time: 6:02 PM
*/
public class FragmentedDiffRequestFromChange {
private final Project myProject;
private final SLRUMap<Pair<Long, String>, List<BeforeAfter<TextRange>>> myRangesCache;
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vcs.changes.FragmentedDiffRequestFromChange");
public FragmentedDiffRequestFromChange(Project project) {
myProject = project;
myRangesCache = new SLRUMap<Pair<Long, String>, List<BeforeAfter<TextRange>>>(10, 10);
}
public static boolean canCreateRequest(Change change) {
if (ChangesUtil.isTextConflictingChange(change) || change.isTreeConflict() || change.isPhantom()) return false;
if (ShowDiffAction.isBinaryChange(change)) return false;
final FilePath filePath = ChangesUtil.getFilePath(change);
if (filePath.isDirectory()) return false;
return true;
}
public PreparedFragmentedContent getRanges(Change change) throws VcsException {
final FilePath filePath = ChangesUtil.getFilePath(change);
final RangesCalculator calculator = new RangesCalculator();
calculator.execute(change, filePath, myRangesCache, LineStatusTrackerManager.getInstance(myProject));
final VcsException exception = calculator.getException();
if (exception != null) {
LOG.info(exception);
throw exception;
}
List<BeforeAfter<TextRange>> ranges = calculator.getRanges();
if (ranges == null || ranges.isEmpty()) return null;
FragmentedContent fragmentedContent = new FragmentedContent(calculator.getOldDocument(), calculator.getDocument(), ranges, change);
VirtualFile file = filePath.getVirtualFile();
if (file == null) {
filePath.hardRefresh();
file = filePath.getVirtualFile();
}
final PreparedFragmentedContent preparedFragmentedContent = new PreparedFragmentedContent(myProject, fragmentedContent,
filePath.getName(), filePath.getFileType(),
change.getBeforeRevision() == null ? null : change.getBeforeRevision().getRevisionNumber(),
change.getAfterRevision() == null ? null : change.getAfterRevision().getRevisionNumber(), filePath, file);
return preparedFragmentedContent;
}
private static class RangesCalculator {
private List<BeforeAfter<TextRange>> myRanges;
private VcsException myException;
private Document myDocument;
private Document myOldDocument;
public Document getDocument() {
return myDocument;
}
public Document getOldDocument() {
return myOldDocument;
}
public void execute(final Change change,
final FilePath filePath,
final SLRUMap<Pair<Long, String>, List<BeforeAfter<TextRange>>> cache,
final LineStatusTrackerManagerI lstManager) {
try {
myDocument = null;
myOldDocument = documentFromRevision(change.getBeforeRevision());
final String convertedPath = FilePathsHelper.convertPath(filePath);
if (filePath.getVirtualFile() != null) {
myDocument = FileStatus.DELETED.equals(change.getFileStatus())
? new DocumentImpl("")
: FileDocumentManager.getInstance().getDocument(filePath.getVirtualFile());
if (myDocument != null) {
final List<BeforeAfter<TextRange>> cached = cache.get(new Pair<Long, String>(myDocument.getModificationStamp(), convertedPath));
if (cached != null) {
myRanges = cached;
return;
}
}
}
if (myDocument == null) {
myDocument = documentFromRevision(change.getAfterRevision());
final List<BeforeAfter<TextRange>> cached = cache.get(new Pair<Long, String>(-1L, convertedPath));
if (cached != null) {
myRanges = cached;
return;
}
}
ComparisonPolicy comparisonPolicy = DiffManagerImpl.getInstanceEx().getComparisonPolicy();
if (comparisonPolicy == null) {
comparisonPolicy = ComparisonPolicy.DEFAULT;
}
final TextCompareProcessor processor = new TextCompareProcessor(comparisonPolicy);
final List<LineFragment> lineFragments = processor.process(myOldDocument.getText(), myDocument.getText());
myRanges = new ArrayList<BeforeAfter<TextRange>>(lineFragments.size());
for (LineFragment lineFragment : lineFragments) {
if (!lineFragment.isEqual()) {
final TextRange oldRange = lineFragment.getRange(FragmentSide.SIDE1);
final TextRange newRange = lineFragment.getRange(FragmentSide.SIDE2);
int beforeBegin = myOldDocument.getLineNumber(oldRange.getStartOffset());
int beforeEnd = myOldDocument.getLineNumber(correctRangeEnd(oldRange.getEndOffset(), myOldDocument));
int afterBegin = myDocument.getLineNumber(newRange.getStartOffset());
int afterEnd = myDocument.getLineNumber(correctRangeEnd(newRange.getEndOffset(), myDocument));
if (oldRange.isEmpty()) {
beforeEnd = beforeBegin - 1;
}
if (newRange.isEmpty()) {
afterEnd = afterBegin - 1;
}
myRanges
.add(new BeforeAfter<TextRange>(new UnfairTextRange(beforeBegin, beforeEnd), new UnfairTextRange(afterBegin, afterEnd)));
}
}
cache
.put(new Pair<Long, String>(myDocument.getModificationStamp(), convertedPath), new ArrayList<BeforeAfter<TextRange>>(myRanges));
}
catch (VcsException e) {
myException = e;
}
catch (FilesTooBigForDiffException e) {
myException = new VcsException(e);
}
}
private int correctRangeEnd(final int end, final Document document) {
if (end == 0) return end;
return "\n".equals(document.getText(new TextRange(end - 1, end))) ? end - 1 : end;
}
public List<BeforeAfter<TextRange>> getRanges() {
return myRanges;
}
public VcsException getException() {
return myException;
}
private Document documentFromRevision(final ContentRevision cr) throws VcsException {
final Document oldDocument = new DocumentImpl(StringUtil.convertLineSeparators(notNullContentRevision(cr)),true);
// todo !!! a question how to show line separators in diff etc
// todo currently document doesn't allow to put \r as separator
oldDocument.setReadOnly(true);
return oldDocument;
}
private String notNullContentRevision(final ContentRevision cr) throws VcsException {
if (cr == null) return "";
String content = cr.getContent();
return content == null ? "" : content;
}
}
private static class MyWorker {
private final Document myDocument;
private final Document myOldDocument;
private final List<Range> myRanges;
private MyWorker(Document document, Document oldDocument, final List<Range> ranges) {
myDocument = document;
myOldDocument = oldDocument;
myRanges = ranges;
}
@Nullable
public List<BeforeAfter<TextRange>> execute() {
final List<BeforeAfter<TextRange>> result = new ArrayList<BeforeAfter<TextRange>>();
if (myRanges == null || myRanges.isEmpty()) return Collections.emptyList();
for (Range range : myRanges) {
final TextRange before = new TextRange(range.getVcsLine1(), range.getVcsLine2());
final TextRange after = new TextRange(range.getLine1(), range.getLine2());
result.add(new BeforeAfter<TextRange>(before, after));
}
return result;
}
}
}