2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com
.intellij
.openapi
.vcs
.ex
;
18 import com
.intellij
.codeInsight
.hint
.EditorFragmentComponent
;
19 import com
.intellij
.codeInsight
.hint
.HintManagerImpl
;
20 import com
.intellij
.openapi
.actionSystem
.*;
21 import com
.intellij
.openapi
.application
.ApplicationManager
;
22 import com
.intellij
.openapi
.command
.CommandProcessor
;
23 import com
.intellij
.openapi
.command
.undo
.UndoManager
;
24 import com
.intellij
.openapi
.diagnostic
.Logger
;
25 import com
.intellij
.openapi
.diff
.*;
26 import com
.intellij
.openapi
.editor
.Document
;
27 import com
.intellij
.openapi
.editor
.Editor
;
28 import com
.intellij
.openapi
.editor
.EditorFactory
;
29 import com
.intellij
.openapi
.editor
.ScrollType
;
30 import com
.intellij
.openapi
.editor
.colors
.*;
31 import com
.intellij
.openapi
.editor
.event
.DocumentAdapter
;
32 import com
.intellij
.openapi
.editor
.event
.DocumentEvent
;
33 import com
.intellij
.openapi
.editor
.ex
.DocumentEx
;
34 import com
.intellij
.openapi
.editor
.ex
.EditorEx
;
35 import com
.intellij
.openapi
.editor
.ex
.EditorGutterComponentEx
;
36 import com
.intellij
.openapi
.editor
.highlighter
.EditorHighlighter
;
37 import com
.intellij
.openapi
.editor
.highlighter
.EditorHighlighterFactory
;
38 import com
.intellij
.openapi
.editor
.markup
.*;
39 import com
.intellij
.openapi
.fileEditor
.FileDocumentManager
;
40 import com
.intellij
.openapi
.ide
.CopyPasteManager
;
41 import com
.intellij
.openapi
.project
.DumbAware
;
42 import com
.intellij
.openapi
.project
.Project
;
43 import com
.intellij
.openapi
.util
.IconLoader
;
44 import com
.intellij
.openapi
.util
.TextRange
;
45 import com
.intellij
.openapi
.util
.text
.StringUtil
;
46 import com
.intellij
.openapi
.vcs
.VcsBundle
;
47 import com
.intellij
.openapi
.vcs
.actions
.ShowNextChangeMarkerAction
;
48 import com
.intellij
.openapi
.vcs
.actions
.ShowPrevChangeMarkerAction
;
49 import com
.intellij
.openapi
.vfs
.ReadonlyStatusHandler
;
50 import com
.intellij
.openapi
.vfs
.VirtualFile
;
51 import com
.intellij
.ui
.HintListener
;
52 import com
.intellij
.ui
.LightweightHint
;
53 import com
.intellij
.ui
.SideBorder2
;
54 import com
.intellij
.util
.ui
.UIUtil
;
55 import org
.jetbrains
.annotations
.NotNull
;
56 import org
.jetbrains
.annotations
.Nullable
;
60 import java
.awt
.datatransfer
.StringSelection
;
61 import java
.awt
.event
.MouseEvent
;
63 import java
.util
.List
;
68 public class LineStatusTracker
{
69 private static final Logger LOG
= Logger
.getInstance("#com.intellij.openapi.vcs.ex.LineStatusTracker");
70 private final Document myDocument
;
71 private final Document myUpToDateDocument
;
72 @SuppressWarnings({"FieldAccessedSynchronizedAndUnsynchronized"}) private List
<Range
> myRanges
= new ArrayList
<Range
>();
73 private final Project myProject
;
74 @SuppressWarnings({"FieldAccessedSynchronizedAndUnsynchronized"}) private int myHighlighterCount
= 0;
76 private MyDocumentListener myDocumentListener
;
78 private boolean myIsReleased
= false;
79 private boolean myIsInitialized
= false;
80 private boolean myBulkUpdate
;
82 public LineStatusTracker(Document document
, Document upToDateDocument
, Project project
) {
83 myDocument
= document
;
84 myUpToDateDocument
= upToDateDocument
;
85 myUpToDateDocument
.putUserData(UndoManager
.DONT_RECORD_UNDO
, Boolean
.TRUE
);
89 public synchronized void initialize(@NotNull final String upToDateContent
) {
90 if (myIsReleased
) return;
91 LOG
.assertTrue(!myIsInitialized
);
93 ApplicationManager
.getApplication().runWriteAction(new Runnable() {
95 myUpToDateDocument
.replaceString(0, myUpToDateDocument
.getTextLength(), StringUtil
.convertLineSeparators(upToDateContent
));
99 myUpToDateDocument
.setReadOnly(true);
102 myDocumentListener
= new MyDocumentListener();
103 myDocument
.addDocumentListener(myDocumentListener
);
106 myIsInitialized
= true;
110 private synchronized void reinstallRanges() {
111 reinstallRanges(new RangesBuilder(myDocument
, myUpToDateDocument
).getRanges());
114 private void reinstallRanges(List
<Range
> ranges
) {
115 removeHighlighters();
120 private void addHighlighters() {
121 for (Range range
: myRanges
) {
122 if (!range
.hasHighlighter()) range
.setHighlighter(createHighlighter(range
));
126 @SuppressWarnings({"AutoBoxing"})
127 synchronized private RangeHighlighter
createHighlighter(Range range
) {
129 range
.getOffset1() >= myDocument
.getLineCount() ? myDocument
.getTextLength() : myDocument
.getLineStartOffset(range
.getOffset1());
132 range
.getOffset2() >= myDocument
.getLineCount() ? myDocument
.getTextLength() : myDocument
.getLineStartOffset(range
.getOffset2());
135 RangeHighlighter highlighter
= myDocument
.getMarkupModel(myProject
)
136 .addRangeHighlighter(first
, second
, HighlighterLayer
.FIRST
- 1, null, HighlighterTargetArea
.LINES_IN_RANGE
);
137 myHighlighterCount
++;
138 TextAttributes attr
= getAttributesFor(range
);
139 highlighter
.setErrorStripeMarkColor(attr
.getErrorStripeColor());
140 highlighter
.setThinErrorStripeMark(true);
141 highlighter
.setGreedyToLeft(true);
142 highlighter
.setGreedyToRight(true);
143 highlighter
.setLineMarkerRenderer(createRenderer(range
));
144 highlighter
.setEditorFilter(MarkupEditorFilterFactory
.createIsNotDiffFilter());
145 final int line1
= myDocument
.getLineNumber(first
);
146 final int line2
= myDocument
.getLineNumber(second
);
147 final String tooltip
;
148 if (line1
== line2
) {
149 tooltip
= VcsBundle
.message("tooltip.text.line.changed", line1
);
152 tooltip
= VcsBundle
.message("tooltip.text.lines.changed", line1
, line2
);
155 highlighter
.setErrorStripeTooltip(tooltip
);
160 private void removeHighlighters() {
161 for (Range oldRange
: myRanges
) {
162 removeHighlighter(oldRange
.getHighlighter());
163 oldRange
.setHighlighter(null);
167 synchronized void removeHighlighter(RangeHighlighter highlighter
) {
168 if (highlighter
== null) return;
169 MarkupModel markupModel
= myDocument
.getMarkupModel(myProject
);
170 //noinspection ConstantConditions
171 if (markupModel
== null) return;
172 markupModel
.removeHighlighter(highlighter
);
173 myHighlighterCount
--;
176 private static TextAttributesKey
getDiffColor(Range range
) {
177 switch (range
.getType()) {
179 return DiffColors
.DIFF_INSERTED
;
181 return DiffColors
.DIFF_DELETED
;
183 return DiffColors
.DIFF_MODIFIED
;
190 private static ColorKey
getEditorColorNameFor(Range range
) {
191 switch (range
.getType()) {
193 return EditorColors
.MODIFIED_LINES_COLOR
;
195 return EditorColors
.ADDED_LINES_COLOR
;
199 private static TextAttributes
getAttributesFor(Range range
) {
200 EditorColorsScheme globalScheme
= EditorColorsManager
.getInstance().getGlobalScheme();
201 Color color
= globalScheme
.getColor(getEditorColorNameFor(range
));
202 TextAttributes textAttributes
= new TextAttributes(null, color
, null, EffectType
.BOXED
, Font
.PLAIN
);
203 textAttributes
.setErrorStripeColor(color
);
204 return textAttributes
;
207 private static void paintGutterFragment(Editor editor
, Graphics g
, Rectangle r
, TextAttributesKey diffAttributeKey
) {
208 EditorGutterComponentEx gutter
= ((EditorEx
)editor
).getGutterComponentEx();
209 g
.setColor(editor
.getColorsScheme().getAttributes(diffAttributeKey
).getBackgroundColor());
210 int endX
= gutter
.getWhitespaceSeparatorOffset();
211 int x
= r
.x
+ r
.width
- 2;
212 int width
= endX
- x
;
214 g
.fillRect(x
, r
.y
+ 2, width
, r
.height
- 4);
215 g
.setColor(gutter
.getFoldingColor(false));
216 UIUtil
.drawLine(g
, x
, r
.y
+ 2, x
+ width
, r
.y
+ 2);
217 UIUtil
.drawLine(g
, x
, r
.y
+ 2, x
, r
.y
+ r
.height
- 3);
218 UIUtil
.drawLine(g
, x
, r
.y
+ r
.height
- 3, x
+ width
, r
.y
+ r
.height
- 3);
221 int[] xPoints
= new int[]{x
,
224 int[] yPoints
= new int[]{r
.y
- 4,
227 g
.fillPolygon(xPoints
, yPoints
, 3);
229 g
.setColor(gutter
.getFoldingColor(false));
230 g
.drawPolygon(xPoints
, yPoints
, 3);
235 private LineMarkerRenderer
createRenderer(final Range range
) {
236 return new ActiveGutterRenderer() {
237 public void paint(Editor editor
, Graphics g
, Rectangle r
) {
238 paintGutterFragment(editor
, g
, r
, getDiffColor(range
));
241 public void doAction(Editor editor
, MouseEvent e
) {
243 JComponent comp
= (JComponent
)e
.getComponent(); // shall be EditorGutterComponent, cast is safe.
244 JLayeredPane layeredPane
= comp
.getRootPane().getLayeredPane();
245 Point point
= SwingUtilities
.convertPoint(comp
, ((EditorEx
)editor
).getGutterComponentEx().getWidth(), e
.getY(), layeredPane
);
246 showActiveHint(range
, editor
, point
);
249 public boolean canDoAction(final MouseEvent e
) {
250 final EditorGutterComponentEx gutter
= (EditorGutterComponentEx
)e
.getComponent();
251 return e
.getX() > gutter
.getLineMarkerAreaOffset() + gutter
.getIconsAreaWidth();
256 public synchronized void release() {
258 if (!myIsInitialized
) return;
259 LOG
.assertTrue(!myIsReleased
);
261 removeHighlighters();
262 if (myDocumentListener
!= null) {
263 myDocument
.removeDocumentListener(myDocumentListener
);
264 myDocumentListener
= null;
272 public Document
getDocument() {
276 public VirtualFile
getVirtualFile() {
277 return FileDocumentManager
.getInstance().getFile(getDocument());
280 public List
<Range
> getRanges() {
284 public Document
getUpToDateDocument() {
285 return myUpToDateDocument
;
288 public void startBulkUpdate() {
290 for (Range oldRange
: myRanges
) {
291 removeHighlighter(oldRange
.getHighlighter());
292 oldRange
.setHighlighter(null);
297 public void finishBulkUpdate() {
299 myBulkUpdate
= false;
304 private class MyDocumentListener
extends DocumentAdapter
{
305 private int myFirstChangedLine
;
306 private int myUpToDateFirstLine
;
307 private int myUpToDateLastLine
;
308 private int myLastChangedLine
;
309 private int myLinesBeforeChange
;
311 public void beforeDocumentChange(DocumentEvent e
) {
312 if (myBulkUpdate
) return;
313 myFirstChangedLine
= myDocument
.getLineNumber(e
.getOffset());
314 myLastChangedLine
= myDocument
.getLineNumber(e
.getOffset() + e
.getOldLength());
315 if (StringUtil
.endsWithChar(e
.getOldFragment(), '\n')) myLastChangedLine
++;
317 myLinesBeforeChange
= myDocument
.getLineNumber(e
.getOffset() + e
.getOldLength()) - myDocument
.getLineNumber(e
.getOffset());
319 Range firstChangedRange
= getLastRangeBeforeLine(myFirstChangedLine
);
321 if (firstChangedRange
== null) {
322 myUpToDateFirstLine
= myFirstChangedLine
;
324 else if (firstChangedRange
.containsLine(myFirstChangedLine
)) {
325 myFirstChangedLine
= firstChangedRange
.getOffset1();
326 myUpToDateFirstLine
= firstChangedRange
.getUOffset1();
329 myUpToDateFirstLine
= firstChangedRange
.getUOffset2() + (myFirstChangedLine
- firstChangedRange
.getOffset2());
332 Range myLastChangedRange
= getLastRangeBeforeLine(myLastChangedLine
);
334 if (myLastChangedRange
== null) {
335 myUpToDateLastLine
= myLastChangedLine
;
337 else if (myLastChangedRange
.containsLine(myLastChangedLine
)) {
338 myUpToDateLastLine
= myLastChangedRange
.getUOffset2();
339 myLastChangedLine
= myLastChangedRange
.getOffset2();
342 myUpToDateLastLine
= myLastChangedRange
.getUOffset2() + (myLastChangedLine
- myLastChangedRange
.getOffset2());
348 private Range
getLastRangeBeforeLine(int line
) {
350 for (Range range
: myRanges
) {
351 if (range
.isMoreThen(line
)) return result
;
357 public void documentChanged(DocumentEvent e
) {
358 if (myBulkUpdate
) return;
360 int line
= myDocument
.getLineNumber(e
.getOffset() + e
.getNewLength());
361 int linesAfterChange
= line
- myDocument
.getLineNumber(e
.getOffset());
362 int linesShift
= linesAfterChange
- myLinesBeforeChange
;
364 List
<Range
> rangesAfterChange
= getRangesAfter(myLastChangedLine
);
365 List
<Range
> rangesBeforeChange
= getRangesBefore(myFirstChangedLine
);
367 List
<Range
> changedRanges
= getChangedRanges(myFirstChangedLine
, myLastChangedLine
);
369 int newSize
= rangesBeforeChange
.size() + changedRanges
.size() + rangesAfterChange
.size();
370 if (myRanges
.size() != newSize
) {
371 LOG
.info("Ranges: " + myRanges
+ "; first changed line: " + myFirstChangedLine
+ "; last changed line: " + myLastChangedLine
);
372 LOG
.assertTrue(false);
376 myLastChangedLine
+= linesShift
;
379 List
<Range
> newChangedRanges
= getNewChangedRanges();
381 shiftRanges(rangesAfterChange
, linesShift
);
383 if (!changedRanges
.equals(newChangedRanges
)) {
384 replaceRanges(changedRanges
, newChangedRanges
);
386 myRanges
= new ArrayList
<Range
>();
388 myRanges
.addAll(rangesBeforeChange
);
389 myRanges
.addAll(newChangedRanges
);
390 myRanges
.addAll(rangesAfterChange
);
392 if (myHighlighterCount
!= myRanges
.size()) {
393 LOG
.assertTrue(false, "Highlighters: " + myHighlighterCount
+ ", ranges: " + myRanges
.size());
396 myRanges
= mergeRanges(myRanges
);
398 for (Range range
: myRanges
) {
399 if (!range
.hasHighlighter()) range
.setHighlighter(createHighlighter(range
));
403 if (myHighlighterCount
!= myRanges
.size()) {
404 LOG
.assertTrue(false, "Highlighters: " + myHighlighterCount
+ ", ranges: " + myRanges
.size());
410 private List
<Range
> getNewChangedRanges() {
411 List
<String
> lines
= new DocumentWrapper(myDocument
).getLines(myFirstChangedLine
, myLastChangedLine
);
412 List
<String
> uLines
= new DocumentWrapper(myUpToDateDocument
)
413 .getLines(myUpToDateFirstLine
, myUpToDateLastLine
);
414 return new RangesBuilder(lines
, uLines
, myFirstChangedLine
, myUpToDateFirstLine
).getRanges();
417 private List
<Range
> mergeRanges(List
<Range
> ranges
) {
418 ArrayList
<Range
> result
= new ArrayList
<Range
>();
419 Iterator
<Range
> iterator
= ranges
.iterator();
420 if (!iterator
.hasNext()) return result
;
421 Range prev
= iterator
.next();
422 while (iterator
.hasNext()) {
423 Range range
= iterator
.next();
424 if (prev
.canBeMergedWith(range
)) {
425 prev
= prev
.mergeWith(range
, LineStatusTracker
.this);
436 private void replaceRanges(List
<Range
> rangesInChange
, List
<Range
> newRangesInChange
) {
437 for (Range range
: rangesInChange
) {
438 removeHighlighter(range
.getHighlighter());
439 range
.setHighlighter(null);
441 for (Range range
: newRangesInChange
) {
442 range
.setHighlighter(createHighlighter(range
));
446 private void shiftRanges(List
<Range
> rangesAfterChange
, int shift
) {
447 for (final Range aRangesAfterChange
: rangesAfterChange
) {
448 aRangesAfterChange
.shift(shift
);
454 private List
<Range
> getChangedRanges(int from
, int to
) {
455 return getChangedRanges(myRanges
, from
, to
);
458 public static List
<Range
> getChangedRanges(List
<Range
> ranges
, int from
, int to
) {
459 ArrayList
<Range
> result
= new ArrayList
<Range
>();
460 for (Range range
: ranges
) {
461 if (range
.getOffset1() <= to
&& range
.getOffset2() >= from
) result
.add(range
);
462 // if (range.getOffset1() > to) break;
467 private List
<Range
> getRangesBefore(int line
) {
468 return getRangesBefore(myRanges
, line
);
472 public static List
<Range
> getRangesBefore(List
<Range
> ranges
, int line
) {
473 ArrayList
<Range
> result
= new ArrayList
<Range
>();
475 for (Range range
: ranges
) {
476 if (range
.getOffset2() < line
) result
.add(range
);
477 //if (range.getOffset2() > line) break;
482 private List
<Range
> getRangesAfter(int line
) {
483 return getRangesAfter(myRanges
, line
);
486 public static List
<Range
> getRangesAfter(List
<Range
> ranges
, int line
) {
487 ArrayList
<Range
> result
= new ArrayList
<Range
>();
488 for (Range range
: ranges
) {
489 if (range
.getOffset1() > line
) result
.add(range
);
494 public void moveToRange(final Range range
, final Editor editor
) {
495 final int firstOffset
= myDocument
.getLineStartOffset(Math
.min(range
.getOffset1(), myDocument
.getLineCount() - 1));
496 editor
.getCaretModel().moveToOffset(firstOffset
);
497 editor
.getScrollingModel().scrollToCaret(ScrollType
.CENTER
);
499 editor
.getScrollingModel().runActionOnScrollingFinished(new Runnable() {
501 Point p
= editor
.visualPositionToXY(editor
.offsetToVisualPosition(firstOffset
));
502 JComponent editorComponent
= editor
.getContentComponent();
503 JLayeredPane layeredPane
= editorComponent
.getRootPane().getLayeredPane();
504 p
= SwingUtilities
.convertPoint(editorComponent
, 0, p
.y
, layeredPane
);
505 showActiveHint(range
, editor
, p
);
511 private Range
getNextRange(Range range
) {
512 int index
= myRanges
.indexOf(range
);
513 if (index
== myRanges
.size() - 1) return null;
514 return myRanges
.get(index
+ 1);
518 private Range
getPrevRange(Range range
) {
519 int index
= myRanges
.indexOf(range
);
520 if (index
<= 0) return null;
521 return myRanges
.get(index
- 1);
525 public Range
getNextRange(int line
) {
526 final Range currentRange
= getRangeForLine(line
);
527 if (currentRange
!= null) {
528 return getNextRange(currentRange
);
531 for (Range range
: myRanges
) {
532 if (line
> range
.getOffset1() || line
> range
.getOffset2()) {
541 public Range
getPrevRange(int line
) {
542 final Range currentRange
= getRangeForLine(line
);
543 if (currentRange
!= null) {
544 return getPrevRange(currentRange
);
547 for (ListIterator
<Range
> iterator
= myRanges
.listIterator(myRanges
.size()); iterator
.hasPrevious();) {
548 Range range
= iterator
.previous();
549 if (range
.getOffset1() > line
) {
558 public Range
getRangeForLine(final int line
) {
559 for (final Range range
: myRanges
) {
560 if (range
.getType() == Range
.DELETED
&& line
== range
.getOffset1()) {
563 else if (line
>= range
.getOffset1() && line
< range
.getOffset2()) {
570 public static abstract class MyAction
extends AnAction
implements DumbAware
{
571 protected final LineStatusTracker myLineStatusTracker
;
572 protected final Range myRange
;
574 protected MyAction(String text
, Icon icon
, LineStatusTracker lineStatusTracker
, Range range
) {
575 super(text
, null, icon
);
576 myLineStatusTracker
= lineStatusTracker
;
580 public void update(AnActionEvent e
) {
581 e
.getPresentation().setEnabled(isEnabled());
584 public abstract boolean isEnabled();
586 protected int getMyRangeIndex() {
587 List
<Range
> ranges
= myLineStatusTracker
.getRanges();
588 for (int i
= 0; i
< ranges
.size(); i
++) {
589 Range range
= ranges
.get(i
);
590 if (range
.getOffset1() == myRange
.getOffset1() && range
.getOffset2() == myRange
.getOffset2()) {
598 public static class RollbackAction
extends LineStatusTracker
.MyAction
{
599 public RollbackAction(LineStatusTracker lineStatusTracker
, Range range
, Editor editor
) {
600 super(VcsBundle
.message("action.name.rollback"), IconLoader
.getIcon("/actions/reset.png"), lineStatusTracker
, range
);
603 public boolean isEnabled() {
607 public void actionPerformed(AnActionEvent e
) {
608 CommandProcessor
.getInstance().executeCommand(myLineStatusTracker
.getProject(), new Runnable() {
610 ApplicationManager
.getApplication().runWriteAction(new Runnable() {
612 if (!myLineStatusTracker
.getDocument().isWritable()) {
613 final ReadonlyStatusHandler
.OperationStatus operationStatus
= ReadonlyStatusHandler
614 .getInstance(myLineStatusTracker
.getProject()).ensureFilesWritable(myLineStatusTracker
.getVirtualFile());
615 if (operationStatus
.hasReadonlyFiles()) return;
617 myLineStatusTracker
.rollbackChanges(myRange
);
621 }, VcsBundle
.message("command.name.rollback.change"), null);
626 public void rollbackChanges(Range range
) {
627 TextRange currentTextRange
= getCurrentTextRange(range
);
629 if (range
.getType() == Range
.INSERTED
) {
631 .replaceString(currentTextRange
.getStartOffset(), Math
.min(currentTextRange
.getEndOffset() + 1, myDocument
.getTextLength()), "");
633 else if (range
.getType() == Range
.DELETED
) {
634 String upToDateContent
= getUpToDateContent(range
);
635 myDocument
.insertString(currentTextRange
.getStartOffset(), upToDateContent
);
639 String upToDateContent
= getUpToDateContent(range
);
640 myDocument
.replaceString(currentTextRange
.getStartOffset(), Math
.min(currentTextRange
.getEndOffset() + 1, myDocument
.getTextLength()),
645 public String
getUpToDateContent(Range range
) {
646 TextRange textRange
= getUpToDateRange(range
);
647 final int startOffset
= textRange
.getStartOffset();
648 final int endOffset
= Math
.min(textRange
.getEndOffset() + 1, myUpToDateDocument
.getTextLength());
649 return myUpToDateDocument
.getCharsSequence().subSequence(startOffset
, endOffset
).toString();
652 private Project
getProject() {
656 public class ShowDiffAction
extends LineStatusTracker
.MyAction
{
657 public ShowDiffAction(LineStatusTracker lineStatusTracker
, Range range
, Editor editor
) {
658 super(VcsBundle
.message("action.name.show.difference"), IconLoader
.getIcon("/actions/diff.png"), lineStatusTracker
, range
);
661 public boolean isEnabled() {
662 return isModifiedRange() || isDeletedRange();
665 private boolean isDeletedRange() {
666 return myRange
.getType() == Range
.DELETED
;
669 private boolean isModifiedRange() {
670 return myRange
.getType() == Range
.MODIFIED
;
673 public void actionPerformed(AnActionEvent e
) {
674 DiffManager
.getInstance().getDiffTool().show(createDiffData());
677 private DiffRequest
createDiffData() {
678 return new DiffRequest(myLineStatusTracker
.getProject()) {
679 public DiffContent
[] getContents() {
680 return new DiffContent
[]{createDiffContent(myLineStatusTracker
.getUpToDateDocument(),
681 myLineStatusTracker
.getUpToDateRange(myRange
), null),
682 createDiffContent(myLineStatusTracker
.getDocument(), myLineStatusTracker
.getCurrentTextRange(myRange
),
683 myLineStatusTracker
.getVirtualFile())};
686 public String
[] getContentTitles() {
687 return new String
[]{VcsBundle
.message("diff.content.title.up.to.date"),
688 VcsBundle
.message("diff.content.title.current.range")};
691 public String
getWindowTitle() {
692 return VcsBundle
.message("dialog.title.diff.for.range");
697 private DiffContent
createDiffContent(final Document uDocument
, TextRange textRange
, VirtualFile file
) {
698 DiffContent diffContent
= new DocumentContent(myProject
, uDocument
);
699 return new FragmentContent(diffContent
, textRange
, myLineStatusTracker
.getProject(), file
);
703 public class CopyAction
extends MyAction
{
704 protected CopyAction(LineStatusTracker lineStatusTracker
, Range range
) {
705 super(VcsBundle
.message("action.name.copy.old.text"), IconLoader
.getIcon("/actions/copy.png"), lineStatusTracker
, range
);
708 public boolean isEnabled() {
709 return myRange
.getType() == Range
.DELETED
|| myRange
.getType() == Range
.MODIFIED
;
712 public void actionPerformed(final AnActionEvent e
) {
713 final String content
= myLineStatusTracker
.getUpToDateContent(myRange
);
714 CopyPasteManager
.getInstance().setContents(new StringSelection(content
));
718 private TextRange
getCurrentTextRange(Range range
) {
719 return getRange(range
.getType(), range
.getOffset1(), range
.getOffset2(), Range
.DELETED
, myDocument
);
722 private TextRange
getUpToDateRange(Range range
) {
723 return getRange(range
.getType(), range
.getUOffset1(), range
.getUOffset2(), Range
.INSERTED
, myUpToDateDocument
);
726 private static TextRange
getRange(byte rangeType
, int offset1
, int offset2
, byte emptyRangeCondition
, Document document
) {
727 if (rangeType
== emptyRangeCondition
) {
733 lineStartOffset
= document
.getLineEndOffset(offset1
- 1);
735 //if (lineStartOffset > 0) lineStartOffset--;
736 return new TextRange(lineStartOffset
, lineStartOffset
);
740 int startOffset
= document
.getLineStartOffset(offset1
);
741 int endOffset
= document
.getLineEndOffset(offset2
- 1);
742 if (startOffset
> 0) {
746 return new TextRange(startOffset
, endOffset
);
751 public void showActiveHint(Range range
, final Editor editor
, Point point
) {
753 DefaultActionGroup group
= new DefaultActionGroup();
755 final AnAction globalShowNextAction
= ActionManager
.getInstance().getAction("VcsShowNextChangeMarker");
756 final AnAction globalShowPrevAction
= ActionManager
.getInstance().getAction("VcsShowPrevChangeMarker");
758 final ShowPrevChangeMarkerAction localShowPrevAction
= new ShowPrevChangeMarkerAction(getPrevRange(range
), this, editor
);
759 final ShowNextChangeMarkerAction localShowNextAction
= new ShowNextChangeMarkerAction(getNextRange(range
), this, editor
);
761 JComponent editorComponent
= editor
.getComponent();
763 localShowNextAction
.registerCustomShortcutSet(localShowNextAction
.getShortcutSet(), editorComponent
);
764 localShowPrevAction
.registerCustomShortcutSet(localShowPrevAction
.getShortcutSet(), editorComponent
);
766 group
.add(localShowPrevAction
);
767 group
.add(localShowNextAction
);
769 localShowNextAction
.copyFrom(globalShowNextAction
);
770 localShowPrevAction
.copyFrom(globalShowPrevAction
);
772 group
.add(new RollbackAction(this, range
, editor
));
773 group
.add(new ShowDiffAction(this, range
, editor
));
774 group
.add(new CopyAction(this, range
));
776 final List
<AnAction
> actionList
= (List
<AnAction
>)editorComponent
.getClientProperty(AnAction
.ourClientProperty
);
778 actionList
.remove(globalShowPrevAction
);
779 actionList
.remove(globalShowNextAction
);
781 JComponent toolbar
= ActionManager
.getInstance().createActionToolbar(ActionPlaces
.FILEHISTORY_VIEW_TOOLBAR
, group
, true).getComponent();
783 final Color background
= ((EditorEx
)editor
).getBackroundColor();
784 final Color foreground
= editor
.getColorsScheme().getColor(EditorColors
.CARET_COLOR
);
785 toolbar
.setBackground(background
);
787 toolbar
.setBorder(new SideBorder2(foreground
, foreground
, range
.getType() != Range
.INSERTED ?
null : foreground
, foreground
, 1));
789 JPanel component
= new JPanel(new BorderLayout());
790 component
.setOpaque(false);
792 JPanel toolbarPanel
= new JPanel(new BorderLayout());
793 toolbarPanel
.setOpaque(false);
794 toolbarPanel
.add(toolbar
, BorderLayout
.WEST
);
795 component
.add(toolbarPanel
, BorderLayout
.NORTH
);
797 if (range
.getType() != Range
.INSERTED
) {
798 DocumentEx doc
= (DocumentEx
)myUpToDateDocument
;
799 EditorEx uEditor
= (EditorEx
)EditorFactory
.getInstance().createViewer(doc
, myProject
);
800 EditorHighlighter highlighter
= EditorHighlighterFactory
.getInstance().createEditorHighlighter(myProject
, getFileName());
801 uEditor
.setHighlighter(highlighter
);
803 EditorFragmentComponent editorFragmentComponent
=
804 EditorFragmentComponent
.createEditorFragmentComponent(uEditor
, range
.getUOffset1(), range
.getUOffset2(), false, false);
806 component
.add(editorFragmentComponent
, BorderLayout
.CENTER
);
807 EditorFactory
.getInstance().releaseEditor(uEditor
);
810 LightweightHint lightweightHint
= new LightweightHint(component
);
811 lightweightHint
.addHintListener(new HintListener() {
812 public void hintHidden(EventObject event
) {
813 actionList
.remove(localShowPrevAction
);
814 actionList
.remove(localShowNextAction
);
815 actionList
.add(globalShowPrevAction
);
816 actionList
.add(globalShowNextAction
);
820 HintManagerImpl
.getInstanceImpl().showEditorHint(lightweightHint
, editor
, point
, HintManagerImpl
.HIDE_BY_ANY_KEY
| HintManagerImpl
.HIDE_BY_TEXT_CHANGE
|
821 HintManagerImpl
.HIDE_BY_OTHER_HINT
| HintManagerImpl
.HIDE_BY_SCROLLING
,
825 private String
getFileName() {
826 VirtualFile file
= FileDocumentManager
.getInstance().getFile(myDocument
);
827 if (file
== null) return "";
828 return file
.getName();
831 public static LineStatusTracker
createOn(Document doc
, String upToDateContent
, Project project
) {
832 Document document
= EditorFactory
.getInstance().createDocument(StringUtil
.convertLineSeparators(upToDateContent
));
833 final LineStatusTracker tracker
= new LineStatusTracker(doc
, document
, project
);
834 tracker
.initialize(upToDateContent
);
838 public static LineStatusTracker
createOn(Document doc
, Project project
) {
839 Document document
= EditorFactory
.getInstance().createDocument("");
840 return new LineStatusTracker(doc
, document
, project
);