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 git4idea
.annotate
;
18 import com
.intellij
.openapi
.diagnostic
.Logger
;
19 import com
.intellij
.openapi
.editor
.EditorGutterAction
;
20 import com
.intellij
.openapi
.project
.Project
;
21 import com
.intellij
.openapi
.vcs
.FileStatus
;
22 import com
.intellij
.openapi
.vcs
.FileStatusListener
;
23 import com
.intellij
.openapi
.vcs
.FileStatusManager
;
24 import com
.intellij
.openapi
.vcs
.VcsException
;
25 import com
.intellij
.openapi
.vcs
.annotate
.*;
26 import com
.intellij
.openapi
.vcs
.history
.VcsFileRevision
;
27 import com
.intellij
.openapi
.vcs
.history
.VcsRevisionNumber
;
28 import com
.intellij
.openapi
.vfs
.VirtualFile
;
29 import com
.intellij
.openapi
.vfs
.VirtualFileAdapter
;
30 import com
.intellij
.openapi
.vfs
.VirtualFileEvent
;
31 import com
.intellij
.openapi
.vfs
.VirtualFileManager
;
32 import com
.intellij
.util
.EventDispatcher
;
33 import com
.intellij
.util
.text
.SyncDateFormat
;
34 import git4idea
.GitRevisionNumber
;
35 import git4idea
.actions
.GitShowAllSubmittedFilesAction
;
36 import git4idea
.i18n
.GitBundle
;
37 import org
.jetbrains
.annotations
.NotNull
;
40 import java
.text
.SimpleDateFormat
;
42 import java
.util
.List
;
45 * Git file annotation implementation
47 * Based on the JetBrains SVNAnnotationProvider.
49 public class GitFileAnnotation
implements FileAnnotation
{
50 private final static Logger LOG
= Logger
.getInstance("#git4idea.annotate.GitFileAnnotation");
53 * the format of the date shown in annotations
55 private static final SyncDateFormat DATE_FORMAT
= new SyncDateFormat(SimpleDateFormat
.getDateInstance(SimpleDateFormat
.SHORT
));
59 private final StringBuffer myContentBuffer
= new StringBuffer();
61 * The currently annotated lines
63 private final ArrayList
<LineInfo
> myLines
= new ArrayList
<LineInfo
>();
65 * The project reference
67 private final Project myProject
;
69 * Annotation change listeners
71 private final EventDispatcher
<AnnotationListener
> myListeners
= EventDispatcher
.create(AnnotationListener
.class);
73 * Map from revision numbers to revisions
75 private final Map
<VcsRevisionNumber
, VcsFileRevision
> myRevisionMap
= new HashMap
<VcsRevisionNumber
, VcsFileRevision
>();
77 * listener for file system events
79 private final VirtualFileAdapter myFileListener
;
81 private final MyFileStatusListener myFileStatusListener
;
84 * the virtual file for which annotations are generated
86 private final VirtualFile myFile
;
88 * If true, file system is monitored for changes
90 private final boolean myMonitorFlag
;
93 * Date annotation aspect
95 private final LineAnnotationAspect DATE_ASPECT
= new LineAnnotationAspectAdapter() {
96 public String
getValue(int lineNumber
) {
97 if (myLines
.size() <= lineNumber
|| lineNumber
< 0 || myLines
.get(lineNumber
) == null) {
101 final Date date
= myLines
.get(lineNumber
).getDate();
102 return date
== null ?
"" : DATE_FORMAT
.format(date
);
107 * revision annotation aspect
109 private final LineAnnotationAspect REVISION_ASPECT
= new RevisionAnnotationAspect();
111 * author annotation aspect
113 private final LineAnnotationAspect AUTHOR_ASPECT
= new LineAnnotationAspectAdapter() {
114 public String
getValue(int lineNumber
) {
115 if (myLines
.size() <= lineNumber
|| lineNumber
< 0 || myLines
.get(lineNumber
) == null) {
119 final String author
= myLines
.get(lineNumber
).getAuthor();
120 return author
== null ?
"" : author
;
128 * @param project the project of annotation provider
129 * @param file the git root
130 * @param monitorFlag if false the file system will not be listened for changes (used for annotated files from the repository).
132 public GitFileAnnotation(@NotNull final Project project
, @NotNull VirtualFile file
, final boolean monitorFlag
) {
135 myMonitorFlag
= monitorFlag
;
137 myFileListener
= new VirtualFileAdapter() {
139 public void contentsChanged(final VirtualFileEvent event
) {
140 if (myFile
!= event
.getFile()) return;
141 if (!event
.isFromRefresh()) return;
142 fireAnnotationChanged();
145 VirtualFileManager
.getInstance().addVirtualFileListener(myFileListener
);
146 myFileStatusListener
= new MyFileStatusListener();
147 FileStatusManager
.getInstance(myProject
).addFileStatusListener(myFileStatusListener
);
150 myFileListener
= null;
151 myFileStatusListener
= null;
156 * Add revisions to the list (from log)
158 * @param revisions revisions to add
160 public void addLogEntries(List
<VcsFileRevision
> revisions
) {
161 for (VcsFileRevision vcsFileRevision
: revisions
) {
162 myRevisionMap
.put(vcsFileRevision
.getRevisionNumber(), vcsFileRevision
);
167 * Fire annotation changed event
169 private void fireAnnotationChanged() {
170 myListeners
.getMulticaster().onAnnotationChanged();
171 LOG
.debug("annotations changed fired from...", new Throwable());
177 public void addListener(AnnotationListener listener
) {
178 myListeners
.addListener(listener
);
184 public void removeListener(AnnotationListener listener
) {
185 myListeners
.removeListener(listener
);
191 public void dispose() {
193 VirtualFileManager
.getInstance().removeVirtualFileListener(myFileListener
);
194 FileStatusManager
.getInstance(myProject
).removeFileStatusListener(myFileStatusListener
);
201 public LineAnnotationAspect
[] getAspects() {
202 return new LineAnnotationAspect
[]{REVISION_ASPECT
, DATE_ASPECT
, AUTHOR_ASPECT
};
208 public String
getToolTip(final int lineNumber
) {
209 if (myLines
.size() <= lineNumber
|| lineNumber
< 0) {
212 final LineInfo info
= myLines
.get(lineNumber
);
216 VcsFileRevision fileRevision
= myRevisionMap
.get(info
.getRevision());
217 if (fileRevision
!= null) {
219 .message("annotation.tool.tip", info
.getRevision().asString(), fileRevision
.getAuthor(), fileRevision
.getRevisionDate(),
220 fileRevision
.getCommitMessage());
230 public String
getAnnotatedContent() {
231 return myContentBuffer
.toString();
237 public List
<VcsFileRevision
> getRevisions() {
238 final List
<VcsFileRevision
> result
= new ArrayList
<VcsFileRevision
>(myRevisionMap
.values());
239 Collections
.sort(result
, new Comparator
<VcsFileRevision
>() {
240 public int compare(final VcsFileRevision o1
, final VcsFileRevision o2
) {
241 return -1 * o1
.getRevisionNumber().compareTo(o2
.getRevisionNumber());
247 public AnnotationSourceSwitcher
getAnnotationSourceSwitcher() {
254 public VcsRevisionNumber
getLineRevisionNumber(final int lineNumber
) {
255 if (myLines
.size() <= lineNumber
|| lineNumber
< 0 || myLines
.get(lineNumber
) == null) {
258 final LineInfo lineInfo
= myLines
.get(lineNumber
);
259 return lineInfo
== null ?
null : lineInfo
.getRevision();
263 * Get revision number for the line.
265 public VcsRevisionNumber
originalRevision(int lineNumber
) {
266 return getLineRevisionNumber(lineNumber
);
272 * @param date the revision date
273 * @param revision the revision number
274 * @param author the author
275 * @param line the line content
276 * @param lineNumber the line number for revision
277 * @throws VcsException in case when line could not be processed
279 public void appendLineInfo(final Date date
,
280 final GitRevisionNumber revision
,
283 final long lineNumber
) throws VcsException
{
284 int expectedLineNo
= myLines
.size() + 1;
285 if (lineNumber
!= expectedLineNo
) {
286 throw new VcsException("Adding for info for line " + lineNumber
+ " but we are expecting it to be for " + expectedLineNo
);
288 myLines
.add(new LineInfo(date
, revision
, author
));
289 myContentBuffer
.append(line
);
293 * Revision annotation aspect implementation
295 private class RevisionAnnotationAspect
extends LineAnnotationAspectAdapter
implements EditorGutterAction
{
299 public String
getValue(int lineNumber
) {
300 if (myLines
.size() <= lineNumber
|| lineNumber
< 0 || myLines
.get(lineNumber
) == null) {
304 final GitRevisionNumber revision
= myLines
.get(lineNumber
).getRevision();
305 return revision
== null ?
"" : String
.valueOf(revision
.getShortRev());
312 public Cursor
getCursor(final int lineNum
) {
313 return Cursor
.getPredefinedCursor(Cursor
.HAND_CURSOR
);
319 public void doAction(int lineNum
) {
320 if (lineNum
>= 0 && lineNum
< myLines
.size()) {
321 final LineInfo info
= myLines
.get(lineNum
);
322 VcsFileRevision revision
= myRevisionMap
.get(info
.getRevision());
323 if (revision
!= null) {
324 GitShowAllSubmittedFilesAction
.showSubmittedFiles(myProject
, revision
, myFile
);
333 static class LineInfo
{
337 private final Date myDate
;
341 private final GitRevisionNumber myRevision
;
343 * the author of the change
345 private final String myAuthor
;
350 * @param date date of the change
351 * @param revision revision number
352 * @param author the author of the change
354 public LineInfo(final Date date
, final GitRevisionNumber revision
, final String author
) {
356 myRevision
= revision
;
361 * @return the revision date
363 public Date
getDate() {
368 * @return the revision number
370 public GitRevisionNumber
getRevision() {
375 * @return the author of the change
377 public String
getAuthor() {
382 private class MyFileStatusListener
implements FileStatusListener
{
383 public void fileStatusesChanged() {
387 public void fileStatusChanged(@NotNull VirtualFile virtualFile
) {
388 if (myFile
.equals(virtualFile
)) {
393 private void checkAndFire() {
394 // for the case of commit changes... remove annotation gutter
395 if (FileStatus
.NOT_CHANGED
.equals(FileStatusManager
.getInstance(myProject
).getStatus(myFile
))) {
396 fireAnnotationChanged();