diff: fix refresh of FakeContent
[idea/community.git] / platform / vcs-impl / src / com / intellij / openapi / vcs / changes / CacheChangeProcessor.java
1 /*
2  * Copyright 2000-2015 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.intellij.openapi.vcs.changes;
17
18 import com.intellij.diff.chains.DiffRequestProducerException;
19 import com.intellij.diff.impl.DiffRequestProcessor;
20 import com.intellij.diff.requests.*;
21 import com.intellij.diff.tools.util.SoftHardCacheMap;
22 import com.intellij.diff.util.DiffUserDataKeys;
23 import com.intellij.diff.util.DiffUserDataKeysEx.ScrollToPolicy;
24 import com.intellij.diff.util.WaitingBackgroundableTaskExecutor;
25 import com.intellij.icons.AllIcons;
26 import com.intellij.openapi.actionSystem.AnAction;
27 import com.intellij.openapi.actionSystem.AnActionEvent;
28 import com.intellij.openapi.diagnostic.Logger;
29 import com.intellij.openapi.progress.ProcessCanceledException;
30 import com.intellij.openapi.progress.ProgressIndicator;
31 import com.intellij.openapi.progress.util.ProgressWindow;
32 import com.intellij.openapi.project.DumbAwareAction;
33 import com.intellij.openapi.project.Project;
34 import com.intellij.openapi.util.Pair;
35 import com.intellij.openapi.vcs.changes.actions.diff.ChangeDiffRequestProducer;
36 import com.intellij.util.containers.ContainerUtil;
37 import com.intellij.util.containers.Convertor;
38 import org.jetbrains.annotations.*;
39
40 import java.util.Collections;
41 import java.util.List;
42
43 public abstract class CacheChangeProcessor extends DiffRequestProcessor {
44   private static final Logger LOG = Logger.getInstance(CacheChangeProcessor.class);
45
46   @NotNull private final Project myProject;
47
48   @NotNull private final SoftHardCacheMap<Change, Pair<Change, DiffRequest>> myRequestCache =
49     new SoftHardCacheMap<Change, Pair<Change, DiffRequest>>(5, 5);
50
51   @Nullable private Change myCurrentChange;
52
53   @NotNull private final WaitingBackgroundableTaskExecutor myTaskExecutor = new WaitingBackgroundableTaskExecutor();
54
55   public CacheChangeProcessor(@NotNull Project project) {
56     super(project);
57     myProject = project;
58   }
59
60   //
61   // Abstract
62   //
63
64   @NotNull
65   protected abstract List<Change> getSelectedChanges();
66
67   @NotNull
68   protected abstract List<Change> getAllChanges();
69
70   protected abstract void selectChange(@NotNull Change change);
71
72   //
73   // Update
74   //
75
76   public void updateRequest(final boolean force, @Nullable final ScrollToPolicy scrollToChangePolicy) {
77     final Change change = myCurrentChange;
78     DiffRequest cachedRequest = loadRequestFast(change);
79     if (cachedRequest != null) {
80       applyRequest(cachedRequest, force, scrollToChangePolicy);
81       return;
82     }
83
84     myTaskExecutor.execute(
85       new Convertor<ProgressIndicator, Runnable>() {
86         @Override
87         public Runnable convert(ProgressIndicator indicator) {
88           final DiffRequest request = loadRequest(change, indicator);
89           return new Runnable() {
90             @Override
91             public void run() {
92               myRequestCache.put(change, Pair.create(change, request));
93               applyRequest(request, force, scrollToChangePolicy);
94             }
95           };
96         }
97       },
98       new Runnable() {
99         @Override
100         public void run() {
101           applyRequest(new LoadingDiffRequest(ChangeDiffRequestProducer.getRequestTitle(change)), force, scrollToChangePolicy);
102         }
103       },
104       ProgressWindow.DEFAULT_PROGRESS_DIALOG_POSTPONE_TIME_MILLIS
105     );
106   }
107
108   @Nullable
109   @Contract("null -> !null")
110   protected DiffRequest loadRequestFast(@Nullable Change change) {
111     if (change == null) return new NoDiffRequest();
112
113     Pair<Change, DiffRequest> pair = myRequestCache.get(change);
114     if (pair != null) {
115       Change oldChange = pair.first;
116       if (ChangeDiffRequestProducer.isEquals(oldChange, change)) {
117         return pair.second;
118       }
119     }
120
121     if (change.getBeforeRevision() instanceof FakeRevision || change.getAfterRevision() instanceof FakeRevision) {
122       return new LoadingDiffRequest(ChangeDiffRequestProducer.getRequestTitle(change));
123     }
124
125     return null;
126   }
127
128   @NotNull
129   @CalledInBackground
130   private DiffRequest loadRequest(@NotNull Change change, @NotNull ProgressIndicator indicator) {
131     ChangeDiffRequestProducer presentable = ChangeDiffRequestProducer.create(myProject, change);
132     if (presentable == null) return new ErrorDiffRequest("Can't show diff");
133     try {
134       return presentable.process(getContext(), indicator);
135     }
136     catch (ProcessCanceledException e) {
137       OperationCanceledDiffRequest request = new OperationCanceledDiffRequest(presentable.getName());
138       request.putUserData(DiffUserDataKeys.CONTEXT_ACTIONS, Collections.<AnAction>singletonList(new ReloadRequestAction(change)));
139       return request;
140     }
141     catch (DiffRequestProducerException e) {
142       return new ErrorDiffRequest(presentable, e);
143     }
144     catch (Exception e) {
145       return new ErrorDiffRequest(presentable, e);
146     }
147   }
148
149   //
150   // Impl
151   //
152
153   @Override
154   protected void onDispose() {
155     super.onDispose();
156     myTaskExecutor.abort();
157     myRequestCache.clear();
158   }
159
160   //
161   // Navigation
162   //
163
164   /*
165    * Multiple selection:
166    * - iterate inside selection
167    *
168    * Single selection:
169    * - iterate all changes
170    * - update selection after movement
171    *
172    * current element should always be among allChanges and selection (if they are not empty)
173    */
174
175   public void clear() {
176     myCurrentChange = null;
177     updateRequest();
178   }
179
180   @CalledInAwt
181   public void refresh() {
182     List<Change> selectedChanges = getSelectedChanges();
183
184     if (selectedChanges.isEmpty()) {
185       myCurrentChange = null;
186       updateRequest();
187       return;
188     }
189
190     Change selectedChange = myCurrentChange != null ? ContainerUtil.find(selectedChanges, myCurrentChange) : null;
191     if (selectedChange == null) {
192       myCurrentChange = selectedChanges.get(0);
193       updateRequest();
194       return;
195     }
196
197     if (myCurrentChange.getBeforeRevision() instanceof FakeRevision || myCurrentChange.getAfterRevision() instanceof FakeRevision) {
198       myCurrentChange = selectedChange;
199       updateRequest();
200     }
201   }
202
203   @Override
204   protected boolean hasNextChange() {
205     if (myCurrentChange == null) return false;
206
207     List<Change> selectedChanges = getSelectedChanges();
208     if (selectedChanges.isEmpty()) return false;
209
210     if (selectedChanges.size() > 1) {
211       int index = selectedChanges.indexOf(myCurrentChange);
212       return index != -1 && index < selectedChanges.size() - 1;
213     }
214     else {
215       List<Change> allChanges = getAllChanges();
216       int index = allChanges.indexOf(myCurrentChange);
217       return index != -1 && index < allChanges.size() - 1;
218     }
219   }
220
221   @Override
222   protected boolean hasPrevChange() {
223     if (myCurrentChange == null) return false;
224
225     List<Change> selectedChanges = getSelectedChanges();
226     if (selectedChanges.isEmpty()) return false;
227
228     if (selectedChanges.size() > 1) {
229       int index = selectedChanges.indexOf(myCurrentChange);
230       return index != -1 && index > 0;
231     }
232     else {
233       List<Change> allChanges = getAllChanges();
234       int index = allChanges.indexOf(myCurrentChange);
235       return index != -1 && index > 0;
236     }
237   }
238
239   @Override
240   protected void goToNextChange(boolean fromDifferences) {
241     List<Change> selectedChanges = getSelectedChanges();
242     List<Change> allChanges = getAllChanges();
243
244     if (selectedChanges.size() > 1) {
245       int index = selectedChanges.indexOf(myCurrentChange);
246       myCurrentChange = selectedChanges.get(index + 1);
247     }
248     else {
249       int index = allChanges.indexOf(myCurrentChange);
250       myCurrentChange = allChanges.get(index + 1);
251       selectChange(myCurrentChange);
252     }
253
254     updateRequest(false, fromDifferences ? ScrollToPolicy.FIRST_CHANGE : null);
255   }
256
257   @Override
258   protected void goToPrevChange(boolean fromDifferences) {
259     List<Change> selectedChanges = getSelectedChanges();
260     List<Change> allChanges = getAllChanges();
261
262     if (selectedChanges.size() > 1) {
263       int index = selectedChanges.indexOf(myCurrentChange);
264       myCurrentChange = selectedChanges.get(index - 1);
265     }
266     else {
267       int index = allChanges.indexOf(myCurrentChange);
268       myCurrentChange = allChanges.get(index - 1);
269       selectChange(myCurrentChange);
270     }
271
272     updateRequest(false, fromDifferences ? ScrollToPolicy.LAST_CHANGE : null);
273   }
274
275   @Override
276   protected boolean isNavigationEnabled() {
277     return getSelectedChanges().size() > 1 || getAllChanges().size() > 1;
278   }
279
280   //
281   // Actions
282   //
283
284   protected class ReloadRequestAction extends DumbAwareAction {
285     @NotNull private final Change myChange;
286
287     public ReloadRequestAction(@NotNull Change change) {
288       super("Reload", null, AllIcons.Actions.Refresh);
289       myChange = change;
290     }
291
292     @Override
293     public void actionPerformed(AnActionEvent e) {
294       myRequestCache.remove(myChange);
295       updateRequest(true);
296     }
297   }
298 }