IDEA-212693 vcs: show navigatable links in annotations popup
[idea/community.git] / plugins / svn4idea / src / org / jetbrains / idea / svn / annotate / BaseSvnFileAnnotation.java
1 // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package org.jetbrains.idea.svn.annotate;
3
4 import com.intellij.openapi.vcs.VcsKey;
5 import com.intellij.openapi.vcs.annotate.*;
6 import com.intellij.openapi.vcs.history.VcsFileRevision;
7 import com.intellij.openapi.vcs.history.VcsRevisionNumber;
8 import com.intellij.util.text.DateFormatUtil;
9 import com.intellij.xml.util.XmlStringUtil;
10 import git4idea.annotate.AnnotationTooltipBuilder;
11 import org.jetbrains.annotations.NotNull;
12 import org.jetbrains.annotations.Nullable;
13 import org.jetbrains.idea.svn.SvnBundle;
14 import org.jetbrains.idea.svn.SvnConfiguration;
15 import org.jetbrains.idea.svn.SvnRevisionNumber;
16 import org.jetbrains.idea.svn.SvnVcs;
17 import org.jetbrains.idea.svn.api.Revision;
18 import org.jetbrains.idea.svn.checkin.CommitInfo;
19 import org.jetbrains.idea.svn.history.SvnFileRevision;
20
21 import java.util.*;
22
23 public abstract class BaseSvnFileAnnotation extends FileAnnotation {
24   protected final String myContents;
25   protected final VcsRevisionNumber myBaseRevision;
26   private final MyPartiallyCreatedInfos myInfos;
27
28   protected final SvnVcs myVcs;
29   private final Map<Long, SvnFileRevision> myRevisionMap = new HashMap<>();
30
31   private final LineAnnotationAspect DATE_ASPECT = new SvnAnnotationAspect(LineAnnotationAspect.DATE, true) {
32
33     @Override
34     public String getValue(@NotNull CommitInfo info) {
35       return DateFormatUtil.formatPrettyDate(info.getDate());
36     }
37   };
38
39   private final LineAnnotationAspect REVISION_ASPECT = new SvnAnnotationAspect(LineAnnotationAspect.REVISION, false) {
40
41     @Override
42     public String getValue(@NotNull CommitInfo info) {
43       return String.valueOf(info.getRevisionNumber());
44     }
45   };
46
47   private final LineAnnotationAspect ORIGINAL_REVISION_ASPECT = new SvnAnnotationAspect(SvnBundle.message("annotation.original.revision"), false) {
48     @Override
49     public String getValue(int lineNumber) {
50       final long value = myInfos.originalRevision(lineNumber);
51       return (value == -1) ? "" : String.valueOf(value);
52     }
53
54     @Override
55     protected long getRevision(int lineNum) {
56       return myInfos.originalRevision(lineNum);
57     }
58
59     @Override
60     public String getTooltipText(int lineNumber) {
61       // TODO: Check what is the difference in returning "" or null
62       if (!myInfos.isValid(lineNumber)) return "";
63
64       CommitInfo info = myInfos.get(lineNumber);
65       if (info == null) return null;
66
67       SvnFileRevision revision = myRevisionMap.get(info.getRevisionNumber());
68       return revision != null ? XmlStringUtil.escapeString("Revision " + info.getRevisionNumber() + ": " + revision.getCommitMessage()) : "";
69     }
70   };
71
72   private final LineAnnotationAspect AUTHOR_ASPECT = new SvnAnnotationAspect(LineAnnotationAspect.AUTHOR, true) {
73
74     @Override
75     public String getValue(@NotNull CommitInfo info) {
76       return info.getAuthor();
77     }
78   };
79
80   private final SvnConfiguration myConfiguration;
81   private final boolean myShowMergeSources;
82   // null if full annotation
83   private SvnRevisionNumber myFirstRevisionNumber;
84
85   public void setRevision(final long revision, final SvnFileRevision svnRevision) {
86     myRevisionMap.put(revision, svnRevision);
87   }
88
89   public SvnFileRevision getRevision(final long revision) {
90     return myRevisionMap.get(revision);
91   }
92
93   public void setFirstRevision(Revision revision) {
94     myFirstRevisionNumber = new SvnRevisionNumber(revision);
95   }
96
97   public SvnRevisionNumber getFirstRevisionNumber() {
98     return myFirstRevisionNumber;
99   }
100
101   public BaseSvnFileAnnotation(@NotNull SvnVcs vcs, final String contents, final VcsRevisionNumber baseRevision) {
102     super(vcs.getProject());
103     myVcs = vcs;
104     myContents = contents;
105     myBaseRevision = baseRevision;
106     myConfiguration = vcs.getSvnConfiguration();
107     myShowMergeSources = myConfiguration.isShowMergeSourcesInAnnotate();
108
109     myInfos = new MyPartiallyCreatedInfos();
110   }
111
112   @Override
113   public LineAnnotationAspect[] getAspects() {
114     return new LineAnnotationAspect[]{REVISION_ASPECT, DATE_ASPECT, AUTHOR_ASPECT};
115   }
116
117   @Nullable
118   @Override
119   public String getToolTip(int lineNumber) {
120     return getToolTip(lineNumber, false);
121   }
122
123   @Nullable
124   @Override
125   public String getHtmlToolTip(int lineNumber) {
126     return getToolTip(lineNumber, true);
127   }
128
129   @Nullable
130   private String getToolTip(int lineNumber, boolean asHtml) {
131     final CommitInfo info = myInfos.getOrNull(lineNumber);
132     if (info == null) return null;
133
134     SvnFileRevision revision = myRevisionMap.get(info.getRevisionNumber());
135     if (revision == null) return null;
136
137     String prefix = myInfos.getAnnotationSource(lineNumber).showMerged() ? "Merge source revision" : "Revision";
138     return AnnotationTooltipBuilder.buildSimpleTooltip(getProject(), asHtml, prefix,
139                                                        String.valueOf(info.getRevisionNumber()),
140                                                        revision.getCommitMessage());
141   }
142
143   @Override
144   public String getAnnotatedContent() {
145     return myContents;
146   }
147
148   public void setLineInfo(int lineNumber, @NotNull CommitInfo info, @Nullable CommitInfo mergeInfo) {
149     myInfos.appendNumberedLineInfo(lineNumber, info, mergeInfo);
150   }
151
152   @Override
153   @Nullable
154   public VcsRevisionNumber originalRevision(final int lineNumber) {
155     SvnFileRevision revision = myInfos.isValid(lineNumber) ? myRevisionMap.get(myInfos.originalRevision(lineNumber)) : null;
156
157     return revision != null ? revision.getRevisionNumber() : null;
158   }
159
160   @Override
161   public VcsRevisionNumber getLineRevisionNumber(final int lineNumber) {
162     CommitInfo info = myInfos.getOrNull(lineNumber);
163
164     return info != null && info.getRevisionNumber() >= 0 ? new SvnRevisionNumber(Revision.of(info.getRevisionNumber())) : null;
165   }
166
167   @Override
168   public Date getLineDate(int lineNumber) {
169     CommitInfo info = myInfos.getOrNull(lineNumber);
170
171     return info != null ? info.getDate() : null;
172   }
173
174   @Override
175   public List<VcsFileRevision> getRevisions() {
176     final List<VcsFileRevision> result = new ArrayList<>(myRevisionMap.values());
177     Collections.sort(result, (o1, o2) -> o2.getRevisionNumber().compareTo(o1.getRevisionNumber()));
178     return result;
179   }
180
181   @Override
182   @Nullable
183   public AnnotationSourceSwitcher getAnnotationSourceSwitcher() {
184     if (! myShowMergeSources) return null;
185     return new AnnotationSourceSwitcher() {
186       @Override
187       @NotNull
188       public AnnotationSource getAnnotationSource(int lineNumber) {
189         return myInfos.getAnnotationSource(lineNumber);
190       }
191
192       @Override
193       public boolean mergeSourceAvailable(int lineNumber) {
194         return myInfos.mergeSourceAvailable(lineNumber);
195       }
196
197       @Override
198       @NotNull
199       public LineAnnotationAspect getRevisionAspect() {
200         return ORIGINAL_REVISION_ASPECT;
201       }
202
203       @Override
204       @NotNull
205       public AnnotationSource getDefaultSource() {
206         return AnnotationSource.getInstance(myShowMergeSources);
207       }
208
209       @Override
210       public void switchTo(AnnotationSource source) {
211         myInfos.setShowMergeSource(source.showMerged());
212       }
213     };
214   }
215
216   @Override
217   public int getLineCount() {
218     return myInfos.size();
219   }
220
221   @Override
222   public VcsKey getVcsKey() {
223     return SvnVcs.getKey();
224   }
225
226   private abstract class SvnAnnotationAspect extends LineAnnotationAspectAdapter {
227
228     SvnAnnotationAspect(String id, boolean showByDefault) {
229       super(id, showByDefault);
230     }
231
232     protected long getRevision(final int lineNum) {
233       final CommitInfo lineInfo = myInfos.get(lineNum);
234       return (lineInfo == null) ? -1 : lineInfo.getRevisionNumber();
235     }
236
237     @Override
238     protected void showAffectedPaths(int lineNum) {
239       if (myInfos.isValid(lineNum)) {
240         final long revision = getRevision(lineNum);
241         if (revision >= 0) {
242           showAllAffectedPaths(new SvnRevisionNumber(Revision.of(revision)));
243         }
244       }
245     }
246
247     @Override
248     public String getValue(int lineNumber) {
249       CommitInfo info = myInfos.getOrNull(lineNumber);
250
251       return info == null ? "" : getValue(info);
252     }
253
254     public String getValue(@NotNull CommitInfo info) {
255       return "";
256     }
257   }
258
259   protected abstract void showAllAffectedPaths(SvnRevisionNumber number);
260
261   private static class MyPartiallyCreatedInfos {
262     private boolean myShowMergeSource;
263     private final Map<Integer, CommitInfo> myMappedLineInfo;
264     private final Map<Integer, CommitInfo> myMergeSourceInfos;
265     private int myMaxIdx;
266
267     private MyPartiallyCreatedInfos() {
268       myMergeSourceInfos = new HashMap<>();
269       myMappedLineInfo = new HashMap<>();
270       myMaxIdx = 0;
271     }
272
273     void setShowMergeSource(boolean showMergeSource) {
274       myShowMergeSource = showMergeSource;
275     }
276
277     int size() {
278       return myMaxIdx + 1;
279     }
280
281     void appendNumberedLineInfo(final int lineNumber, @NotNull CommitInfo info, @Nullable CommitInfo mergeInfo) {
282       if (myMappedLineInfo.get(lineNumber) != null) return;
283       myMaxIdx = (myMaxIdx < lineNumber) ? lineNumber : myMaxIdx;
284       myMappedLineInfo.put(lineNumber, info);
285       if (mergeInfo != null) {
286         myMergeSourceInfos.put(lineNumber, mergeInfo);
287       }
288     }
289
290     CommitInfo get(final int idx) {
291       if (myShowMergeSource) {
292         final CommitInfo lineInfo = myMergeSourceInfos.get(idx);
293         if (lineInfo != null) {
294           return lineInfo;
295         }
296       }
297       return myMappedLineInfo.get(idx);
298     }
299
300     @Nullable
301     CommitInfo getOrNull(int lineNumber) {
302       return isValid(lineNumber) ? get(lineNumber) : null;
303     }
304
305     private boolean isValid(int lineNumber) {
306       return lineNumber >= 0 && lineNumber < size();
307     }
308
309     AnnotationSource getAnnotationSource(final int line) {
310       return myShowMergeSource ? AnnotationSource.getInstance(myMergeSourceInfos.containsKey(line)) : AnnotationSource.LOCAL;
311     }
312
313     public long originalRevision(final int line) {
314       CommitInfo info = line < size() ? myMappedLineInfo.get(line) : null;
315
316       return info == null ? -1 : info.getRevisionNumber();
317     }
318
319     public boolean mergeSourceAvailable(int lineNumber) {
320       return myMergeSourceInfos.containsKey(lineNumber);
321     }
322   }
323
324   @Nullable
325   @Override
326   public VcsRevisionNumber getCurrentRevision() {
327     return myBaseRevision;
328   }
329 }