[git] IDEA-115318 Clicking "amend" shouldn't revert what user has typed
[idea/community.git] / platform / vcs-impl / src / com / intellij / openapi / vcs / changes / committed / CommittedChangesPanel.java
1 /*
2  * Copyright 2000-2011 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
17 /*
18  * Created by IntelliJ IDEA.
19  * User: yole
20  * Date: 05.12.2006
21  * Time: 19:39:22
22  */
23 package com.intellij.openapi.vcs.changes.committed;
24
25 import com.intellij.openapi.Disposable;
26 import com.intellij.openapi.actionSystem.*;
27 import com.intellij.openapi.application.ApplicationManager;
28 import com.intellij.openapi.application.ModalityState;
29 import com.intellij.openapi.diagnostic.Logger;
30 import com.intellij.openapi.progress.ProgressIndicator;
31 import com.intellij.openapi.progress.ProgressManager;
32 import com.intellij.openapi.progress.Task;
33 import com.intellij.openapi.project.Project;
34 import com.intellij.openapi.ui.Messages;
35 import com.intellij.openapi.util.Disposer;
36 import com.intellij.openapi.util.text.StringUtil;
37 import com.intellij.openapi.vcs.*;
38 import com.intellij.openapi.vcs.changes.BackgroundFromStartOption;
39 import com.intellij.openapi.vcs.versionBrowser.ChangeBrowserSettings;
40 import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList;
41 import com.intellij.openapi.vfs.VirtualFile;
42 import com.intellij.ui.FilterComponent;
43 import com.intellij.util.AsynchConsumer;
44 import com.intellij.util.BufferedListConsumer;
45 import com.intellij.util.Consumer;
46 import com.intellij.util.WaitForProgressToShow;
47 import com.intellij.util.containers.ContainerUtil;
48 import org.jetbrains.annotations.NotNull;
49 import org.jetbrains.annotations.Nullable;
50
51 import javax.swing.*;
52 import javax.swing.event.ChangeEvent;
53 import javax.swing.event.ChangeListener;
54 import java.awt.*;
55 import java.util.ArrayList;
56 import java.util.Collections;
57 import java.util.LinkedList;
58 import java.util.List;
59
60 public class CommittedChangesPanel extends JPanel implements TypeSafeDataProvider, Disposable {
61   private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vcs.changes.committed.CommittedChangesPanel");
62
63   private final CommittedChangesTreeBrowser myBrowser;
64   private final Project myProject;
65   private CommittedChangesProvider myProvider;
66   private ChangeBrowserSettings mySettings;
67   private final RepositoryLocation myLocation;
68   private int myMaxCount = 0;
69   private final MyFilterComponent myFilterComponent = new MyFilterComponent();
70   private final List<Runnable> myShouldBeCalledOnDispose;
71   private volatile boolean myDisposed;
72   private volatile boolean myInLoad;
73   private Consumer<String> myIfNotCachedReloader;
74
75   public CommittedChangesPanel(Project project, final CommittedChangesProvider provider, final ChangeBrowserSettings settings,
76                                @Nullable final RepositoryLocation location, @Nullable ActionGroup extraActions) {
77     super(new BorderLayout());
78     mySettings = settings;
79     myProject = project;
80     myProvider = provider;
81     myLocation = location;
82     myShouldBeCalledOnDispose = new ArrayList<Runnable>();
83     myBrowser = new CommittedChangesTreeBrowser(project, new ArrayList<CommittedChangeList>());
84     Disposer.register(this, myBrowser);
85     add(myBrowser, BorderLayout.CENTER);
86
87     final VcsCommittedViewAuxiliary auxiliary = provider.createActions(myBrowser, location);
88
89     JPanel toolbarPanel = new JPanel();
90     toolbarPanel.setLayout(new BoxLayout(toolbarPanel, BoxLayout.X_AXIS));
91
92     ActionGroup group = (ActionGroup) ActionManager.getInstance().getAction("CommittedChangesToolbar");
93
94     ActionToolbar toolBar = myBrowser.createGroupFilterToolbar(project, group, extraActions,
95                                                                auxiliary != null ? auxiliary.getToolbarActions() : Collections.<AnAction>emptyList());
96     toolbarPanel.add(toolBar.getComponent());
97     toolbarPanel.add(Box.createHorizontalGlue());
98     toolbarPanel.add(myFilterComponent);
99     myFilterComponent.setMinimumSize(myFilterComponent.getPreferredSize());
100     myFilterComponent.setMaximumSize(myFilterComponent.getPreferredSize());
101     myBrowser.setToolBar(toolbarPanel);
102
103     if (auxiliary != null) {
104       myShouldBeCalledOnDispose.add(auxiliary.getCalledOnViewDispose());
105       myBrowser.setTableContextMenu(group, auxiliary.getPopupActions());
106     } else {
107       myBrowser.setTableContextMenu(group, Collections.<AnAction>emptyList());
108     }
109     
110     final AnAction anAction = ActionManager.getInstance().getAction("CommittedChanges.Refresh");
111     anAction.registerCustomShortcutSet(CommonShortcuts.getRerun(), this);
112     myBrowser.addFilter(myFilterComponent);
113     myIfNotCachedReloader = myLocation == null ? null : new Consumer<String>() {
114       @Override
115       public void consume(String s) {
116         refreshChanges(false);
117       }
118     };
119   }
120
121   public RepositoryLocation getRepositoryLocation() {
122     return myLocation;
123   }
124
125   public void setMaxCount(final int maxCount) {
126     myMaxCount = maxCount;
127   }
128
129   public void setProvider(final CommittedChangesProvider provider) {
130     if (myProvider != provider) {
131       myProvider = provider;
132       mySettings = provider.createDefaultSettings(); 
133     }
134   }
135
136   public void refreshChanges(final boolean cacheOnly) {
137     if (myLocation != null) {
138       refreshChangesFromLocation();
139     }
140     else {
141       refreshChangesFromCache(cacheOnly);
142     }
143   }
144
145   private void refreshChangesFromLocation() {
146     myBrowser.reset();
147
148     myInLoad = true;
149     myBrowser.setLoading(true);
150     ProgressManager.getInstance().run(new Task.Backgroundable(myProject, "Loading changes", true, BackgroundFromStartOption.getInstance()) {
151       
152       public void run(@NotNull final ProgressIndicator indicator) {
153         try {
154           final AsynchConsumer<List<CommittedChangeList>> appender = new AsynchConsumer<List<CommittedChangeList>>() {
155             public void finished() {
156             }
157
158             public void consume(final List<CommittedChangeList> list) {
159               new AbstractCalledLater(myProject, ModalityState.stateForComponent(myBrowser)) {
160                 public void run() {
161                   myBrowser.append(list);
162                 }
163               }.callMe();
164             }
165           };
166           final BufferedListConsumer<CommittedChangeList> bufferedListConsumer = new BufferedListConsumer<CommittedChangeList>(30, appender,-1);
167
168           myProvider.loadCommittedChanges(mySettings, myLocation, myMaxCount, new AsynchConsumer<CommittedChangeList>() {
169             public void finished() {
170               bufferedListConsumer.flush();
171             }
172             public void consume(CommittedChangeList committedChangeList) {
173               if (myDisposed) {
174                 indicator.cancel();
175               }
176               ProgressManager.checkCanceled();
177               bufferedListConsumer.consumeOne(committedChangeList);
178             }
179           });
180         }
181         catch (final VcsException e) {
182           LOG.info(e);
183           WaitForProgressToShow.runOrInvokeLaterAboveProgress(new Runnable() {
184             public void run() {
185               Messages.showErrorDialog(myProject, "Error refreshing view: " + StringUtil.join(e.getMessages(), "\n"), "Committed Changes");
186             }
187           }, null, myProject);
188         } finally {
189           myInLoad = false;
190           myBrowser.setLoading(false);
191         }
192       }
193     });
194   }
195
196   public void clearCaches() {
197     final CommittedChangesCache cache = CommittedChangesCache.getInstance(myProject);
198     cache.clearCaches(new Runnable() {
199       @Override
200       public void run() {
201         ApplicationManager.getApplication().invokeLater(new Runnable() {
202           @Override
203           public void run() {
204             updateFilteredModel(Collections.<CommittedChangeList>emptyList(), true);
205           }
206         }, ModalityState.NON_MODAL, myProject.getDisposed());
207       }
208     });
209   }
210
211   private void refreshChangesFromCache(final boolean cacheOnly) {
212     final CommittedChangesCache cache = CommittedChangesCache.getInstance(myProject);
213     cache.hasCachesForAnyRoot(new Consumer<Boolean>() {
214       public void consume(final Boolean notEmpty) {
215         if (! notEmpty) {
216           if (cacheOnly) {
217             myBrowser.getEmptyText().setText(VcsBundle.message("committed.changes.not.loaded.message"));
218             return;
219           }
220           if (!CacheSettingsDialog.showSettingsDialog(myProject)) return;
221         }
222         cache.getProjectChangesAsync(mySettings, myMaxCount, cacheOnly,
223                                      new Consumer<List<CommittedChangeList>>() {
224                                        public void consume(final List<CommittedChangeList> committedChangeLists) {
225                                          updateFilteredModel(committedChangeLists, false);
226                                          }
227                                        },
228                                      new Consumer<List<VcsException>>() {
229                                        public void consume(final List<VcsException> vcsExceptions) {
230                                          AbstractVcsHelper.getInstance(myProject).showErrors(vcsExceptions, "Error refreshing VCS history");
231                                        }
232                                      });
233       }
234     });
235   }
236
237   private static class FilterHelper {
238     private final String[] myParts;
239
240     FilterHelper(final String filterString) {
241       myParts = filterString.split(" ");
242       for(int i = 0; i < myParts.length; ++ i) {
243         myParts [i] = myParts [i].toLowerCase();
244       }
245     }
246
247     public boolean filter(@NotNull final CommittedChangeList cl) {
248       return changeListMatches(cl, myParts);
249     }
250
251     private static boolean changeListMatches(@NotNull final CommittedChangeList changeList, final String[] filterWords) {
252       for(String word: filterWords) {
253         final String comment = changeList.getComment();
254         final String committer = changeList.getCommitterName();
255         if ((comment != null && comment.toLowerCase().contains(word)) ||
256             (committer != null && committer.toLowerCase().contains(word)) ||
257             Long.toString(changeList.getNumber()).contains(word)) {
258           return true;
259         }
260       }
261       return false;
262     }
263   }
264
265   private void updateFilteredModel(List<CommittedChangeList> committedChangeLists, final boolean reset) {
266     if (committedChangeLists == null) {
267       return;
268     }
269     final String emptyText;
270     if (reset) {
271       emptyText = VcsBundle.message("committed.changes.not.loaded.message");
272     } else {
273       emptyText = VcsBundle.message("committed.changes.empty.message");
274     }
275     myBrowser.getEmptyText().setText(emptyText);
276     myBrowser.setItems(committedChangeLists, CommittedChangesBrowserUseCase.COMMITTED);
277   }
278
279   public void setChangesFilter() {
280     CommittedChangesFilterDialog filterDialog = new CommittedChangesFilterDialog(myProject, myProvider.createFilterUI(true), mySettings);
281     filterDialog.show();
282     if (filterDialog.isOK()) {
283       mySettings = filterDialog.getSettings();
284       refreshChanges(false);
285     }
286   }
287
288   public void calcData(DataKey key, DataSink sink) {
289     if (key.equals(VcsDataKeys.REMOTE_HISTORY_CHANGED_LISTENER)) {
290       sink.put(VcsDataKeys.REMOTE_HISTORY_CHANGED_LISTENER, myIfNotCachedReloader);
291     } else if (VcsDataKeys.REMOTE_HISTORY_LOCATION.equals(key)) {
292       sink.put(VcsDataKeys.REMOTE_HISTORY_LOCATION, myLocation);
293     }
294     //if (key.equals(VcsDataKeys.CHANGES) || key.equals(VcsDataKeys.CHANGE_LISTS)) {
295       myBrowser.calcData(key, sink);
296     //}
297   }
298
299   public void dispose() {
300     for (Runnable runnable : myShouldBeCalledOnDispose) {
301       runnable.run();
302     }
303     myDisposed = true;
304   }
305
306   private class MyFilterComponent extends FilterComponent implements ChangeListFilteringStrategy {
307     private final List<ChangeListener> myList = ContainerUtil.createLockFreeCopyOnWriteList();
308
309     public MyFilterComponent() {
310       super("COMMITTED_CHANGES_FILTER_HISTORY", 20);
311     }
312
313     @Override
314     public CommittedChangesFilterKey getKey() {
315       return new CommittedChangesFilterKey("text", CommittedChangesFilterPriority.TEXT);
316     }
317
318     public void filter() {
319       for (ChangeListener changeListener : myList) {
320         changeListener.stateChanged(new ChangeEvent(this));
321       }
322     }
323     public JComponent getFilterUI() {
324       return null;
325     }
326     public void setFilterBase(List<CommittedChangeList> changeLists) {
327     }
328     public void addChangeListener(ChangeListener listener) {
329       myList.add(listener);
330     }
331     public void removeChangeListener(ChangeListener listener) {
332       myList.remove(listener);
333     }
334     public void resetFilterBase() {
335     }
336     public void appendFilterBase(List<CommittedChangeList> changeLists) {
337     }
338     @NotNull
339     public List<CommittedChangeList> filterChangeLists(List<CommittedChangeList> changeLists) {
340       final FilterHelper filterHelper = new FilterHelper(myFilterComponent.getFilter());
341       final List<CommittedChangeList> result = new ArrayList<CommittedChangeList>();
342       for (CommittedChangeList list : changeLists) {
343         if (filterHelper.filter(list)) {
344           result.add(list);
345         }
346       }
347       return result;
348     }
349   }
350
351   public void passCachedListsToListener(final VcsConfigurationChangeListener.DetailedNotification notification,
352                                         final Project project, final VirtualFile root) {
353     final LinkedList<CommittedChangeList> resultList = new LinkedList<CommittedChangeList>();
354     myBrowser.reportLoadedLists(new CommittedChangeListsListener() {
355       public void onBeforeStartReport() {
356       }
357       public boolean report(CommittedChangeList list) {
358         resultList.add(list);
359         return false;
360       }
361       public void onAfterEndReport() {
362         if (! resultList.isEmpty()) {
363           notification.execute(project, root, resultList);
364         }
365       }
366     });
367   }
368
369   public boolean isInLoad() {
370     return myInLoad;
371   }
372 }