VCS: line status tracker installing/removing fix. All changes to line status trackers model (that in LineStatusTrackerManager) will be done under write action. + dummy implementation if lst is forbidden. + just translate into current line in VCS annotation, no false tracker creation. + fix for "Diff for change" when last symbol in line is added

This commit is contained in:
irengrig
2010-11-01 13:43:38 +03:00
parent 88eb628049
commit 79ead28491
14 changed files with 933 additions and 727 deletions
@@ -27,6 +27,7 @@
<implementation-class>com.intellij.openapi.vcs.impl.VcsDirectoryMappingStorage</implementation-class> <implementation-class>com.intellij.openapi.vcs.impl.VcsDirectoryMappingStorage</implementation-class>
</component> </component>
<component> <component>
<interface-class>com.intellij.openapi.vcs.impl.LineStatusTrackerManagerI</interface-class>
<implementation-class>com.intellij.openapi.vcs.impl.LineStatusTrackerManager</implementation-class> <implementation-class>com.intellij.openapi.vcs.impl.LineStatusTrackerManager</implementation-class>
<option name="workspace" value="true"/> <option name="workspace" value="true"/>
<skipForDefaultProject/> <skipForDefaultProject/>
@@ -20,6 +20,7 @@ import com.intellij.openapi.actionSystem.Presentation;
import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
import com.intellij.openapi.vcs.ex.LineStatusTracker; import com.intellij.openapi.vcs.ex.LineStatusTracker;
import com.intellij.openapi.vcs.ex.LineStatusTrackerDrawing;
import com.intellij.openapi.vcs.ex.Range; import com.intellij.openapi.vcs.ex.Range;
import com.intellij.openapi.vcs.impl.LineStatusTrackerManager; import com.intellij.openapi.vcs.impl.LineStatusTrackerManager;
@@ -92,7 +93,8 @@ public abstract class ShowChangeMarkerAction extends AbstractVcsAction {
LineStatusTracker lineStatusTracker = myChangeMarkerContext.getLineStatusTracker(context); LineStatusTracker lineStatusTracker = myChangeMarkerContext.getLineStatusTracker(context);
Range range = myChangeMarkerContext.getRange(context); Range range = myChangeMarkerContext.getRange(context);
lineStatusTracker.moveToRange(range, editor);
LineStatusTrackerDrawing.moveToRange(range, editor, lineStatusTracker);
} }
protected interface ChangeMarkerContext { protected interface ChangeMarkerContext {
@@ -0,0 +1,42 @@
/*
* 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.ex;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.project.DumbAware;
import javax.swing.*;
/**
* @author irengrig
*/
public abstract class BaseLineStatusRangeAction extends AnAction implements DumbAware {
protected final LineStatusTracker myLineStatusTracker;
protected final Range myRange;
BaseLineStatusRangeAction(final String text, final Icon icon, final LineStatusTracker lineStatusTracker, final Range range) {
super(text, null, icon);
myLineStatusTracker = lineStatusTracker;
myRange = range;
}
public void update(final AnActionEvent e) {
e.getPresentation().setEnabled(isEnabled());
}
public abstract boolean isEnabled();
}
@@ -0,0 +1,41 @@
/*
* 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.ex;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.ide.CopyPasteManager;
import com.intellij.openapi.util.IconLoader;
import com.intellij.openapi.vcs.VcsBundle;
import java.awt.datatransfer.StringSelection;
/**
* @author irengrig
*/
public class CopyLineStatusRangeAction extends BaseLineStatusRangeAction {
CopyLineStatusRangeAction(final LineStatusTracker lineStatusTracker, final Range range) {
super(VcsBundle.message("action.name.copy.old.text"), IconLoader.getIcon("/actions/copy.png"), lineStatusTracker, range);
}
public boolean isEnabled() {
return Range.DELETED == myRange.getType() || Range.MODIFIED == myRange.getType();
}
public void actionPerformed(final AnActionEvent e) {
final String content = myLineStatusTracker.getUpToDateContent(myRange);
CopyPasteManager.getInstance().setContents(new StringSelection(content));
}
}
@@ -16,6 +16,7 @@
package com.intellij.openapi.vcs.ex; package com.intellij.openapi.vcs.ex;
import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Document;
import com.intellij.openapi.util.TextRange;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -53,10 +54,8 @@ public class DocumentWrapper {
return result; return result;
} }
private String getLine(int i) { private String getLine(final int i) {
int lineStartOffset = myDocument.getLineStartOffset(i); return myDocument.getText(new TextRange(myDocument.getLineStartOffset(i), myDocument.getLineEndOffset(i)));
String line = myDocument.getCharsSequence().subSequence(lineStartOffset, myDocument.getLineEndOffset(i)).toString();
return line;
} }
} }
@@ -15,135 +15,111 @@
*/ */
package com.intellij.openapi.vcs.ex; package com.intellij.openapi.vcs.ex;
import com.intellij.codeInsight.hint.EditorFragmentComponent; import com.intellij.openapi.application.Application;
import com.intellij.codeInsight.hint.HintManagerImpl;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.command.undo.UndoManager; import com.intellij.openapi.command.undo.UndoManager;
import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.diff.*;
import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorFactory; import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.editor.ScrollType;
import com.intellij.openapi.editor.colors.EditorColors;
import com.intellij.openapi.editor.colors.EditorColorsManager;
import com.intellij.openapi.editor.colors.EditorColorsScheme;
import com.intellij.openapi.editor.colors.TextAttributesKey;
import com.intellij.openapi.editor.event.DocumentAdapter; import com.intellij.openapi.editor.event.DocumentAdapter;
import com.intellij.openapi.editor.event.DocumentEvent; import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.editor.ex.DocumentEx;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.ex.EditorGutterComponentEx;
import com.intellij.openapi.editor.highlighter.EditorHighlighter;
import com.intellij.openapi.editor.highlighter.EditorHighlighterFactory;
import com.intellij.openapi.editor.markup.*; import com.intellij.openapi.editor.markup.*;
import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.ide.CopyPasteManager;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.IconLoader; import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vcs.VcsBundle; import com.intellij.openapi.vcs.VcsBundle;
import com.intellij.openapi.vcs.actions.ShowNextChangeMarkerAction;
import com.intellij.openapi.vcs.actions.ShowPrevChangeMarkerAction;
import com.intellij.openapi.vfs.ReadonlyStatusHandler;
import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.ColoredSideBorder;
import com.intellij.ui.HintListener;
import com.intellij.ui.LightweightHint;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import javax.swing.*; import java.util.ArrayList;
import java.awt.*; import java.util.Iterator;
import java.awt.datatransfer.StringSelection;
import java.awt.event.MouseEvent;
import java.util.*;
import java.util.List; import java.util.List;
import java.util.ListIterator;
/** /**
* @author irengrig
* author: lesya * author: lesya
*/ */
public class LineStatusTracker { public class LineStatusTracker {
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vcs.ex.LineStatusTracker"); private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vcs.ex.LineStatusTracker");
// true -> have contents
private boolean myBaseLoaded;
private final Document myDocument; private final Document myDocument;
private final Document myUpToDateDocument; private final Document myUpToDateDocument;
@SuppressWarnings({"FieldAccessedSynchronizedAndUnsynchronized"}) private List<Range> myRanges = new ArrayList<Range>();
private List<Range> myRanges;
private final Project myProject; private final Project myProject;
@SuppressWarnings({"FieldAccessedSynchronizedAndUnsynchronized"}) private int myHighlighterCount = 0;
private MyDocumentListener myDocumentListener; private MyDocumentListener myDocumentListener;
private boolean myIsReleased = false;
private boolean myIsInitialized = false;
private boolean myBulkUpdate; private boolean myBulkUpdate;
private final Application myApplication;
public LineStatusTracker(Document document, Document upToDateDocument, Project project) { private LineStatusTracker(final Document document, final Document upToDateDocument, final Project project) {
myApplication = ApplicationManager.getApplication();
myDocument = document; myDocument = document;
myUpToDateDocument = upToDateDocument; myUpToDateDocument = upToDateDocument;
myUpToDateDocument.putUserData(UndoManager.DONT_RECORD_UNDO, Boolean.TRUE); myUpToDateDocument.putUserData(UndoManager.DONT_RECORD_UNDO, Boolean.TRUE);
myProject = project; myProject = project;
myBaseLoaded = false;
myRanges = new ArrayList<Range>();
} }
public synchronized void initialize(@NotNull final String upToDateContent) { public void initialize(@NotNull final String upToDateContent) {
if (myIsReleased) return; ApplicationManager.getApplication().isReadAccessAllowed();
LOG.assertTrue(!myIsInitialized); LOG.assertTrue(!myBaseLoaded);
try {
ApplicationManager.getApplication().runWriteAction(new Runnable() { ApplicationManager.getApplication().runWriteAction(new Runnable() {
public void run() { public void run() {
try {
myUpToDateDocument.setReadOnly(false);
myUpToDateDocument.replaceString(0, myUpToDateDocument.getTextLength(), StringUtil.convertLineSeparators(upToDateContent)); myUpToDateDocument.replaceString(0, myUpToDateDocument.getTextLength(), StringUtil.convertLineSeparators(upToDateContent));
myUpToDateDocument.setReadOnly(true);
reinstallRanges();
if (myDocumentListener == null) {
myDocumentListener = new MyDocumentListener();
myDocument.addDocumentListener(myDocumentListener);
}
} }
}); finally {
myBaseLoaded = true;
myUpToDateDocument.setReadOnly(true); }
reinstallRanges(); }
});
myDocumentListener = new MyDocumentListener();
myDocument.addDocumentListener(myDocumentListener);
}
finally {
myIsInitialized = true;
}
} }
private synchronized void reinstallRanges() { private void reinstallRanges() {
reinstallRanges(new RangesBuilder(myDocument, myUpToDateDocument).getRanges()); myApplication.assertWriteAccessAllowed();
}
private void reinstallRanges(List<Range> ranges) { removeHighlightersFromMarkupModel();
removeHighlighters(); myRanges = new RangesBuilder(myDocument, myUpToDateDocument).getRanges();
myRanges = ranges; for (final Range range : myRanges) {
addHighlighters(); range.setHighlighter(createHighlighter(range));
}
private void addHighlighters() {
for (Range range : myRanges) {
if (!range.hasHighlighter()) range.setHighlighter(createHighlighter(range));
} }
} }
@SuppressWarnings({"AutoBoxing"}) @SuppressWarnings({"AutoBoxing"})
synchronized private RangeHighlighter createHighlighter(Range range) { private RangeHighlighter createHighlighter(final Range range) {
int first = int first =
range.getOffset1() >= myDocument.getLineCount() ? myDocument.getTextLength() : myDocument.getLineStartOffset(range.getOffset1()); range.getOffset1() >= myDocument.getLineCount() ? myDocument.getTextLength() : myDocument.getLineStartOffset(range.getOffset1());
int second = int second =
range.getOffset2() >= myDocument.getLineCount() ? myDocument.getTextLength() : myDocument.getLineStartOffset(range.getOffset2()); range.getOffset2() >= myDocument.getLineCount() ? myDocument.getTextLength() : myDocument.getLineStartOffset(range.getOffset2());
final RangeHighlighter highlighter = myDocument.getMarkupModel(myProject)
RangeHighlighter highlighter = myDocument.getMarkupModel(myProject)
.addRangeHighlighter(first, second, HighlighterLayer.FIRST - 1, null, HighlighterTargetArea.LINES_IN_RANGE); .addRangeHighlighter(first, second, HighlighterLayer.FIRST - 1, null, HighlighterTargetArea.LINES_IN_RANGE);
myHighlighterCount++; final TextAttributes attr = LineStatusTrackerDrawing.getAttributesFor(range);
TextAttributes attr = getAttributesFor(range);
highlighter.setErrorStripeMarkColor(attr.getErrorStripeColor()); highlighter.setErrorStripeMarkColor(attr.getErrorStripeColor());
highlighter.setThinErrorStripeMark(true); highlighter.setThinErrorStripeMark(true);
highlighter.setGreedyToLeft(true); highlighter.setGreedyToLeft(true);
highlighter.setGreedyToRight(true); highlighter.setGreedyToRight(true);
highlighter.setLineMarkerRenderer(createRenderer(range)); highlighter.setLineMarkerRenderer(LineStatusTrackerDrawing.createRenderer(range, this));
highlighter.setEditorFilter(MarkupEditorFilterFactory.createIsNotDiffFilter()); highlighter.setEditorFilter(MarkupEditorFilterFactory.createIsNotDiffFilter());
final int line1 = myDocument.getLineNumber(first); final int line1 = myDocument.getLineNumber(first);
final int line2 = myDocument.getLineNumber(second); final int line2 = myDocument.getLineNumber(second);
@@ -159,141 +135,17 @@ public class LineStatusTracker {
return highlighter; return highlighter;
} }
public void release() {
private void removeHighlighters() { myApplication.runWriteAction(new Runnable() {
for (Range oldRange : myRanges) { @Override
removeHighlighter(oldRange.getHighlighter()); public void run() {
oldRange.setHighlighter(null); if (myDocumentListener != null) {
} myDocument.removeDocumentListener(myDocumentListener);
} }
removeHighlightersFromMarkupModel();
synchronized void removeHighlighter(RangeHighlighter highlighter) { myRanges.clear();
if (highlighter == null) return;
MarkupModel markupModel = myDocument.getMarkupModel(myProject);
//noinspection ConstantConditions
if (markupModel == null) return;
markupModel.removeHighlighter(highlighter);
myHighlighterCount--;
}
private static TextAttributesKey getDiffColor(Range range) {
switch (range.getType()) {
case Range.INSERTED:
return DiffColors.DIFF_INSERTED;
case Range.DELETED:
return DiffColors.DIFF_DELETED;
case Range.MODIFIED:
return DiffColors.DIFF_MODIFIED;
default:
assert false;
return null;
}
}
private static TextAttributesKey getEditorColorNameFor(Range range) {
switch (range.getType()) {
case Range.MODIFIED:
return DiffColors.DIFF_MODIFIED;
case Range.DELETED:
return DiffColors.DIFF_DELETED;
default:
return DiffColors.DIFF_INSERTED;
}
}
private static TextAttributes getAttributesFor(Range range) {
final EditorColorsScheme globalScheme = EditorColorsManager.getInstance().getGlobalScheme();
final Color stripeColor = globalScheme.getAttributes(getEditorColorNameFor(range)).getErrorStripeColor();
final TextAttributes textAttributes = new TextAttributes(null, stripeColor, null, EffectType.BOXED, Font.PLAIN);
textAttributes.setErrorStripeColor(stripeColor);
return textAttributes;
}
private static void paintGutterFragment(Editor editor, Graphics g, Rectangle r, TextAttributesKey diffAttributeKey) {
final EditorGutterComponentEx gutter = ((EditorEx)editor).getGutterComponentEx();
final Color stripeColor = editor.getColorsScheme().getAttributes(diffAttributeKey).getErrorStripeColor();
g.setColor(brighter(stripeColor));
int endX = gutter.getWhitespaceSeparatorOffset();
int x = r.x + r.width - 2;
int width = endX - x;
if (r.height > 0) {
g.fillRect(x, r.y + 2, width, r.height - 4);
g.setColor(gutter.getOutlineColor(false));
UIUtil.drawLine(g, x, r.y + 2, x + width, r.y + 2);
UIUtil.drawLine(g, x, r.y + 2, x, r.y + r.height - 3);
UIUtil.drawLine(g, x, r.y + r.height - 3, x + width, r.y + r.height - 3);
}
else {
int[] xPoints = new int[]{x,
x,
x + width - 1};
int[] yPoints = new int[]{r.y - 4,
r.y + 4,
r.y};
g.fillPolygon(xPoints, yPoints, 3);
g.setColor(gutter.getOutlineColor(false));
g.drawPolygon(xPoints, yPoints, 3);
}
}
@Nullable
private static Color brighter(final Color color) {
if (color == null) {
return null;
}
final float[] hsbStripeColor = Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), null);
if (hsbStripeColor[1] < 0.02f) {
// color is grey
hsbStripeColor[2] = Math.min(1.0f, hsbStripeColor[2] * 1.3f);
} else {
// let's decrease color saturation
hsbStripeColor[1] *= 0.3;
// max brightness
hsbStripeColor[2] = 1.0f;
}
return Color.getHSBColor(hsbStripeColor[0], hsbStripeColor[1], hsbStripeColor[2]);
}
private LineMarkerRenderer createRenderer(final Range range) {
return new ActiveGutterRenderer() {
public void paint(Editor editor, Graphics g, Rectangle r) {
paintGutterFragment(editor, g, r, getDiffColor(range));
} }
});
public void doAction(Editor editor, MouseEvent e) {
e.consume();
JComponent comp = (JComponent)e.getComponent(); // shall be EditorGutterComponent, cast is safe.
JLayeredPane layeredPane = comp.getRootPane().getLayeredPane();
Point point = SwingUtilities.convertPoint(comp, ((EditorEx)editor).getGutterComponentEx().getWidth(), e.getY(), layeredPane);
showActiveHint(range, editor, point);
}
public boolean canDoAction(final MouseEvent e) {
final EditorGutterComponentEx gutter = (EditorGutterComponentEx)e.getComponent();
return e.getX() > gutter.getLineMarkerAreaOffset() + gutter.getIconsAreaWidth();
}
};
}
public synchronized void release() {
try {
if (!myIsInitialized) return;
LOG.assertTrue(!myIsReleased);
removeHighlighters();
if (myDocumentListener != null) {
myDocument.removeDocumentListener(myDocumentListener);
myDocumentListener = null;
}
}
finally {
myIsReleased = true;
}
} }
public Document getDocument() { public Document getDocument() {
@@ -305,6 +157,8 @@ public class LineStatusTracker {
} }
public List<Range> getRanges() { public List<Range> getRanges() {
myApplication.assertReadAccessAllowed();
return myRanges; return myRanges;
} }
@@ -313,19 +167,56 @@ public class LineStatusTracker {
} }
public void startBulkUpdate() { public void startBulkUpdate() {
myBulkUpdate = true; myApplication.runWriteAction(new Runnable() {
for (Range oldRange : myRanges) { @Override
removeHighlighter(oldRange.getHighlighter()); public void run() {
oldRange.setHighlighter(null); myBulkUpdate = true;
removeHighlightersFromMarkupModel();
myRanges.clear();
}
});
}
private void removeHighlightersFromMarkupModel() {
final MarkupModel markupModel = myDocument.getMarkupModel(myProject);
for (Range range : myRanges) {
markupModel.removeHighlighter(range.getHighlighter());
} }
myRanges.clear();
} }
public void finishBulkUpdate() { public void finishBulkUpdate() {
if (myBulkUpdate) { myApplication.runWriteAction(new Runnable() {
myBulkUpdate = false; @Override
reinstallRanges(); public void run() {
} myBulkUpdate = false;
reinstallRanges();
}
});
}
/**
* @return true if was cleared and base revision contents load should be started
* false -> load was already started; after contents is loaded,
*/
public boolean resetForBaseRevisionLoad() {
return myApplication.runReadAction(new Computable<Boolean>() {
@Override
public Boolean compute() {
if (!myBaseLoaded) return false;
return myApplication.runWriteAction(new Computable<Boolean>() {
@Override
public Boolean compute() {
myUpToDateDocument.setReadOnly(false);
myUpToDateDocument.setText("");
myUpToDateDocument.setReadOnly(true);
removeHighlightersFromMarkupModel();
myRanges.clear();
myBaseLoaded = false;
return true;
}
});
}
});
} }
private class MyDocumentListener extends DocumentAdapter { private class MyDocumentListener extends DocumentAdapter {
@@ -336,7 +227,9 @@ public class LineStatusTracker {
private int myLinesBeforeChange; private int myLinesBeforeChange;
public void beforeDocumentChange(DocumentEvent e) { public void beforeDocumentChange(DocumentEvent e) {
if (myBulkUpdate) return; if (myBulkUpdate || (! myBaseLoaded)) return;
myApplication.assertWriteAccessAllowed();
myFirstChangedLine = myDocument.getLineNumber(e.getOffset()); myFirstChangedLine = myDocument.getLineNumber(e.getOffset());
myLastChangedLine = myDocument.getLineNumber(e.getOffset() + e.getOldLength()); myLastChangedLine = myDocument.getLineNumber(e.getOffset() + e.getOldLength());
if (StringUtil.endsWithChar(e.getOldFragment(), '\n')) myLastChangedLine++; if (StringUtil.endsWithChar(e.getOldFragment(), '\n')) myLastChangedLine++;
@@ -382,14 +275,15 @@ public class LineStatusTracker {
} }
public void documentChanged(DocumentEvent e) { public void documentChanged(DocumentEvent e) {
if (myBulkUpdate) return; if (myBulkUpdate || (! myBaseLoaded)) return;
myApplication.assertWriteAccessAllowed();
int line = myDocument.getLineNumber(e.getOffset() + e.getNewLength()); int line = myDocument.getLineNumber(e.getOffset() + e.getNewLength());
int linesAfterChange = line - myDocument.getLineNumber(e.getOffset()); int linesAfterChange = line - myDocument.getLineNumber(e.getOffset());
int linesShift = linesAfterChange - myLinesBeforeChange; int linesShift = linesAfterChange - myLinesBeforeChange;
List<Range> rangesAfterChange = getRangesAfter(myLastChangedLine); List<Range> rangesAfterChange = getRangesAfter(myRanges, myLastChangedLine);
List<Range> rangesBeforeChange = getRangesBefore(myFirstChangedLine); List<Range> rangesBeforeChange = getRangesBefore(myRanges, myFirstChangedLine);
List<Range> changedRanges = getChangedRanges(myFirstChangedLine, myLastChangedLine); List<Range> changedRanges = getChangedRanges(myFirstChangedLine, myLastChangedLine);
@@ -416,19 +310,10 @@ public class LineStatusTracker {
myRanges.addAll(newChangedRanges); myRanges.addAll(newChangedRanges);
myRanges.addAll(rangesAfterChange); myRanges.addAll(rangesAfterChange);
if (myHighlighterCount != myRanges.size()) {
LOG.error("Highlighters: " + myHighlighterCount + ", ranges: " + myRanges.size());
}
myRanges = mergeRanges(myRanges); myRanges = mergeRanges(myRanges);
for (Range range : myRanges) { for (Range range : myRanges) {
if (!range.hasHighlighter()) range.setHighlighter(createHighlighter(range)); if (!range.hasHighlighter()) range.setHighlighter(createHighlighter(range));
}
if (myHighlighterCount != myRanges.size()) {
LOG.error("Highlighters: " + myHighlighterCount + ", ranges: " + myRanges.size());
} }
} }
@@ -442,6 +327,8 @@ public class LineStatusTracker {
} }
private List<Range> mergeRanges(List<Range> ranges) { private List<Range> mergeRanges(List<Range> ranges) {
final MarkupModel markupModel = myDocument.getMarkupModel(myProject);
ArrayList<Range> result = new ArrayList<Range>(); ArrayList<Range> result = new ArrayList<Range>();
Iterator<Range> iterator = ranges.iterator(); Iterator<Range> iterator = ranges.iterator();
if (!iterator.hasNext()) return result; if (!iterator.hasNext()) return result;
@@ -449,6 +336,8 @@ public class LineStatusTracker {
while (iterator.hasNext()) { while (iterator.hasNext()) {
Range range = iterator.next(); Range range = iterator.next();
if (prev.canBeMergedWith(range)) { if (prev.canBeMergedWith(range)) {
markupModel.removeHighlighter(range.getHighlighter());
markupModel.removeHighlighter(prev.getHighlighter());
prev = prev.mergeWith(range, LineStatusTracker.this); prev = prev.mergeWith(range, LineStatusTracker.this);
} }
else { else {
@@ -461,8 +350,10 @@ public class LineStatusTracker {
} }
private void replaceRanges(List<Range> rangesInChange, List<Range> newRangesInChange) { private void replaceRanges(List<Range> rangesInChange, List<Range> newRangesInChange) {
final MarkupModel markupModel = myDocument.getMarkupModel(myProject);
for (Range range : rangesInChange) { for (Range range : rangesInChange) {
removeHighlighter(range.getHighlighter()); markupModel.removeHighlighter(range.getHighlighter());
range.setHighlighter(null); range.setHighlighter(null);
} }
for (Range range : newRangesInChange) { for (Range range : newRangesInChange) {
@@ -491,9 +382,51 @@ public class LineStatusTracker {
return result; return result;
} }
private List<Range> getRangesBefore(int line) { @Nullable
return getRangesBefore(myRanges, line); Range getNextRange(final Range range) {
final int index = myRanges.indexOf(range);
if (index == myRanges.size() - 1) return null;
return myRanges.get(index + 1);
}
@Nullable
Range getPrevRange(final Range range) {
final int index = myRanges.indexOf(range);
if (index <= 0) return null;
return myRanges.get(index - 1);
}
@Nullable
public Range getNextRange(final int line) {
final Range currentRange = getRangeForLine(line);
if (currentRange != null) {
return getNextRange(currentRange);
}
for (final Range range : myRanges) {
if (line > range.getOffset1() || line > range.getOffset2()) {
continue;
}
return range;
}
return null;
}
@Nullable
public Range getPrevRange(final int line) {
final Range currentRange = getRangeForLine(line);
if (currentRange != null) {
return getPrevRange(currentRange);
}
for (ListIterator<Range> iterator = myRanges.listIterator(myRanges.size()); iterator.hasPrevious();) {
final Range range = iterator.previous();
if (range.getOffset1() > line) {
continue;
}
return range;
}
return null;
} }
public static List<Range> getRangesBefore(List<Range> ranges, int line) { public static List<Range> getRangesBefore(List<Range> ranges, int line) {
@@ -506,10 +439,6 @@ public class LineStatusTracker {
return result; return result;
} }
private List<Range> getRangesAfter(int line) {
return getRangesAfter(myRanges, line);
}
public static List<Range> getRangesAfter(List<Range> ranges, int line) { public static List<Range> getRangesAfter(List<Range> ranges, int line) {
ArrayList<Range> result = new ArrayList<Range>(); ArrayList<Range> result = new ArrayList<Range>();
for (Range range : ranges) { for (Range range : ranges) {
@@ -518,69 +447,6 @@ public class LineStatusTracker {
return result; return result;
} }
public void moveToRange(final Range range, final Editor editor) {
final int firstOffset = myDocument.getLineStartOffset(Math.min(range.getOffset1(), myDocument.getLineCount() - 1));
editor.getCaretModel().moveToOffset(firstOffset);
editor.getScrollingModel().scrollToCaret(ScrollType.CENTER);
editor.getScrollingModel().runActionOnScrollingFinished(new Runnable() {
public void run() {
Point p = editor.visualPositionToXY(editor.offsetToVisualPosition(firstOffset));
JComponent editorComponent = editor.getContentComponent();
JLayeredPane layeredPane = editorComponent.getRootPane().getLayeredPane();
p = SwingUtilities.convertPoint(editorComponent, 0, p.y, layeredPane);
showActiveHint(range, editor, p);
}
});
}
@Nullable
private Range getNextRange(Range range) {
int index = myRanges.indexOf(range);
if (index == myRanges.size() - 1) return null;
return myRanges.get(index + 1);
}
@Nullable
private Range getPrevRange(Range range) {
int index = myRanges.indexOf(range);
if (index <= 0) return null;
return myRanges.get(index - 1);
}
@Nullable
public Range getNextRange(int line) {
final Range currentRange = getRangeForLine(line);
if (currentRange != null) {
return getNextRange(currentRange);
}
for (Range range : myRanges) {
if (line > range.getOffset1() || line > range.getOffset2()) {
continue;
}
return range;
}
return null;
}
@Nullable
public Range getPrevRange(int line) {
final Range currentRange = getRangeForLine(line);
if (currentRange != null) {
return getPrevRange(currentRange);
}
for (ListIterator<Range> iterator = myRanges.listIterator(myRanges.size()); iterator.hasPrevious();) {
Range range = iterator.previous();
if (range.getOffset1() > line) {
continue;
}
return range;
}
return null;
}
@Nullable @Nullable
public Range getRangeForLine(final int line) { public Range getRangeForLine(final int line) {
for (final Range range : myRanges) { for (final Range range : myRanges) {
@@ -594,63 +460,9 @@ public class LineStatusTracker {
return null; return null;
} }
public static abstract class MyAction extends AnAction implements DumbAware { public void rollbackChanges(final Range range) {
protected final LineStatusTracker myLineStatusTracker; myApplication.assertWriteAccessAllowed();
protected final Range myRange;
protected MyAction(String text, Icon icon, LineStatusTracker lineStatusTracker, Range range) {
super(text, null, icon);
myLineStatusTracker = lineStatusTracker;
myRange = range;
}
public void update(AnActionEvent e) {
e.getPresentation().setEnabled(isEnabled());
}
public abstract boolean isEnabled();
protected int getMyRangeIndex() {
List<Range> ranges = myLineStatusTracker.getRanges();
for (int i = 0; i < ranges.size(); i++) {
Range range = ranges.get(i);
if (range.getOffset1() == myRange.getOffset1() && range.getOffset2() == myRange.getOffset2()) {
return i;
}
}
return -1;
}
}
public static class RollbackAction extends LineStatusTracker.MyAction {
public RollbackAction(LineStatusTracker lineStatusTracker, Range range, Editor editor) {
super(VcsBundle.message("action.name.rollback"), IconLoader.getIcon("/actions/reset.png"), lineStatusTracker, range);
}
public boolean isEnabled() {
return true;
}
public void actionPerformed(AnActionEvent e) {
CommandProcessor.getInstance().executeCommand(myLineStatusTracker.getProject(), new Runnable() {
public void run() {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
public void run() {
if (!myLineStatusTracker.getDocument().isWritable()) {
final ReadonlyStatusHandler.OperationStatus operationStatus = ReadonlyStatusHandler
.getInstance(myLineStatusTracker.getProject()).ensureFilesWritable(myLineStatusTracker.getVirtualFile());
if (operationStatus.hasReadonlyFiles()) return;
}
myLineStatusTracker.rollbackChanges(myRange);
}
});
}
}, VcsBundle.message("command.name.rollback.change"), null);
}
}
public void rollbackChanges(Range range) {
TextRange currentTextRange = getCurrentTextRange(range); TextRange currentTextRange = getCurrentTextRange(range);
if (range.getType() == Range.INSERTED) { if (range.getType() == Range.INSERTED) {
@@ -676,81 +488,25 @@ public class LineStatusTracker {
return myUpToDateDocument.getCharsSequence().subSequence(startOffset, endOffset).toString(); return myUpToDateDocument.getCharsSequence().subSequence(startOffset, endOffset).toString();
} }
private Project getProject() { Project getProject() {
return myProject; return myProject;
} }
public class ShowDiffAction extends LineStatusTracker.MyAction { TextRange getCurrentTextRange(Range range) {
public ShowDiffAction(LineStatusTracker lineStatusTracker, Range range, Editor editor) { return getRange(range.getType(), range.getOffset1(), range.getOffset2(), Range.DELETED, myDocument, false);
super(VcsBundle.message("action.name.show.difference"), IconLoader.getIcon("/actions/diff.png"), lineStatusTracker, range);
}
public boolean isEnabled() {
return isModifiedRange() || isDeletedRange();
}
private boolean isDeletedRange() {
return myRange.getType() == Range.DELETED;
}
private boolean isModifiedRange() {
return myRange.getType() == Range.MODIFIED;
}
public void actionPerformed(AnActionEvent e) {
DiffManager.getInstance().getDiffTool().show(createDiffData());
}
private DiffRequest createDiffData() {
return new DiffRequest(myLineStatusTracker.getProject()) {
public DiffContent[] getContents() {
return new DiffContent[]{createDiffContent(myLineStatusTracker.getUpToDateDocument(),
myLineStatusTracker.getUpToDateRange(myRange), null),
createDiffContent(myLineStatusTracker.getDocument(), myLineStatusTracker.getCurrentTextRange(myRange),
myLineStatusTracker.getVirtualFile())};
}
public String[] getContentTitles() {
return new String[]{VcsBundle.message("diff.content.title.up.to.date"),
VcsBundle.message("diff.content.title.current.range")};
}
public String getWindowTitle() {
return VcsBundle.message("dialog.title.diff.for.range");
}
};
}
private DiffContent createDiffContent(final Document uDocument, TextRange textRange, VirtualFile file) {
DiffContent diffContent = new DocumentContent(myProject, uDocument);
return new FragmentContent(diffContent, textRange, myLineStatusTracker.getProject(), file);
}
} }
public class CopyAction extends MyAction { TextRange getUpToDateRange(Range range) {
protected CopyAction(LineStatusTracker lineStatusTracker, Range range) { return getRange(range.getType(), range.getUOffset1(), range.getUOffset2(), Range.INSERTED, myUpToDateDocument, false);
super(VcsBundle.message("action.name.copy.old.text"), IconLoader.getIcon("/actions/copy.png"), lineStatusTracker, range);
}
public boolean isEnabled() {
return myRange.getType() == Range.DELETED || myRange.getType() == Range.MODIFIED;
}
public void actionPerformed(final AnActionEvent e) {
final String content = myLineStatusTracker.getUpToDateContent(myRange);
CopyPasteManager.getInstance().setContents(new StringSelection(content));
}
} }
private TextRange getCurrentTextRange(Range range) { // a hack
return getRange(range.getType(), range.getOffset1(), range.getOffset2(), Range.DELETED, myDocument); TextRange getUpToDateRangeWithEndSymbol(Range range) {
return getRange(range.getType(), range.getUOffset1(), range.getUOffset2(), Range.INSERTED, myUpToDateDocument, true);
} }
private TextRange getUpToDateRange(Range range) { private static TextRange getRange(byte rangeType, int offset1, int offset2, byte emptyRangeCondition, Document document,
return getRange(range.getType(), range.getUOffset1(), range.getUOffset2(), Range.INSERTED, myUpToDateDocument); final boolean keepEnd) {
}
private static TextRange getRange(byte rangeType, int offset1, int offset2, byte emptyRangeCondition, Document document) {
if (rangeType == emptyRangeCondition) { if (rangeType == emptyRangeCondition) {
int lineStartOffset; int lineStartOffset;
if (offset1 == 0) { if (offset1 == 0) {
@@ -767,104 +523,28 @@ public class LineStatusTracker {
int startOffset = document.getLineStartOffset(offset1); int startOffset = document.getLineStartOffset(offset1);
int endOffset = document.getLineEndOffset(offset2 - 1); int endOffset = document.getLineEndOffset(offset2 - 1);
if (startOffset > 0) { if (startOffset > 0) {
startOffset--; -- startOffset;
endOffset--; if (! keepEnd) {
-- endOffset;
}
} }
return new TextRange(startOffset, endOffset); return new TextRange(startOffset, endOffset);
} }
} }
public static LineStatusTracker createOn(final Document doc, final String upToDateContent, final Project project) {
public void showActiveHint(Range range, final Editor editor, Point point) { final Document document = EditorFactory.getInstance().createDocument(StringUtil.convertLineSeparators(upToDateContent));
DefaultActionGroup group = new DefaultActionGroup();
final AnAction globalShowNextAction = ActionManager.getInstance().getAction("VcsShowNextChangeMarker");
final AnAction globalShowPrevAction = ActionManager.getInstance().getAction("VcsShowPrevChangeMarker");
final ShowPrevChangeMarkerAction localShowPrevAction = new ShowPrevChangeMarkerAction(getPrevRange(range), this, editor);
final ShowNextChangeMarkerAction localShowNextAction = new ShowNextChangeMarkerAction(getNextRange(range), this, editor);
JComponent editorComponent = editor.getComponent();
localShowNextAction.registerCustomShortcutSet(localShowNextAction.getShortcutSet(), editorComponent);
localShowPrevAction.registerCustomShortcutSet(localShowPrevAction.getShortcutSet(), editorComponent);
group.add(localShowPrevAction);
group.add(localShowNextAction);
localShowNextAction.copyFrom(globalShowNextAction);
localShowPrevAction.copyFrom(globalShowPrevAction);
group.add(new RollbackAction(this, range, editor));
group.add(new ShowDiffAction(this, range, editor));
group.add(new CopyAction(this, range));
final List<AnAction> actionList = (List<AnAction>)editorComponent.getClientProperty(AnAction.ourClientProperty);
actionList.remove(globalShowPrevAction);
actionList.remove(globalShowNextAction);
JComponent toolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.FILEHISTORY_VIEW_TOOLBAR, group, true).getComponent();
final Color background = ((EditorEx)editor).getBackgroundColor();
final Color foreground = editor.getColorsScheme().getColor(EditorColors.CARET_COLOR);
toolbar.setBackground(background);
toolbar.setBorder(new ColoredSideBorder(foreground, foreground, range.getType() != Range.INSERTED ? null : foreground, foreground, 1));
JPanel component = new JPanel(new BorderLayout());
component.setOpaque(false);
JPanel toolbarPanel = new JPanel(new BorderLayout());
toolbarPanel.setOpaque(false);
toolbarPanel.add(toolbar, BorderLayout.WEST);
component.add(toolbarPanel, BorderLayout.NORTH);
if (range.getType() != Range.INSERTED) {
DocumentEx doc = (DocumentEx)myUpToDateDocument;
EditorEx uEditor = (EditorEx)EditorFactory.getInstance().createViewer(doc, myProject);
EditorHighlighter highlighter = EditorHighlighterFactory.getInstance().createEditorHighlighter(myProject, getFileName());
uEditor.setHighlighter(highlighter);
EditorFragmentComponent editorFragmentComponent =
EditorFragmentComponent.createEditorFragmentComponent(uEditor, range.getUOffset1(), range.getUOffset2(), false, false);
component.add(editorFragmentComponent, BorderLayout.CENTER);
EditorFactory.getInstance().releaseEditor(uEditor);
}
LightweightHint lightweightHint = new LightweightHint(component);
lightweightHint.addHintListener(new HintListener() {
public void hintHidden(EventObject event) {
actionList.remove(localShowPrevAction);
actionList.remove(localShowNextAction);
actionList.add(globalShowPrevAction);
actionList.add(globalShowNextAction);
}
});
HintManagerImpl.getInstanceImpl().showEditorHint(lightweightHint, editor, point, HintManagerImpl.HIDE_BY_ANY_KEY | HintManagerImpl.HIDE_BY_TEXT_CHANGE |
HintManagerImpl.HIDE_BY_OTHER_HINT | HintManagerImpl.HIDE_BY_SCROLLING,
-1, false);
}
private String getFileName() {
VirtualFile file = FileDocumentManager.getInstance().getFile(myDocument);
if (file == null) return "";
return file.getName();
}
public static LineStatusTracker createOn(Document doc, String upToDateContent, Project project) {
Document document = EditorFactory.getInstance().createDocument(StringUtil.convertLineSeparators(upToDateContent));
final LineStatusTracker tracker = new LineStatusTracker(doc, document, project); final LineStatusTracker tracker = new LineStatusTracker(doc, document, project);
tracker.initialize(upToDateContent); tracker.initialize(upToDateContent);
return tracker; return tracker;
} }
public static LineStatusTracker createOn(Document doc, Project project) { public static LineStatusTracker createOn(final Document doc, final Project project) {
Document document = EditorFactory.getInstance().createDocument(""); final Document document = EditorFactory.getInstance().createDocument("");
return new LineStatusTracker(doc, document, project); return new LineStatusTracker(doc, document, project);
} }
public boolean isBaseLoaded() {
return myBaseLoaded;
}
} }
@@ -0,0 +1,250 @@
/*
* 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.ex;
import com.intellij.codeInsight.hint.EditorFragmentComponent;
import com.intellij.codeInsight.hint.HintManagerImpl;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.ActionPlaces;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.openapi.diff.DiffColors;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.editor.ScrollType;
import com.intellij.openapi.editor.colors.EditorColors;
import com.intellij.openapi.editor.colors.EditorColorsManager;
import com.intellij.openapi.editor.colors.EditorColorsScheme;
import com.intellij.openapi.editor.colors.TextAttributesKey;
import com.intellij.openapi.editor.ex.DocumentEx;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.ex.EditorGutterComponentEx;
import com.intellij.openapi.editor.highlighter.EditorHighlighter;
import com.intellij.openapi.editor.highlighter.EditorHighlighterFactory;
import com.intellij.openapi.editor.markup.ActiveGutterRenderer;
import com.intellij.openapi.editor.markup.EffectType;
import com.intellij.openapi.editor.markup.LineMarkerRenderer;
import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.vcs.actions.ShowNextChangeMarkerAction;
import com.intellij.openapi.vcs.actions.ShowPrevChangeMarkerAction;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.ColoredSideBorder;
import com.intellij.ui.HintListener;
import com.intellij.ui.LightweightHint;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.util.EventObject;
/**
* @author irengrig
*/
public class LineStatusTrackerDrawing {
private LineStatusTrackerDrawing() {
}
static TextAttributes getAttributesFor(final Range range) {
final EditorColorsScheme globalScheme = EditorColorsManager.getInstance().getGlobalScheme();
final Color stripeColor = globalScheme.getAttributes(getDiffColor(range)).getErrorStripeColor();
final TextAttributes textAttributes = new TextAttributes(null, stripeColor, null, EffectType.BOXED, Font.PLAIN);
textAttributes.setErrorStripeColor(stripeColor);
return textAttributes;
}
private static void paintGutterFragment(final Editor editor, final Graphics g, final Rectangle r, final TextAttributesKey diffAttributeKey) {
final EditorGutterComponentEx gutter = ((EditorEx)editor).getGutterComponentEx();
final Color stripeColor = editor.getColorsScheme().getAttributes(diffAttributeKey).getErrorStripeColor();
g.setColor(brighter(stripeColor));
final int endX = gutter.getWhitespaceSeparatorOffset();
final int x = r.x + r.width - 2;
final int width = endX - x;
if (r.height > 0) {
g.fillRect(x, r.y + 2, width, r.height - 4);
g.setColor(gutter.getOutlineColor(false));
UIUtil.drawLine(g, x, r.y + 2, x + width, r.y + 2);
UIUtil.drawLine(g, x, r.y + 2, x, r.y + r.height - 3);
UIUtil.drawLine(g, x, r.y + r.height - 3, x + width, r.y + r.height - 3);
}
else {
final int[] xPoints = new int[]{x,
x,
x + width - 1};
final int[] yPoints = new int[]{r.y - 4,
r.y + 4,
r.y};
g.fillPolygon(xPoints, yPoints, 3);
g.setColor(gutter.getOutlineColor(false));
g.drawPolygon(xPoints, yPoints, 3);
}
}
@Nullable
private static Color brighter(final Color color) {
if (color == null) {
return null;
}
final float[] hsbStripeColor = Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), null);
if (hsbStripeColor[1] < 0.02f) {
// color is grey
hsbStripeColor[2] = Math.min(1.0f, hsbStripeColor[2] * 1.3f);
} else {
// let's decrease color saturation
hsbStripeColor[1] *= 0.3;
// max brightness
hsbStripeColor[2] = 1.0f;
}
return Color.getHSBColor(hsbStripeColor[0], hsbStripeColor[1], hsbStripeColor[2]);
}
public static LineMarkerRenderer createRenderer(final Range range, final LineStatusTracker tracker) {
return new ActiveGutterRenderer() {
public void paint(final Editor editor, final Graphics g, final Rectangle r) {
paintGutterFragment(editor, g, r, getDiffColor(range));
}
public void doAction(final Editor editor, final MouseEvent e) {
e.consume();
final JComponent comp = (JComponent)e.getComponent(); // shall be EditorGutterComponent, cast is safe.
final JLayeredPane layeredPane = comp.getRootPane().getLayeredPane();
final Point point = SwingUtilities.convertPoint(comp, ((EditorEx)editor).getGutterComponentEx().getWidth(), e.getY(), layeredPane);
showActiveHint(range, editor, point, tracker);
}
public boolean canDoAction(final MouseEvent e) {
final EditorGutterComponentEx gutter = (EditorGutterComponentEx)e.getComponent();
return e.getX() > gutter.getLineMarkerAreaOffset() + gutter.getIconsAreaWidth();
}
};
}
public static void showActiveHint(final Range range, final Editor editor, final Point point, final LineStatusTracker tracker) {
final DefaultActionGroup group = new DefaultActionGroup();
final AnAction globalShowNextAction = ActionManager.getInstance().getAction("VcsShowNextChangeMarker");
final AnAction globalShowPrevAction = ActionManager.getInstance().getAction("VcsShowPrevChangeMarker");
final ShowPrevChangeMarkerAction localShowPrevAction = new ShowPrevChangeMarkerAction(tracker.getPrevRange(range), tracker, editor);
final ShowNextChangeMarkerAction localShowNextAction = new ShowNextChangeMarkerAction(tracker.getNextRange(range), tracker, editor);
final JComponent editorComponent = editor.getComponent();
localShowNextAction.registerCustomShortcutSet(localShowNextAction.getShortcutSet(), editorComponent);
localShowPrevAction.registerCustomShortcutSet(localShowPrevAction.getShortcutSet(), editorComponent);
group.add(localShowPrevAction);
group.add(localShowNextAction);
localShowNextAction.copyFrom(globalShowNextAction);
localShowPrevAction.copyFrom(globalShowPrevAction);
group.add(new RollbackLineStatusRangeAction(tracker, range, editor));
group.add(new ShowLineStatusRangeDiffAction(tracker, range, editor));
group.add(new CopyLineStatusRangeAction(tracker, range));
final java.util.List<AnAction> actionList = (java.util.List<AnAction>)editorComponent.getClientProperty(AnAction.ourClientProperty);
actionList.remove(globalShowPrevAction);
actionList.remove(globalShowNextAction);
final JComponent toolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.FILEHISTORY_VIEW_TOOLBAR, group, true).getComponent();
final Color background = ((EditorEx)editor).getBackgroundColor();
final Color foreground = editor.getColorsScheme().getColor(EditorColors.CARET_COLOR);
toolbar.setBackground(background);
toolbar.setBorder(new ColoredSideBorder(foreground, foreground, (range.getType() != Range.INSERTED) ? null : foreground, foreground, 1));
final JPanel component = new JPanel(new BorderLayout());
component.setOpaque(false);
final JPanel toolbarPanel = new JPanel(new BorderLayout());
toolbarPanel.setOpaque(false);
toolbarPanel.add(toolbar, BorderLayout.WEST);
component.add(toolbarPanel, BorderLayout.NORTH);
if (range.getType() != Range.INSERTED) {
final DocumentEx doc = (DocumentEx) tracker.getUpToDateDocument();
final EditorEx uEditor = (EditorEx)EditorFactory.getInstance().createViewer(doc, tracker.getProject());
final EditorHighlighter highlighter = EditorHighlighterFactory.getInstance().createEditorHighlighter(tracker.getProject(), getFileName(tracker.getDocument()));
uEditor.setHighlighter(highlighter);
final EditorFragmentComponent editorFragmentComponent =
EditorFragmentComponent.createEditorFragmentComponent(uEditor, range.getUOffset1(), range.getUOffset2(), false, false);
component.add(editorFragmentComponent, BorderLayout.CENTER);
EditorFactory.getInstance().releaseEditor(uEditor);
}
final LightweightHint lightweightHint = new LightweightHint(component);
lightweightHint.addHintListener(new HintListener() {
public void hintHidden(final EventObject event) {
actionList.remove(localShowPrevAction);
actionList.remove(localShowNextAction);
actionList.add(globalShowPrevAction);
actionList.add(globalShowNextAction);
}
});
HintManagerImpl.getInstanceImpl().showEditorHint(lightweightHint, editor, point, HintManagerImpl.HIDE_BY_ANY_KEY | HintManagerImpl.HIDE_BY_TEXT_CHANGE |
HintManagerImpl.HIDE_BY_OTHER_HINT | HintManagerImpl.HIDE_BY_SCROLLING,
-1, false);
}
private static String getFileName(final Document document) {
final VirtualFile file = FileDocumentManager.getInstance().getFile(document);
if (file == null) return "";
return file.getName();
}
public static void moveToRange(final Range range, final Editor editor, final LineStatusTracker tracker) {
final Document document = tracker.getDocument();
final int firstOffset = document.getLineStartOffset(Math.min(range.getOffset1(), document.getLineCount() - 1));
editor.getCaretModel().moveToOffset(firstOffset);
editor.getScrollingModel().scrollToCaret(ScrollType.CENTER);
editor.getScrollingModel().runActionOnScrollingFinished(new Runnable() {
public void run() {
Point p = editor.visualPositionToXY(editor.offsetToVisualPosition(firstOffset));
final JComponent editorComponent = editor.getContentComponent();
final JLayeredPane layeredPane = editorComponent.getRootPane().getLayeredPane();
p = SwingUtilities.convertPoint(editorComponent, 0, p.y, layeredPane);
showActiveHint(range, editor, p, tracker);
}
});
}
private static TextAttributesKey getDiffColor(Range range) {
switch (range.getType()) {
case Range.INSERTED:
return DiffColors.DIFF_INSERTED;
case Range.DELETED:
return DiffColors.DIFF_DELETED;
case Range.MODIFIED:
return DiffColors.DIFF_MODIFIED;
default:
assert false;
return null;
}
}
}
@@ -154,12 +154,7 @@ public class Range {
} }
public Range mergeWith(Range range, LineStatusTracker tracker) { public Range mergeWith(Range range, LineStatusTracker tracker) {
tracker.removeHighlighter(getHighlighter()); return new Range(myOffset1, range.myOffset2, myUpToDateOffset1, range.myUpToDateOffset2, mergedStatusWith(range));
setHighlighter(null);
tracker.removeHighlighter(range.getHighlighter());
range.setHighlighter(null);
Range result = new Range(myOffset1, range.myOffset2, myUpToDateOffset1, range.myUpToDateOffset2, mergedStatusWith(range));
return result;
} }
private byte mergedStatusWith(Range range) { private byte mergedStatusWith(Range range) {
@@ -16,11 +16,10 @@
package com.intellij.openapi.vcs.ex; package com.intellij.openapi.vcs.ex;
import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.util.ArrayUtil; import com.intellij.util.ArrayUtil;
import com.intellij.util.diff.Diff; import com.intellij.util.diff.Diff;
import java.util.ArrayList; import java.util.LinkedList;
import java.util.List; import java.util.List;
/** /**
@@ -30,16 +29,12 @@ import java.util.List;
public class RangesBuilder { public class RangesBuilder {
private List<Range> myRanges; private List<Range> myRanges;
public RangesBuilder(String current, String upToDate) {
this(EditorFactory.getInstance().createDocument(current), EditorFactory.getInstance().createDocument(upToDate));
}
public RangesBuilder(Document current, Document upToDate) { public RangesBuilder(Document current, Document upToDate) {
this(new DocumentWrapper(current).getLines(), new DocumentWrapper(upToDate).getLines(), 0, 0); this(new DocumentWrapper(current).getLines(), new DocumentWrapper(upToDate).getLines(), 0, 0);
} }
public RangesBuilder(List<String> current, List<String> upToDate, int shift, int uShift) { public RangesBuilder(List<String> current, List<String> upToDate, int shift, int uShift) {
myRanges = new ArrayList<Range>(); myRanges = new LinkedList<Range>();
int shiftBefore = 0; int shiftBefore = 0;
@@ -0,0 +1,52 @@
/*
* 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.ex;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.IconLoader;
import com.intellij.openapi.vcs.VcsBundle;
import com.intellij.openapi.vfs.ReadonlyStatusHandler;
/**
* @author irengrig
*/
public class RollbackLineStatusRangeAction extends BaseLineStatusRangeAction {
public RollbackLineStatusRangeAction(final LineStatusTracker lineStatusTracker, final Range range, final Editor editor) {
super(VcsBundle.message("action.name.rollback"), IconLoader.getIcon("/actions/reset.png"), lineStatusTracker, range);
}
public boolean isEnabled() {
return true;
}
public void actionPerformed(final AnActionEvent e) {
CommandProcessor.getInstance().executeCommand(myLineStatusTracker.getProject(), new Runnable() {
public void run() {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
public void run() {
if (!myLineStatusTracker.getDocument().isWritable()) {
final ReadonlyStatusHandler.OperationStatus operationStatus = ReadonlyStatusHandler
.getInstance(myLineStatusTracker.getProject()).ensureFilesWritable(myLineStatusTracker.getVirtualFile());
if (operationStatus.hasReadonlyFiles()) return;
}
myLineStatusTracker.rollbackChanges(myRange);
}
});
}
}, VcsBundle.message("command.name.rollback.change"), null);
}
}
@@ -0,0 +1,74 @@
/*
* 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.ex;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.diff.*;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.IconLoader;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vcs.VcsBundle;
import com.intellij.openapi.vfs.VirtualFile;
/**
* @author irengrig
*/
public class ShowLineStatusRangeDiffAction extends BaseLineStatusRangeAction {
public ShowLineStatusRangeDiffAction(final LineStatusTracker lineStatusTracker, final Range range, final Editor editor) {
super(VcsBundle.message("action.name.show.difference"), IconLoader.getIcon("/actions/diff.png"), lineStatusTracker, range);
}
public boolean isEnabled() {
return isModifiedRange() || isDeletedRange();
}
private boolean isDeletedRange() {
return Range.DELETED == myRange.getType();
}
private boolean isModifiedRange() {
return Range.MODIFIED == myRange.getType();
}
public void actionPerformed(final AnActionEvent e) {
DiffManager.getInstance().getDiffTool().show(createDiffData());
}
private DiffRequest createDiffData() {
return new DiffRequest(myLineStatusTracker.getProject()) {
public DiffContent[] getContents() {
return new DiffContent[]{createDiffContent(myLineStatusTracker.getUpToDateDocument(),
myLineStatusTracker.getUpToDateRangeWithEndSymbol(myRange), null),
createDiffContent(myLineStatusTracker.getDocument(), myLineStatusTracker.getCurrentTextRange(myRange),
myLineStatusTracker.getVirtualFile())};
}
public String[] getContentTitles() {
return new String[]{VcsBundle.message("diff.content.title.up.to.date"),
VcsBundle.message("diff.content.title.current.range")};
}
public String getWindowTitle() {
return VcsBundle.message("dialog.title.diff.for.range");
}
};
}
private DiffContent createDiffContent(final Document uDocument, final TextRange textRange, final VirtualFile file) {
final Project project = myLineStatusTracker.getProject();
final DiffContent diffContent = new DocumentContent(project, uDocument);
return new FragmentContent(diffContent, textRange, project, file);
}
}
@@ -24,7 +24,9 @@ package com.intellij.openapi.vcs.impl;
import com.intellij.lifecycle.PeriodicalTasksCloser; import com.intellij.lifecycle.PeriodicalTasksCloser;
import com.intellij.openapi.Disposable; import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.components.ProjectComponent; import com.intellij.openapi.components.ProjectComponent;
import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Document;
@@ -40,11 +42,14 @@ import com.intellij.openapi.editor.ex.DocumentBulkUpdateListener;
import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.vcs.AbstractVcs; import com.intellij.openapi.vcs.AbstractVcs;
import com.intellij.openapi.vcs.FileStatus; import com.intellij.openapi.vcs.FileStatus;
import com.intellij.openapi.vcs.FileStatusListener; import com.intellij.openapi.vcs.FileStatusListener;
import com.intellij.openapi.vcs.FileStatusManager; import com.intellij.openapi.vcs.FileStatusManager;
import com.intellij.openapi.vcs.changes.committed.AbstractCalledLater;
import com.intellij.openapi.vcs.ex.LineStatusTracker; import com.intellij.openapi.vcs.ex.LineStatusTracker;
import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileAdapter; import com.intellij.openapi.vfs.VirtualFileAdapter;
@@ -56,50 +61,79 @@ import com.intellij.util.containers.HashMap;
import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.Map;
public class LineStatusTrackerManager implements ProjectComponent { public class LineStatusTrackerManager implements ProjectComponent, LineStatusTrackerManagerI {
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vcs.impl.LineStatusTrackerManager"); private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vcs.impl.LineStatusTrackerManager");
public static LineStatusTrackerManager getInstance(Project project) { public static LineStatusTrackerManagerI getInstance(final Project project) {
return PeriodicalTasksCloser.getInstance().safeGetComponent(project, LineStatusTrackerManager.class); if (System.getProperty(IGNORE_CHANGEMARKERS_KEY) != null) {
return Dummy.getInstance();
}
return PeriodicalTasksCloser.getInstance().safeGetComponent(project, LineStatusTrackerManagerI.class);
} }
private final Project myProject; private final Project myProject;
private HashMap<Document, LineStatusTracker> myLineStatusTrackers = private final Map<Document, LineStatusTracker> myLineStatusTrackers;
new HashMap<Document, LineStatusTracker>(); // !!! no state queries and self lock for add/remove
// removal from here - not under write action
private final Map<Document, Alarm> myLineStatusUpdateAlarms;
private final HashMap<Document, Alarm> myLineStatusUpdateAlarms =
new HashMap<Document, Alarm>();
private final Object TRACKERS_LOCK = new Object();
private boolean myIsDisposed = false;
@NonNls protected static final String IGNORE_CHANGEMARKERS_KEY = "idea.ignore.changemarkers"; @NonNls protected static final String IGNORE_CHANGEMARKERS_KEY = "idea.ignore.changemarkers";
private final ProjectLevelVcsManagerImpl myVcsManager; private final ProjectLevelVcsManagerImpl myVcsManager;
private final VcsFileStatusProvider myStatusProvider; private final VcsFileStatusProvider myStatusProvider;
private final Application myApplication;
private final FileEditorManager myFileEditorManager;
private final Disposable myDisposable;
public LineStatusTrackerManager(final Project project, final ProjectLevelVcsManagerImpl vcsManager, final VcsFileStatusProvider statusProvider) { public LineStatusTrackerManager(final Project project, final ProjectLevelVcsManagerImpl vcsManager, final VcsFileStatusProvider statusProvider,
final Application application, final FileEditorManager fileEditorManager) {
myProject = project; myProject = project;
myVcsManager = vcsManager; myVcsManager = vcsManager;
myStatusProvider = statusProvider; myStatusProvider = statusProvider;
myApplication = application;
myFileEditorManager = fileEditorManager;
myLineStatusTrackers = new HashMap<Document, LineStatusTracker>();
myLineStatusUpdateAlarms = Collections.synchronizedMap(new HashMap<Document, Alarm>());
project.getMessageBus().connect().subscribe(DocumentBulkUpdateListener.TOPIC, new DocumentBulkUpdateListener.Adapter() { project.getMessageBus().connect().subscribe(DocumentBulkUpdateListener.TOPIC, new DocumentBulkUpdateListener.Adapter() {
public void updateStarted(final Document doc) { public void updateStarted(final Document doc) {
myApplication.assertWriteAccessAllowed(); // can remove after some testing
final LineStatusTracker tracker = getLineStatusTracker(doc); final LineStatusTracker tracker = getLineStatusTracker(doc);
if (tracker != null) tracker.startBulkUpdate(); if (tracker != null) tracker.startBulkUpdate();
} }
public void updateFinished(final Document doc) { public void updateFinished(final Document doc) {
myApplication.assertWriteAccessAllowed(); // can remove after some testing
final LineStatusTracker tracker = getLineStatusTracker(doc); final LineStatusTracker tracker = getLineStatusTracker(doc);
if (tracker != null) tracker.finishBulkUpdate(); if (tracker != null) tracker.finishBulkUpdate();
} }
}); });
myDisposable = new Disposable() {
@Override
public void dispose() {
final Collection<LineStatusTracker> trackers = myLineStatusTrackers.values();
final LineStatusTracker[] lineStatusTrackers = trackers.toArray(new LineStatusTracker[trackers.size()]);
for (final LineStatusTracker tracker : lineStatusTrackers) {
releaseTracker(tracker.getDocument());
}
myLineStatusTrackers.clear();
assert myLineStatusUpdateAlarms.isEmpty();
myLineStatusUpdateAlarms.clear();
}
};
Disposer.register(myProject, myDisposable);
} }
public void projectOpened() { public void projectOpened() {
trackAwtThread();
final MyFileStatusListener fileStatusListener = new MyFileStatusListener(); final MyFileStatusListener fileStatusListener = new MyFileStatusListener();
final EditorFactoryListener editorFactoryListener = new MyEditorFactoryListener(); final EditorFactoryListener editorFactoryListener = new MyEditorFactoryListener();
final MyVirtualFileListener virtualFileListener = new MyVirtualFileListener(); final MyVirtualFileListener virtualFileListener = new MyVirtualFileListener();
@@ -109,7 +143,6 @@ public class LineStatusTrackerManager implements ProjectComponent {
} }
}; };
myLineStatusTrackers = new HashMap<Document, LineStatusTracker>();
final FileStatusManager fsManager = FileStatusManager.getInstance(myProject); final FileStatusManager fsManager = FileStatusManager.getInstance(myProject);
fsManager.addFileStatusListener(fileStatusListener, myProject); fsManager.addFileStatusListener(fileStatusListener, myProject);
@@ -122,9 +155,8 @@ public class LineStatusTrackerManager implements ProjectComponent {
final EditorColorsManager editorColorsManager = EditorColorsManager.getInstance(); final EditorColorsManager editorColorsManager = EditorColorsManager.getInstance();
editorColorsManager.addEditorColorsListener(editorColorsListener); editorColorsManager.addEditorColorsListener(editorColorsListener);
Disposer.register(myProject, new Disposable() { Disposer.register(myDisposable, new Disposable() {
public void dispose() { public void dispose() {
trackAwtThread();
fsManager.removeFileStatusListener(fileStatusListener); fsManager.removeFileStatusListener(fileStatusListener);
virtualFileManager.removeVirtualFileListener(virtualFileListener); virtualFileManager.removeVirtualFileListener(virtualFileListener);
editorColorsManager.removeEditorColorsListener(editorColorsListener); editorColorsManager.removeEditorColorsListener(editorColorsListener);
@@ -133,13 +165,6 @@ public class LineStatusTrackerManager implements ProjectComponent {
} }
public void projectClosed() { public void projectClosed() {
try {
trackAwtThread();
dispose();
}
finally {
myIsDisposed = true;
}
} }
@NonNls @NotNull @NonNls @NotNull
@@ -153,213 +178,212 @@ public class LineStatusTrackerManager implements ProjectComponent {
public void disposeComponent() { public void disposeComponent() {
} }
private void dispose() { @Override
final Collection<LineStatusTracker> trackers = myLineStatusTrackers.values(); public LineStatusTracker getLineStatusTracker(final Document document) {
final LineStatusTracker[] lineStatusTrackers = trackers.toArray(new LineStatusTracker[trackers.size()]); myApplication.assertReadAccessAllowed();
for (LineStatusTracker tracker : lineStatusTrackers) {
releaseTracker(tracker.getDocument());
}
myLineStatusTrackers = null; if ((! myProject.isOpen()) || myProject.isDisposed()) return null;
}
public LineStatusTracker getLineStatusTracker(Document document) {
trackAwtThread();
if (myLineStatusTrackers == null) return null;
return myLineStatusTrackers.get(document); return myLineStatusTrackers.get(document);
} }
private void resetTracker(@NotNull final VirtualFile virtualFile) {
public LineStatusTracker setUpToDateContent(final Document document, final String lastUpToDateContent) { if ((! myProject.isOpen()) || myProject.isDisposed()) return;
trackAwtThread();
LineStatusTracker result = myLineStatusTrackers.get(document);
if (result == null) {
result = LineStatusTracker.createOn(document, lastUpToDateContent, myProject);
myLineStatusTrackers.put(document, result);
}
return result;
}
private LineStatusTracker createTrackerForDocument(Document document, VirtualFile vf) {
LOG.assertTrue(!myLineStatusTrackers.containsKey(document));
LineStatusTracker result = LineStatusTracker.createOn(document, myProject);
myLineStatusTrackers.put(document, result);
return result;
}
private void resetTracker(final VirtualFile virtualFile) {
if (System.getProperty(IGNORE_CHANGEMARKERS_KEY) != null) return;
final Document document = FileDocumentManager.getInstance().getCachedDocument(virtualFile); final Document document = FileDocumentManager.getInstance().getCachedDocument(virtualFile);
if (document == null) { if (document == null) {
if (LOG.isDebugEnabled()) { log("Skipping resetTracker() because no cached document for " + virtualFile.getPath());
LOG.debug("Skipping resetTracker() because no cached document for " + virtualFile.getPath());
}
return; return;
} }
if (LOG.isDebugEnabled()) { log("resetting tracker for file " + virtualFile.getPath());
LOG.debug("resetting tracker for file " + virtualFile.getPath());
} final LineStatusTracker tracker = myLineStatusTrackers.get(document);
synchronized (TRACKERS_LOCK) { final boolean editorOpened = myFileEditorManager.isFileOpen(virtualFile);
final LineStatusTracker tracker = myLineStatusTrackers.get(document); final boolean shouldBeInstalled = shouldBeInstalled(virtualFile) && editorOpened;
if (tracker != null) {
resetTracker(tracker); if (tracker == null && (! shouldBeInstalled)) return;
}
else { myApplication.runWriteAction(new Runnable() {
if (Arrays.asList(FileEditorManager.getInstance(myProject).getOpenFiles()).contains(virtualFile)) { @Override
public void run() {
// remove ?
if (tracker != null) {
if (! shouldBeInstalled) {
releaseTracker(document);
return;
} else if ((! tracker.isBaseLoaded())) {
return; // will be recalculated
} else {
tracker.resetForBaseRevisionLoad();
startAlarm(document, virtualFile);
}
} else if (shouldBeInstalled) {
installTracker(virtualFile, document); installTracker(virtualFile, document);
} }
} }
} });
} }
private boolean releaseTracker(Document document) { private void releaseTracker(final Document document) {
synchronized (TRACKERS_LOCK) { if ((! myProject.isOpen()) || myProject.isDisposed()) return;
releaseUpdateAlarms(document);
if (myLineStatusTrackers == null) return false;
if (!myLineStatusTrackers.containsKey(document)) return false;
LineStatusTracker tracker = myLineStatusTrackers.remove(document);
tracker.release();
return true;
}
}
private void releaseUpdateAlarms(final Document document) { myApplication.runWriteAction(new Runnable() {
if (myLineStatusUpdateAlarms.containsKey(document)) { @Override
final Alarm alarm = myLineStatusUpdateAlarms.get(document); public void run() {
if (alarm != null) { final Alarm alarm = myLineStatusUpdateAlarms.remove(document);
alarm.cancelAllRequests(); if (alarm != null) {
alarm.cancelAllRequests();
}
final LineStatusTracker tracker = myLineStatusTrackers.remove(document);
if (tracker != null) {
tracker.release();
}
} }
myLineStatusUpdateAlarms.remove(document); });
}
} }
public void resetTracker(final LineStatusTracker tracker) { private boolean shouldBeInstalled(final VirtualFile virtualFile) {
trackAwtThread(); ApplicationManager.getApplication().assertIsDispatchThread();
if (tracker != null) {
ApplicationManager.getApplication().invokeLater(new Runnable() { if (virtualFile == null || virtualFile instanceof LightVirtualFile) return false;
if (! virtualFile.isInLocalFileSystem()) return false;
if ((! myProject.isOpen()) || myProject.isDisposed()) return false;
final FileStatusManager statusManager = FileStatusManager.getInstance(myProject);
if (statusManager == null) return false;
final AbstractVcs activeVcs = myVcsManager.getVcsFor(virtualFile);
if (activeVcs == null) {
log("installTracker() for file " + virtualFile.getPath() + " failed: no active VCS");
return false;
}
final FileStatus status = statusManager.getStatus(virtualFile);
if (status == FileStatus.NOT_CHANGED || status == FileStatus.ADDED || status == FileStatus.UNKNOWN || status == FileStatus.IGNORED) {
log("installTracker() for file " + virtualFile.getPath() + " failed: status=" + status);
return false;
}
return true;
}
private void installTracker(final VirtualFile virtualFile, final Document document) {
/*ApplicationManager.getApplication().assertIsDispatchThread();
if (virtualFile == null || virtualFile instanceof LightVirtualFile) return;
if (! virtualFile.isInLocalFileSystem()) return;
if ((! myProject.isOpen()) || myProject.isDisposed()) return;
final FileStatusManager statusManager = FileStatusManager.getInstance(myProject);
if (statusManager == null) return;
final AbstractVcs activeVcs = myVcsManager.getVcsFor(virtualFile);
if (activeVcs == null) {
if (LOG.isDebugEnabled()) {
LOG.info("installTracker() for file " + virtualFile.getPath() + " failed: no active VCS");
}
return;
}
final FileStatus status = statusManager.getStatus(virtualFile);
if (status == FileStatus.NOT_CHANGED || status == FileStatus.ADDED || status == FileStatus.UNKNOWN || status == FileStatus.IGNORED) {
if (LOG.isDebugEnabled()) {
LOG.info("installTracker() for file " + virtualFile.getPath() + " failed: status=" + status);
System.out.println("installTracker() for file " + virtualFile.getPath() + " failed: status=" + status);
}
return;
} */
myApplication.runWriteAction(new Runnable() {
@Override
public void run() {
if (myLineStatusTrackers.containsKey(document)) return;
assert !myLineStatusUpdateAlarms.containsKey(document);
final LineStatusTracker tracker = LineStatusTracker.createOn(document, myProject);
myLineStatusTrackers.put(document, tracker);
startAlarm(document, virtualFile);
}
});
}
private void startAlarm(final Document document, final VirtualFile virtualFile) {
myApplication.assertWriteAccessAllowed();
final Alarm alarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD);
myLineStatusUpdateAlarms.put(document, alarm);
alarm.addRequest(new BaseRevisionLoader(alarm, document, virtualFile), 10);
}
private class BaseRevisionLoader implements Runnable {
private final Alarm myAlarm;
private final VirtualFile myVirtualFile;
private final Document myDocument;
private BaseRevisionLoader(final Alarm alarm, final Document document, final VirtualFile virtualFile) {
myAlarm = alarm;
myDocument = document;
myVirtualFile = virtualFile;
}
@Override
public void run() {
myAlarm.cancelAllRequests();
final Boolean removed = myApplication.runReadAction(new Computable<Boolean>() {
@Override
public Boolean compute() {
final Alarm removed = myLineStatusUpdateAlarms.remove(myDocument);
return removed != null;
}
});
if (! Boolean.TRUE.equals(removed)) return;
if ((! myProject.isOpen()) || myProject.isDisposed()) return;
if (! myVirtualFile.isValid()) {
log("installTracker() for file " + myVirtualFile.getPath() + " failed: virtual file not valid");
return;
}
final String lastUpToDateContent = myStatusProvider.getBaseVersionContent(myVirtualFile);
if (lastUpToDateContent == null) {
log("installTracker() for file " + myVirtualFile.getPath() + " failed: no up to date content");
return;
}
myApplication.invokeLater(new Runnable() {
public void run() { public void run() {
if (myIsDisposed) return; ApplicationManager.getApplication().runWriteAction(new Runnable() {
if (releaseTracker(tracker.getDocument())) { public void run() {
installTracker(tracker.getVirtualFile(), tracker.getDocument()); log("initializing tracker for file " + myVirtualFile.getPath());
} final LineStatusTracker tracker = myLineStatusTrackers.get(myDocument);
if (tracker != null) {
tracker.initialize(lastUpToDateContent);
}
}
});
}
}, new Condition() {
@Override
public boolean value(final Object ignore) {
return (! myProject.isOpen()) || myProject.isDisposed();
} }
}); });
} }
} }
private void installTracker(final VirtualFile virtualFile, final Document document) {
if (virtualFile == null || virtualFile instanceof LightVirtualFile) return;
ApplicationManager.getApplication().assertIsDispatchThread();
if (myProject.isDisposed() || myLineStatusTrackers == null) return;
final FileStatusManager statusManager = FileStatusManager.getInstance(myProject);
if (statusManager == null) return;
final FileStatus status = statusManager.getStatus(virtualFile);
synchronized (TRACKERS_LOCK) {
if (myLineStatusTrackers.containsKey(document)) return;
if (status == FileStatus.NOT_CHANGED ||
status == FileStatus.ADDED ||
status == FileStatus.UNKNOWN ||
status == FileStatus.IGNORED) {
if (LOG.isDebugEnabled()) {
LOG.debug("installTracker() for file " + virtualFile.getPath() + " failed: status=" + status);
}
return;
}
AbstractVcs activeVcs = myVcsManager.getVcsFor(virtualFile);
if (activeVcs == null) {
if (LOG.isDebugEnabled()) {
LOG.debug("installTracker() for file " + virtualFile.getPath() + " failed: no active VCS");
}
return;
}
if (!virtualFile.isInLocalFileSystem()) return;
if (System.getProperty(IGNORE_CHANGEMARKERS_KEY) != null) return;
final Alarm alarm;
if (myLineStatusUpdateAlarms.containsKey(document)) {
alarm = myLineStatusUpdateAlarms.get(document);
alarm.cancelAllRequests();
}
else {
alarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD);
myLineStatusUpdateAlarms.put(document, alarm);
}
final LineStatusTracker tracker = createTrackerForDocument(document, virtualFile);
alarm.addRequest(new Runnable() {
public void run() {
try {
alarm.cancelAllRequests();
if (!virtualFile.isValid()) {
if (LOG.isDebugEnabled()) {
LOG.debug("installTracker() for file " + virtualFile.getPath() + " failed: virtual file not valid");
}
return;
}
final String lastUpToDateContent = myStatusProvider.getBaseVersionContent(virtualFile);
if (lastUpToDateContent == null) {
if (LOG.isDebugEnabled()) {
LOG.debug("installTracker() for file " + virtualFile.getPath() + " failed: no up to date content");
}
return;
}
ApplicationManager.getApplication().invokeLater(new Runnable() {
public void run() {
if (!myProject.isDisposed()) {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
public void run() {
if (LOG.isDebugEnabled()) {
LOG.debug("initializing tracker for file " + virtualFile.getPath());
}
synchronized (TRACKERS_LOCK) {
tracker.initialize(lastUpToDateContent);
}
}
});
}
}
});
}
finally {
// todo guard alarms!!!
myLineStatusUpdateAlarms.remove(document);
}
}
}, 10);
}
}
private void resetTrackersForOpenFiles() { private void resetTrackersForOpenFiles() {
final VirtualFile[] openFiles = FileEditorManager.getInstance(myProject).getOpenFiles(); myApplication.assertReadAccessAllowed();
synchronized (TRACKERS_LOCK) { if ((! myProject.isOpen()) || myProject.isDisposed()) return;
for(VirtualFile openFile: openFiles) {
resetTracker(openFile); final VirtualFile[] openFiles = myFileEditorManager.getOpenFiles();
} for(final VirtualFile openFile: openFiles) {
// write action inside is sufficient level
resetTracker(openFile);
} }
} }
private class MyFileStatusListener implements FileStatusListener { private class MyFileStatusListener implements FileStatusListener {
public void fileStatusesChanged() { public void fileStatusesChanged() {
if (myProject.isDisposed()) return; if (myProject.isDisposed()) return;
LOG.debug("LineStatusTrackerManager: fileStatusesChanged"); log("LineStatusTrackerManager: fileStatusesChanged");
trackAwtThread();
resetTrackersForOpenFiles(); resetTrackersForOpenFiles();
} }
public void fileStatusChanged(@NotNull VirtualFile virtualFile) { public void fileStatusChanged(@NotNull VirtualFile virtualFile) {
trackAwtThread();
resetTracker(virtualFile); resetTracker(virtualFile);
} }
} }
@@ -370,33 +394,46 @@ public class LineStatusTrackerManager implements ProjectComponent {
// outside of EDT, so the EDT check mustn't be done here // outside of EDT, so the EDT check mustn't be done here
Editor editor = event.getEditor(); Editor editor = event.getEditor();
if (editor.getProject() != null && editor.getProject() != myProject) return; if (editor.getProject() != null && editor.getProject() != myProject) return;
Document document = editor.getDocument(); final Document document = editor.getDocument();
VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(document); final VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(document);
installTracker(virtualFile, document);
new AbstractCalledLater(myProject, ModalityState.NON_MODAL) {
@Override
public void run() {
if (shouldBeInstalled(virtualFile)) {
installTracker(virtualFile, document);
}
}
}.callMe();
} }
public void editorReleased(EditorFactoryEvent event) { public void editorReleased(EditorFactoryEvent event) {
trackAwtThread();
final Editor editor = event.getEditor(); final Editor editor = event.getEditor();
if (editor.getProject() != null && editor.getProject() != myProject) return; if (editor.getProject() != null && editor.getProject() != myProject) return;
final Document doc = editor.getDocument(); final Document doc = editor.getDocument();
final Editor[] editors = event.getFactory().getEditors(doc, myProject); final Editor[] editors = event.getFactory().getEditors(doc, myProject);
if (editors.length == 0) { if (editors.length == 0) {
releaseTracker(doc); new AbstractCalledLater(myProject, ModalityState.NON_MODAL) {
@Override
public void run() {
releaseTracker(doc);
}
}.callMe();
} }
} }
} }
private class MyVirtualFileListener extends VirtualFileAdapter { private class MyVirtualFileListener extends VirtualFileAdapter {
public void beforeContentsChange(VirtualFileEvent event) { public void beforeContentsChange(VirtualFileEvent event) {
trackAwtThread();
if (event.isFromRefresh()) { if (event.isFromRefresh()) {
resetTracker(event.getFile()); resetTracker(event.getFile());
} }
} }
} }
private static void trackAwtThread() { private static void log(final String s) {
ApplicationManager.getApplication().assertIsDispatchThread(); if (LOG.isDebugEnabled()) {
LOG.debug(s);
}
} }
} }
@@ -0,0 +1,38 @@
/*
* 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.impl;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.vcs.ex.LineStatusTracker;
import org.jetbrains.annotations.Nullable;
/**
* @author irengrig
*/
public interface LineStatusTrackerManagerI {
@Nullable
LineStatusTracker getLineStatusTracker(Document document);
class Dummy implements LineStatusTrackerManagerI {
private final static Dummy ourInstance = new Dummy();
public static Dummy getInstance() {
return ourInstance;
}
@Override
public LineStatusTracker getLineStatusTracker(final Document document) {
return null;
}
}
}
@@ -40,7 +40,7 @@ public class UpToDateLineNumberProviderImpl implements UpToDateLineNumberProvide
public int getLineNumber(int currentNumber) { public int getLineNumber(int currentNumber) {
LineStatusTracker tracker = LineStatusTrackerManager.getInstance(myProject).getLineStatusTracker(myDocument); LineStatusTracker tracker = LineStatusTrackerManager.getInstance(myProject).getLineStatusTracker(myDocument);
if (tracker == null) { if (tracker == null) {
tracker = LineStatusTrackerManager.getInstance(myProject).setUpToDateContent(myDocument, myUpToDateContent); return currentNumber;
} }
return calcLineNumber(tracker, currentNumber); return calcLineNumber(tracker, currentNumber);
} }