shelf: rearrange actions, remove "Shelf Settings" action
[idea/community.git] / platform / vcs-impl / src / com / intellij / openapi / vcs / changes / shelf / ShelvedChangesViewManager.java
1 // Copyright 2000-2020 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 com.intellij.openapi.vcs.changes.shelf;
3
4 import com.intellij.diff.DiffContentFactoryEx;
5 import com.intellij.diff.chains.DiffRequestProducerException;
6 import com.intellij.diff.impl.CacheDiffRequestProcessor;
7 import com.intellij.diff.requests.DiffRequest;
8 import com.intellij.diff.requests.SimpleDiffRequest;
9 import com.intellij.icons.AllIcons;
10 import com.intellij.ide.DataManager;
11 import com.intellij.ide.DeleteProvider;
12 import com.intellij.ide.IdeBundle;
13 import com.intellij.ide.actions.EditSourceAction;
14 import com.intellij.ide.dnd.*;
15 import com.intellij.ide.dnd.aware.DnDAwareTree;
16 import com.intellij.notification.Notification;
17 import com.intellij.notification.NotificationAction;
18 import com.intellij.openapi.Disposable;
19 import com.intellij.openapi.ListSelection;
20 import com.intellij.openapi.actionSystem.*;
21 import com.intellij.openapi.application.ApplicationManager;
22 import com.intellij.openapi.application.ModalityState;
23 import com.intellij.openapi.diagnostic.Logger;
24 import com.intellij.openapi.fileEditor.FileDocumentManager;
25 import com.intellij.openapi.fileTypes.FileType;
26 import com.intellij.openapi.fileTypes.FileTypeManager;
27 import com.intellij.openapi.progress.ProcessCanceledException;
28 import com.intellij.openapi.progress.ProgressIndicator;
29 import com.intellij.openapi.progress.util.BackgroundTaskUtil;
30 import com.intellij.openapi.project.Project;
31 import com.intellij.openapi.startup.StartupActivity;
32 import com.intellij.openapi.util.Disposer;
33 import com.intellij.openapi.util.io.FileUtil;
34 import com.intellij.openapi.util.registry.Registry;
35 import com.intellij.openapi.util.registry.RegistryValue;
36 import com.intellij.openapi.util.registry.RegistryValueListener;
37 import com.intellij.openapi.util.text.StringUtil;
38 import com.intellij.openapi.vcs.*;
39 import com.intellij.openapi.vcs.changes.*;
40 import com.intellij.openapi.vcs.changes.actions.ShowDiffPreviewAction;
41 import com.intellij.openapi.vcs.changes.patch.PatchFileType;
42 import com.intellij.openapi.vcs.changes.patch.tool.PatchDiffRequest;
43 import com.intellij.openapi.vcs.changes.ui.*;
44 import com.intellij.openapi.vfs.VirtualFile;
45 import com.intellij.openapi.wm.IdeFocusManager;
46 import com.intellij.openapi.wm.ToolWindow;
47 import com.intellij.pom.Navigatable;
48 import com.intellij.pom.NavigatableAdapter;
49 import com.intellij.ui.*;
50 import com.intellij.ui.content.Content;
51 import com.intellij.ui.content.impl.ContentImpl;
52 import com.intellij.util.Consumer;
53 import com.intellij.util.IconUtil;
54 import com.intellij.util.IconUtil.IconSizeWrapper;
55 import com.intellij.util.PathUtil;
56 import com.intellij.util.containers.UtilKt;
57 import com.intellij.util.text.DateFormatUtil;
58 import com.intellij.util.ui.GraphicsUtil;
59 import com.intellij.util.ui.tree.TreeUtil;
60 import com.intellij.util.ui.update.MergingUpdateQueue;
61 import com.intellij.util.ui.update.Update;
62 import com.intellij.vcsUtil.VcsUtil;
63 import one.util.streamex.StreamEx;
64 import org.jetbrains.annotations.CalledInAwt;
65 import org.jetbrains.annotations.NonNls;
66 import org.jetbrains.annotations.NotNull;
67 import org.jetbrains.annotations.Nullable;
68
69 import javax.swing.*;
70 import javax.swing.event.CellEditorListener;
71 import javax.swing.event.ChangeEvent;
72 import javax.swing.tree.DefaultMutableTreeNode;
73 import javax.swing.tree.DefaultTreeCellEditor;
74 import javax.swing.tree.TreeCellEditor;
75 import javax.swing.tree.TreePath;
76 import java.awt.*;
77 import java.awt.event.MouseEvent;
78 import java.io.IOException;
79 import java.util.List;
80 import java.util.*;
81 import java.util.function.Predicate;
82 import java.util.stream.Collectors;
83
84 import static com.intellij.icons.AllIcons.Vcs.Patch_applied;
85 import static com.intellij.openapi.vcs.changes.shelf.DiffShelvedChangesActionProvider.createAppliedTextPatch;
86 import static com.intellij.openapi.vcs.changes.ui.ChangesGroupingSupport.REPOSITORY_GROUPING;
87 import static com.intellij.openapi.vcs.changes.ui.ChangesViewContentManager.SHELF;
88 import static com.intellij.openapi.vcs.changes.ui.ChangesViewContentManager.getToolWindowFor;
89 import static com.intellij.openapi.vcs.changes.ui.ChangesViewContentManagerKt.isCommitToolWindow;
90 import static com.intellij.util.FontUtil.spaceAndThinSpace;
91 import static com.intellij.util.containers.ContainerUtil.*;
92 import static java.util.Comparator.comparing;
93 import static java.util.Objects.requireNonNull;
94
95 // open for Rider
96 public class ShelvedChangesViewManager implements Disposable {
97   private static final Logger LOG = Logger.getInstance(ShelvedChangesViewManager.class);
98   @NonNls static final String SHELF_CONTEXT_MENU = "Vcs.Shelf.ContextMenu";
99   private static final String SHELVE_PREVIEW_SPLITTER_PROPORTION = "ShelvedChangesViewManager.DETAILS_SPLITTER_PROPORTION"; //NON-NLS
100
101   private final ShelveChangesManager myShelveChangesManager;
102   private final Project myProject;
103   private ShelfToolWindowPanel myPanel = null;
104   private ContentImpl myContent = null;
105   private final MergingUpdateQueue myUpdateQueue;
106   private final List<Runnable> myPostUpdateEdtActivity = new ArrayList<>();
107
108   public static final DataKey<ChangesTree> SHELVED_CHANGES_TREE =
109     DataKey.create("ShelveChangesManager.ShelvedChangesTree");
110   public static final DataKey<List<ShelvedChangeList>> SHELVED_CHANGELIST_KEY =
111     DataKey.create("ShelveChangesManager.ShelvedChangeListData");
112   public static final DataKey<List<ShelvedChangeList>> SHELVED_RECYCLED_CHANGELIST_KEY =
113     DataKey.create("ShelveChangesManager.ShelvedRecycledChangeListData");
114   public static final DataKey<List<ShelvedChangeList>> SHELVED_DELETED_CHANGELIST_KEY =
115     DataKey.create("ShelveChangesManager.ShelvedDeletedChangeListData");
116   public static final DataKey<List<ShelvedChange>> SHELVED_CHANGE_KEY = DataKey.create("ShelveChangesManager.ShelvedChange");
117   public static final DataKey<List<ShelvedBinaryFile>> SHELVED_BINARY_FILE_KEY = DataKey.create("ShelveChangesManager.ShelvedBinaryFile");
118
119   public static ShelvedChangesViewManager getInstance(Project project) {
120     return project.getService(ShelvedChangesViewManager.class);
121   }
122
123   public ShelvedChangesViewManager(Project project) {
124     myProject = project;
125     myShelveChangesManager = ShelveChangesManager.getInstance(project);
126     myUpdateQueue = new MergingUpdateQueue("Update Shelf Content", 200, true, null, myProject, null, true);
127
128     project.getMessageBus().connect().subscribe(ShelveChangesManager.SHELF_TOPIC, e -> scheduleContentUpdate());
129   }
130
131   private void scheduleContentUpdate() {
132     myUpdateQueue.queue(new MyContentUpdater());
133   }
134
135   private void updateTreeIfShown(@NotNull Consumer<? super ShelfTree> treeConsumer) {
136     if (myContent == null) return;
137     treeConsumer.consume(myPanel.myTree);
138   }
139
140   @CalledInAwt
141   void updateViewContent() {
142     if (myShelveChangesManager.getAllLists().isEmpty()) {
143       if (myContent != null) {
144         removeContent(myContent);
145         VcsNotifier.getInstance(myProject).hideAllNotificationsByType(ShelfNotification.class);
146       }
147       myContent = null;
148     }
149     else {
150       if (myContent == null) {
151         myPanel = new ShelfToolWindowPanel(myProject);
152         myContent = new ContentImpl(myPanel.myRootPanel, VcsBundle.message("shelf.tab"), false);
153         myContent.setTabName(SHELF);
154         MyDnDTarget dnDTarget = new MyDnDTarget(myPanel.myProject, myContent);
155         myContent.putUserData(Content.TAB_DND_TARGET_KEY, dnDTarget);
156
157         myContent.setCloseable(false);
158         myContent.setDisposer(myPanel);
159         DnDSupport.createBuilder(myPanel.myTree)
160           .setImageProvider(myPanel::createDraggedImage)
161           .setBeanProvider(myPanel::createDragStartBean)
162           .setTargetChecker(dnDTarget)
163           .setDropHandler(dnDTarget)
164           .setDisposableParent(myContent)
165           .install();
166         addContent(myContent);
167       }
168       updateTreeIfShown(tree -> {
169         tree.rebuildTree();
170       });
171     }
172   }
173
174   protected void removeContent(Content content) {
175     ChangesViewContentI contentManager = ChangesViewContentManager.getInstance(myProject);
176     contentManager.removeContent(content);
177     contentManager.selectContent(ChangesViewContentManager.LOCAL_CHANGES);
178   }
179
180   protected void addContent(Content content) {
181     ChangesViewContentI contentManager = ChangesViewContentManager.getInstance(myProject);
182     contentManager.addContent(content);
183   }
184
185   protected void activateContent() {
186     ChangesViewContentI contentManager = ChangesViewContentManager.getInstance(myProject);
187     contentManager.setSelectedContent(myContent);
188
189     ToolWindow window = getToolWindowFor(myProject, SHELF);
190     if (window != null && !window.isVisible()) {
191       window.activate(null);
192     }
193   }
194
195   private static final class MyShelvedTreeModelBuilder extends TreeModelBuilder {
196     private MyShelvedTreeModelBuilder(Project project, @NotNull ChangesGroupingPolicyFactory grouping) {
197       super(project, grouping);
198     }
199
200     public void setShelvedLists(@NotNull List<ShelvedChangeList> shelvedLists) {
201       createShelvedListsWithChangesNode(shelvedLists, myRoot);
202     }
203
204     public void setDeletedShelvedLists(@NotNull List<ShelvedChangeList> shelvedLists) {
205       createShelvedListsWithChangesNode(shelvedLists, createTagNode(VcsBundle.message("shelve.recently.deleted.node")));
206     }
207
208     private void createShelvedListsWithChangesNode(@NotNull List<ShelvedChangeList> shelvedLists, @NotNull ChangesBrowserNode<?> parentNode) {
209       shelvedLists.forEach(changeList -> {
210         List<ShelvedWrapper> shelvedChanges = new ArrayList<>();
211         requireNonNull(changeList.getChanges()).stream().map(ShelvedWrapper::new).forEach(shelvedChanges::add);
212         changeList.getBinaryFiles().stream().map(ShelvedWrapper::new).forEach(shelvedChanges::add);
213
214         shelvedChanges.sort(comparing(s -> s.getChange(myProject), CHANGE_COMPARATOR));
215
216         ShelvedListNode shelvedListNode = new ShelvedListNode(changeList);
217         insertSubtreeRoot(shelvedListNode, parentNode);
218         for (ShelvedWrapper shelved : shelvedChanges) {
219           Change change = shelved.getChange(myProject);
220           FilePath filePath = ChangesUtil.getFilePath(change);
221           insertChangeNode(change, shelvedListNode, new ShelvedChangeNode(shelved, filePath, change.getOriginText(myProject)));
222         }
223       });
224     }
225   }
226
227   @CalledInAwt
228   private void updateTreeModel() {
229     updateTreeIfShown(tree -> tree.setPaintBusy(true));
230     BackgroundTaskUtil.executeOnPooledThread(myProject, () -> {
231       List<ShelvedChangeList> lists = myShelveChangesManager.getAllLists();
232       lists.forEach(l -> l.loadChangesIfNeeded(myProject));
233       List<ShelvedChangeList> sortedLists = sorted(lists, ChangelistComparator.getInstance());
234       ApplicationManager.getApplication().invokeLater(() -> {
235         updateViewContent();
236         updateTreeIfShown(tree -> {
237           tree.setLoadedLists(sortedLists);
238           tree.setPaintBusy(false);
239           tree.rebuildTree();
240         });
241         myPostUpdateEdtActivity.forEach(Runnable::run);
242         myPostUpdateEdtActivity.clear();
243       }, ModalityState.NON_MODAL, myProject.getDisposed());
244     });
245   }
246
247   @CalledInAwt
248   public void startEditing(@NotNull ShelvedChangeList shelvedChangeList) {
249     runAfterUpdate(() -> {
250       selectShelvedList(shelvedChangeList);
251       updateTreeIfShown(tree -> tree.startEditingAtPath(tree.getLeadSelectionPath()));
252     });
253   }
254
255   static class ChangelistComparator implements Comparator<ShelvedChangeList> {
256     private final static ChangelistComparator ourInstance = new ChangelistComparator();
257
258     public static ChangelistComparator getInstance() {
259       return ourInstance;
260     }
261
262     @Override
263     public int compare(ShelvedChangeList o1, ShelvedChangeList o2) {
264       return o2.DATE.compareTo(o1.DATE);
265     }
266   }
267
268   public void activateView(@Nullable final ShelvedChangeList list) {
269     runAfterUpdate(() -> {
270       if (myContent == null) return;
271
272       if (list != null) {
273         selectShelvedList(list);
274       }
275       activateContent();
276     });
277   }
278
279   private void runAfterUpdate(@NotNull Runnable postUpdateRunnable) {
280     GuiUtils.invokeLaterIfNeeded(() -> {
281       myUpdateQueue.cancelAllUpdates();
282       myPostUpdateEdtActivity.add(postUpdateRunnable);
283       updateTreeModel();
284     }, ModalityState.NON_MODAL, myProject.getDisposed());
285   }
286
287   @Override
288   public void dispose() {
289     myUpdateQueue.cancelAllUpdates();
290   }
291
292   public void closeEditorPreview() {
293     ApplicationManager.getApplication().assertIsDispatchThread();
294
295     if (myContent == null) {
296       return;
297     }
298
299     DiffPreview diffPreview = myPanel.myDiffPreview;
300     if (diffPreview instanceof EditorTabPreview) {
301       ((EditorTabPreview)diffPreview).closePreview();
302     }
303   }
304
305   public void openEditorPreview() {
306     ApplicationManager.getApplication().assertIsDispatchThread();
307
308     if (myContent == null) return;
309     myPanel.openEditorPreview();
310   }
311
312   public void updateOnVcsMappingsChanged() {
313     ApplicationManager.getApplication().invokeLater(() -> {
314       updateTreeIfShown(tree -> {
315         ChangesGroupingSupport treeGroupingSupport = tree.getGroupingSupport();
316         if (treeGroupingSupport.isAvailable(REPOSITORY_GROUPING) && treeGroupingSupport.get(REPOSITORY_GROUPING)) {
317           tree.rebuildTree();
318         }
319       });
320     }, myProject.getDisposed());
321   }
322
323   public void selectShelvedList(@NotNull ShelvedChangeList list) {
324     updateTreeIfShown(tree -> {
325       DefaultMutableTreeNode treeNode = TreeUtil.findNodeWithObject((DefaultMutableTreeNode)tree.getModel().getRoot(), list);
326       if (treeNode == null) {
327         LOG.warn(VcsBundle.message("shelve.changelist.not.found", list.DESCRIPTION));
328         return;
329       }
330       TreeUtil.selectNode(tree, treeNode);
331     });
332   }
333
334   private static final class ShelfTree extends ChangesTree {
335     private List<ShelvedChangeList> myLoadedLists = emptyList();
336     private final DeleteProvider myDeleteProvider = new MyShelveDeleteProvider(myProject, this);
337
338     private ShelfTree(@NotNull Project project) {
339       super(project, false, false, true);
340       setKeepTreeState(true);
341       setDoubleClickHandler(e -> showShelvedChangesDiff());
342       setEnterKeyHandler(e -> showShelvedChangesDiff());
343     }
344
345     public void setLoadedLists(@NotNull List<ShelvedChangeList> lists) {
346       myLoadedLists = new ArrayList<>(lists);
347     }
348
349     @Override
350     public boolean isPathEditable(TreePath path) {
351       return isEditable() && getSelectionCount() == 1 && path.getLastPathComponent() instanceof ShelvedListNode;
352     }
353
354     @NotNull
355     @Override
356     protected ChangesGroupingSupport installGroupingSupport() {
357       return new ChangesGroupingSupport(myProject, this, false);
358     }
359
360     @Override
361     public int getToggleClickCount() {
362       return 2;
363     }
364
365     private boolean showShelvedChangesDiff() {
366       if (!hasExactlySelectedChanges()) return false;
367       DiffShelvedChangesActionProvider.showShelvedChangesDiff(DataManager.getInstance().getDataContext(this));
368       return true;
369     }
370
371     private boolean hasExactlySelectedChanges() {
372       return !UtilKt.isEmpty(VcsTreeModelData.exactlySelected(this).userObjectsStream(ShelvedWrapper.class));
373     }
374
375     @Override
376     public void rebuildTree() {
377       boolean showRecycled = ShelveChangesManager.getInstance(myProject).isShowRecycled();
378       MyShelvedTreeModelBuilder modelBuilder = new MyShelvedTreeModelBuilder(myProject, getGrouping());
379       modelBuilder.setShelvedLists(filter(myLoadedLists, l -> !l.isDeleted() && (showRecycled || !l.isRecycled())));
380       modelBuilder.setDeletedShelvedLists(filter(myLoadedLists, ShelvedChangeList::isDeleted));
381       updateTreeModel(modelBuilder.build());
382     }
383
384     @Nullable
385     @Override
386     public Object getData(@NotNull @NonNls String dataId) {
387       if (SHELVED_CHANGES_TREE.is(dataId)) {
388         return this;
389       }
390       else if (SHELVED_CHANGELIST_KEY.is(dataId)) {
391         return new ArrayList<>(getSelectedLists(this, l -> !l.isRecycled() && !l.isDeleted()));
392       }
393       else if (SHELVED_RECYCLED_CHANGELIST_KEY.is(dataId)) {
394         return new ArrayList<>(getSelectedLists(this, l -> l.isRecycled() && !l.isDeleted()));
395       }
396       else if (SHELVED_DELETED_CHANGELIST_KEY.is(dataId)) {
397         return new ArrayList<>(getSelectedLists(this, l -> l.isDeleted()));
398       }
399       else if (SHELVED_CHANGE_KEY.is(dataId)) {
400         return StreamEx.of(VcsTreeModelData.selected(this).userObjectsStream(ShelvedWrapper.class)).map(s -> s.getShelvedChange())
401           .nonNull().toList();
402       }
403       else if (SHELVED_BINARY_FILE_KEY.is(dataId)) {
404         return StreamEx.of(VcsTreeModelData.selected(this).userObjectsStream(ShelvedWrapper.class)).map(s -> s.getBinaryFile())
405           .nonNull().toList();
406       }
407       else if (VcsDataKeys.HAVE_SELECTED_CHANGES.is(dataId)) {
408         return getSelectionCount() > 0;
409       }
410       else if (VcsDataKeys.CHANGES.is(dataId)) {
411         List<ShelvedWrapper> shelvedChanges = VcsTreeModelData.selected(this).userObjects(ShelvedWrapper.class);
412         if (!shelvedChanges.isEmpty()) {
413           return map2Array(shelvedChanges, Change.class, s -> s.getChange(myProject));
414         }
415       }
416       else if (PlatformDataKeys.DELETE_ELEMENT_PROVIDER.is(dataId)) {
417         return myDeleteProvider;
418       }
419       else if (CommonDataKeys.NAVIGATABLE_ARRAY.is(dataId)) {
420         List<ShelvedWrapper> shelvedChanges = VcsTreeModelData.selected(this).userObjects(ShelvedWrapper.class);
421         final ArrayDeque<Navigatable> navigatables = new ArrayDeque<>();
422         for (final ShelvedWrapper shelvedChange : shelvedChanges) {
423           if (shelvedChange.getBeforePath() != null && !FileStatus.ADDED.equals(shelvedChange.getFileStatus())) {
424             final NavigatableAdapter navigatable = new NavigatableAdapter() {
425               @Override
426               public void navigate(boolean requestFocus) {
427                 final VirtualFile vf = shelvedChange.getBeforeVFUnderProject(myProject);
428                 if (vf != null) {
429                   navigate(myProject, vf, true);
430                 }
431               }
432             };
433             navigatables.add(navigatable);
434           }
435         }
436         return navigatables.toArray(new Navigatable[0]);
437       }
438       return super.getData(dataId);
439     }
440   }
441
442   @NotNull
443   private static Set<ShelvedChangeList> getSelectedLists(@NotNull ChangesTree tree,
444                                                          @NotNull Predicate<? super ShelvedChangeList> condition) {
445     TreePath[] selectionPaths = tree.getSelectionPaths();
446     if (selectionPaths == null) return Collections.emptySet();
447     return StreamEx.of(selectionPaths)
448       .map(path -> TreeUtil.findObjectInPath(path, ShelvedChangeList.class))
449       .filter(Objects::nonNull)
450       .filter(condition)
451       .collect(Collectors.toSet());
452   }
453
454   @NotNull
455   static ListSelection<ShelvedWrapper> getSelectedChangesOrAll(@NotNull DataContext dataContext) {
456     ChangesTree tree = dataContext.getData(SHELVED_CHANGES_TREE);
457     if (tree == null) return ListSelection.createAt(Collections.emptyList(), 0);
458
459     ListSelection<ShelvedWrapper> wrappers = ListSelection.createAt(VcsTreeModelData.selected(tree).userObjects(ShelvedWrapper.class), 0);
460
461     if (wrappers.getList().size() == 1) {
462       // return all changes for selected changelist
463       ShelvedChangeList changeList = getFirstItem(getSelectedLists(tree, it -> true));
464       if (changeList != null) {
465         ChangesBrowserNode<?> changeListNode = (ChangesBrowserNode<?>)TreeUtil.findNodeWithObject(tree.getRoot(), changeList);
466         if (changeListNode != null) {
467           List<ShelvedWrapper> allWrappers = changeListNode.getAllObjectsUnder(ShelvedWrapper.class);
468           if (allWrappers.size() > 1) {
469             ShelvedWrapper toSelect = getFirstItem(wrappers.getList());
470             return ListSelection.create(allWrappers, toSelect);
471           }
472         }
473       }
474     }
475     return wrappers;
476   }
477
478   @NotNull
479   public static List<ShelvedChangeList> getShelvedLists(@NotNull final DataContext dataContext) {
480     List<ShelvedChangeList> shelvedChangeLists = new ArrayList<>();
481     shelvedChangeLists.addAll(notNullize(SHELVED_CHANGELIST_KEY.getData(dataContext)));
482     shelvedChangeLists.addAll(notNullize(SHELVED_RECYCLED_CHANGELIST_KEY.getData(dataContext)));
483     shelvedChangeLists.addAll(notNullize(SHELVED_DELETED_CHANGELIST_KEY.getData(dataContext)));
484     return shelvedChangeLists;
485   }
486
487   @NotNull
488   public static List<ShelvedChangeList> getExactlySelectedLists(@NotNull final DataContext dataContext) {
489     ChangesTree shelvedChangeTree = dataContext.getData(SHELVED_CHANGES_TREE);
490     if (shelvedChangeTree == null) return emptyList();
491     return StreamEx.of(VcsTreeModelData.exactlySelected(shelvedChangeTree).userObjectsStream(ShelvedChangeList.class)).toList();
492   }
493
494   @NotNull
495   public static List<ShelvedChange> getShelveChanges(@NotNull final DataContext dataContext) {
496     return notNullize(dataContext.getData(SHELVED_CHANGE_KEY));
497   }
498
499   @NotNull
500   public static List<ShelvedBinaryFile> getBinaryShelveChanges(@NotNull final DataContext dataContext) {
501     return notNullize(dataContext.getData(SHELVED_BINARY_FILE_KEY));
502   }
503
504   @NotNull
505   public static List<String> getSelectedShelvedChangeNames(@NotNull final DataContext dataContext) {
506     ChangesTree shelvedChangeTree = dataContext.getData(SHELVED_CHANGES_TREE);
507     if (shelvedChangeTree == null) return emptyList();
508     return StreamEx.of(VcsTreeModelData.selected(shelvedChangeTree).userObjectsStream(ShelvedWrapper.class))
509       .map(ShelvedWrapper::getPath).toList();
510   }
511
512   private static final class MyShelveDeleteProvider implements DeleteProvider {
513     @NotNull private final Project myProject;
514     @NotNull private final ShelfTree myTree;
515
516     private MyShelveDeleteProvider(@NotNull Project project, @NotNull ShelfTree tree) {
517       myProject = project;
518       myTree = tree;
519     }
520
521     @Override
522     public void deleteElement(@NotNull DataContext dataContext) {
523       List<ShelvedChangeList> shelvedListsToDelete = TreeUtil.collectSelectedObjectsOfType(myTree, ShelvedChangeList.class);
524
525       List<ShelvedChange> changesToDelete = getChangesNotInLists(shelvedListsToDelete, getShelveChanges(dataContext));
526       List<ShelvedBinaryFile> binariesToDelete = getBinariesNotInLists(shelvedListsToDelete, getBinaryShelveChanges(dataContext));
527
528       ShelveChangesManager manager = ShelveChangesManager.getInstance(myProject);
529       int fileListSize = binariesToDelete.size() + changesToDelete.size();
530       Map<ShelvedChangeList, Date> createdDeletedListsWithOriginalDates =
531         manager.deleteShelves(shelvedListsToDelete, getShelvedLists(dataContext), changesToDelete, binariesToDelete);
532       if (!createdDeletedListsWithOriginalDates.isEmpty()) {
533         showUndoDeleteNotification(shelvedListsToDelete, fileListSize, createdDeletedListsWithOriginalDates);
534       }
535     }
536
537     private void showUndoDeleteNotification(@NotNull List<ShelvedChangeList> shelvedListsToDelete,
538                                             int fileListSize,
539                                             @NotNull Map<ShelvedChangeList, Date> createdDeletedListsWithOriginalDate) {
540       String message = constructDeleteSuccessfullyMessage(fileListSize, shelvedListsToDelete.size(), getFirstItem(shelvedListsToDelete));
541       Notification shelfDeletionNotification = new ShelfDeleteNotification(message);
542       shelfDeletionNotification.addAction(new UndoShelfDeletionAction(myProject, createdDeletedListsWithOriginalDate));
543       shelfDeletionNotification.addAction(ActionManager.getInstance().getAction("ShelvedChanges.ShowRecentlyDeleted"));
544       VcsNotifier.getInstance(myProject).showNotificationAndHideExisting(shelfDeletionNotification, ShelfDeleteNotification.class);
545     }
546
547     private static final class UndoShelfDeletionAction extends NotificationAction {
548       @NotNull private final Project myProject;
549       @NotNull private final Map<ShelvedChangeList, Date> myListDateMap;
550
551       private UndoShelfDeletionAction(@NotNull Project project, @NotNull Map<ShelvedChangeList, Date> listDateMap) {
552         super(IdeBundle.messagePointer("undo.dialog.title"));
553         myProject = project;
554         myListDateMap = listDateMap;
555       }
556
557       @Override
558       public void actionPerformed(@NotNull AnActionEvent e, @NotNull Notification notification) {
559         ShelveChangesManager manager = ShelveChangesManager.getInstance(myProject);
560         List<ShelvedChangeList> cantRestoreList = findAll(myListDateMap.keySet(), l -> !manager.getDeletedLists().contains(l));
561         myListDateMap.forEach((l, d) -> manager.restoreList(l, d));
562         notification.expire();
563         if (!cantRestoreList.isEmpty()) {
564           VcsNotifier.getInstance(myProject).notifyMinorWarning(VcsBundle.message("shelve.undo.deletion"),
565                                                                 VcsBundle.message("shelve.changes.restore.error", cantRestoreList.size()));
566         }
567       }
568     }
569
570     private static List<ShelvedBinaryFile> getBinariesNotInLists(@NotNull List<ShelvedChangeList> listsToDelete,
571                                                                  @NotNull List<ShelvedBinaryFile> binaryFiles) {
572       List<ShelvedBinaryFile> result = new ArrayList<>(binaryFiles);
573       for (ShelvedChangeList list : listsToDelete) {
574         result.removeAll(list.getBinaryFiles());
575       }
576       return result;
577     }
578
579     @NotNull
580     private static List<ShelvedChange> getChangesNotInLists(@NotNull List<ShelvedChangeList> listsToDelete,
581                                                             @NotNull List<ShelvedChange> shelvedChanges) {
582       List<ShelvedChange> result = new ArrayList<>(shelvedChanges);
583       // all changes should be loaded because action performed from loaded shelf tab
584       listsToDelete.stream().map(list -> requireNonNull(list.getChanges())).forEach(result::removeAll);
585       return result;
586     }
587
588     @NotNull
589     private static String constructDeleteSuccessfullyMessage(int fileNum, int listNum, @Nullable ShelvedChangeList first) {
590       String filesMessage = fileNum != 0 ? VcsBundle.message("shelve.delete.files.successful.message", fileNum) : "";
591       String changelistsMessage = listNum != 0 ? VcsBundle
592         .message("shelve.delete.changelists.message", listNum, listNum == 1 && first != null ? first.DESCRIPTION : "") : "";
593       return StringUtil.capitalize(
594         VcsBundle.message("shelve.delete.successful.message", filesMessage, fileNum > 0 && listNum > 0 ? 1 : 0, changelistsMessage));
595     }
596
597     @Override
598     public boolean canDeleteElement(@NotNull DataContext dataContext) {
599       return !getShelvedLists(dataContext).isEmpty();
600     }
601   }
602
603   private static final class MyDnDTarget extends VcsToolwindowDnDTarget {
604     private MyDnDTarget(@NotNull Project project, @NotNull Content content) {
605       super(project, content);
606     }
607
608     @Override
609     public void drop(DnDEvent event) {
610       super.drop(event);
611       Object attachedObject = event.getAttachedObject();
612       if (attachedObject instanceof ChangeListDragBean) {
613         FileDocumentManager.getInstance().saveAllDocuments();
614         List<Change> changes = Arrays.asList(((ChangeListDragBean)attachedObject).getChanges());
615         ShelveChangesManager.getInstance(myProject).shelveSilentlyUnderProgress(changes);
616       }
617     }
618
619     @Override
620     public boolean isDropPossible(@NotNull DnDEvent event) {
621       Object attachedObject = event.getAttachedObject();
622       return attachedObject instanceof ChangeListDragBean && ((ChangeListDragBean)attachedObject).getChanges().length > 0;
623     }
624   }
625
626   private static final class ShelfToolWindowPanel implements ChangesViewContentManagerListener, Disposable {
627     @NotNull private static final RegistryValue isEditorDiffPreview = Registry.get("show.diff.preview.as.editor.tab");
628     @NotNull private static final RegistryValue isOpenEditorDiffPreviewWithSingleClick =
629       Registry.get("show.diff.preview.as.editor.tab.with.single.click");
630
631     private final Project myProject;
632     private final ShelveChangesManager myShelveChangesManager;
633     private final VcsConfiguration myVcsConfiguration;
634
635     @NotNull private final JScrollPane myTreeScrollPane;
636     private final ShelfTree myTree;
637     private final ActionToolbar myToolbar;
638     @NotNull private final JPanel myRootPanel = new JPanel(new BorderLayout());
639
640     private MyShelvedPreviewProcessor myChangeProcessor;
641     private DiffPreview myDiffPreview;
642
643     private ShelfToolWindowPanel(@NotNull Project project) {
644       myProject = project;
645       myShelveChangesManager = ShelveChangesManager.getInstance(myProject);
646       myVcsConfiguration = VcsConfiguration.getInstance(myProject);
647
648       myTree = new ShelfTree(myProject);
649       myTree.setEditable(true);
650       myTree.setDragEnabled(!ApplicationManager.getApplication().isHeadlessEnvironment());
651       myTree.getGroupingSupport().setGroupingKeysOrSkip(myShelveChangesManager.getGrouping());
652       myTree.addGroupingChangeListener(e -> {
653         myShelveChangesManager.setGrouping(myTree.getGroupingSupport().getGroupingKeys());
654         myTree.rebuildTree();
655       });
656       DefaultTreeCellEditor treeCellEditor = new DefaultTreeCellEditor(myTree, null) {
657         @Override
658         public boolean isCellEditable(EventObject event) {
659           return !(event instanceof MouseEvent) && super.isCellEditable(event);
660         }
661       };
662       myTree.setCellEditor(treeCellEditor);
663       treeCellEditor.addCellEditorListener(new CellEditorListener() {
664         @Override
665         public void editingStopped(ChangeEvent e) {
666           DefaultMutableTreeNode node = (DefaultMutableTreeNode)myTree.getLastSelectedPathComponent();
667           if (node instanceof ShelvedListNode && e.getSource() instanceof TreeCellEditor) {
668             String editorValue = ((TreeCellEditor)e.getSource()).getCellEditorValue().toString();
669             ShelvedChangeList shelvedChangeList = ((ShelvedListNode)node).getList();
670             myShelveChangesManager.renameChangeList(shelvedChangeList, editorValue);
671           }
672         }
673
674         @Override
675         public void editingCanceled(ChangeEvent e) {
676         }
677       });
678
679       final AnAction showDiffAction = ActionManager.getInstance().getAction(IdeActions.ACTION_SHOW_DIFF_COMMON);
680       showDiffAction.registerCustomShortcutSet(showDiffAction.getShortcutSet(), myTree);
681       final EditSourceAction editSourceAction = new EditSourceAction();
682       editSourceAction.registerCustomShortcutSet(editSourceAction.getShortcutSet(), myTree);
683
684       DefaultActionGroup actionGroup = new DefaultActionGroup();
685       actionGroup.addAll((ActionGroup)ActionManager.getInstance().getAction("ShelvedChangesToolbar"));
686       actionGroup.add(Separator.getInstance());
687       actionGroup.add(new MyToggleDetailsAction());
688
689       myToolbar = ActionManager.getInstance().createActionToolbar("ShelvedChanges", actionGroup, false);
690       myTreeScrollPane = ScrollPaneFactory.createScrollPane(myTree, SideBorder.LEFT);
691       myRootPanel.add(myTreeScrollPane, BorderLayout.CENTER);
692       addToolbar(isCommitToolWindow(myProject));
693       setDiffPreview();
694       isEditorDiffPreview.addListener(new RegistryValueListener() {
695         @Override
696         public void afterValueChanged(@NotNull RegistryValue value) {
697           setDiffPreview();
698         }
699       }, this);
700       isOpenEditorDiffPreviewWithSingleClick.addListener(new RegistryValueListener() {
701         @Override
702         public void afterValueChanged(@NotNull RegistryValue value) {
703           if (!isSplitterPreview()) setDiffPreview(true);
704         }
705       }, this);
706       myProject.getMessageBus().connect(this).subscribe(ChangesViewContentManagerListener.TOPIC, this);
707
708       DataManager.registerDataProvider(myRootPanel, myTree);
709
710       PopupHandler.installPopupHandler(myTree, "ShelvedChangesPopupMenu", SHELF_CONTEXT_MENU);
711     }
712
713     @Override
714     public void dispose() {
715     }
716
717     @Override
718     public void toolWindowMappingChanged() {
719       addToolbar(isCommitToolWindow(myProject));
720       setDiffPreview();
721     }
722
723     private void addToolbar(boolean isHorizontal) {
724       if (isHorizontal) {
725         myToolbar.setOrientation(SwingConstants.HORIZONTAL);
726         myRootPanel.add(myToolbar.getComponent(), BorderLayout.NORTH);
727       }
728       else {
729         myToolbar.setOrientation(SwingConstants.VERTICAL);
730         myRootPanel.add(myToolbar.getComponent(), BorderLayout.WEST);
731       }
732     }
733
734     private void setDiffPreview() {
735       setDiffPreview(false);
736     }
737
738     private void setDiffPreview(boolean force) {
739       boolean isEditorPreview = isCommitToolWindow(myProject) || isEditorDiffPreview.asBoolean();
740       if (!force) {
741         if (isEditorPreview && myDiffPreview instanceof EditorTabPreview) return;
742         if (!isEditorPreview && isSplitterPreview()) return;
743       }
744
745       if (myChangeProcessor != null) Disposer.dispose(myChangeProcessor);
746
747       myChangeProcessor = new MyShelvedPreviewProcessor(myProject, myTree);
748       Disposer.register(this, myChangeProcessor);
749
750       myDiffPreview = isEditorPreview ? installEditorPreview(myChangeProcessor) : installSplitterPreview(myChangeProcessor);
751     }
752
753     @NotNull
754     private EditorTabPreview installEditorPreview(@NotNull MyShelvedPreviewProcessor changeProcessor) {
755       EditorTabPreview editorPreview = new EditorTabPreview(changeProcessor) {
756         @Override
757         protected String getCurrentName() {
758           ShelvedWrapper myCurrentShelvedElement = changeProcessor.myCurrentShelvedElement;
759           return myCurrentShelvedElement != null ? myCurrentShelvedElement.getRequestName() : VcsBundle.message("shelved.version.name");
760         }
761
762         @Override
763         protected boolean hasContent() {
764           return changeProcessor.myCurrentShelvedElement != null;
765         }
766
767         @Override
768         protected boolean skipPreviewUpdate() {
769           if (super.skipPreviewUpdate()) return true;
770           if (!myTree.equals(IdeFocusManager.getInstance(myProject).getFocusOwner())) return true;
771           if (!isEditorPreviewAllowed()) return true;
772
773           return false;
774         }
775       };
776       editorPreview.setEscapeHandler(() -> {
777         editorPreview.closePreview();
778
779         ToolWindow toolWindow = getToolWindowFor(myProject, SHELF);
780         if (toolWindow != null) toolWindow.activate(null);
781       });
782       if (isOpenEditorDiffPreviewWithSingleClick.asBoolean()) {
783         editorPreview.openWithSingleClick(myTree);
784       }
785       else {
786         editorPreview.openWithDoubleClick(myTree);
787       }
788       editorPreview.installNextDiffActionOn(myTreeScrollPane);
789
790       return editorPreview;
791     }
792
793     @NotNull
794     private PreviewDiffSplitterComponent installSplitterPreview(@NotNull MyShelvedPreviewProcessor changeProcessor) {
795       PreviewDiffSplitterComponent previewSplitter =
796         new PreviewDiffSplitterComponent(changeProcessor, SHELVE_PREVIEW_SPLITTER_PROPORTION);
797       previewSplitter.setFirstComponent(myTreeScrollPane);
798       previewSplitter.setPreviewVisible(myVcsConfiguration.SHELVE_DETAILS_PREVIEW_SHOWN);
799
800       myTree.addSelectionListener(() -> previewSplitter.updatePreview(false), changeProcessor);
801
802       myRootPanel.add(previewSplitter, BorderLayout.CENTER);
803       Disposer.register(changeProcessor, () -> {
804         myRootPanel.remove(previewSplitter);
805         myRootPanel.add(myTreeScrollPane, BorderLayout.CENTER);
806
807         myRootPanel.revalidate();
808         myRootPanel.repaint();
809       });
810
811       return previewSplitter;
812     }
813
814     private boolean isSplitterPreview() {
815       return myDiffPreview instanceof PreviewDiffSplitterComponent;
816     }
817
818     private boolean isEditorPreviewAllowed() {
819       return !isOpenEditorDiffPreviewWithSingleClick.asBoolean() || myVcsConfiguration.SHELVE_DETAILS_PREVIEW_SHOWN;
820     }
821
822     private void openEditorPreview() {
823       if (isSplitterPreview()) return;
824       if (!isEditorPreviewAllowed()) return;
825
826       ((EditorTabPreview)myDiffPreview).openPreview(false);
827     }
828
829     @Nullable
830     private DnDDragStartBean createDragStartBean(@NotNull DnDActionInfo info) {
831       if (info.isMove()) {
832         DataContext dc = DataManager.getInstance().getDataContext(myTree);
833         return new DnDDragStartBean(new ShelvedChangeListDragBean(getShelveChanges(dc), getBinaryShelveChanges(dc), getShelvedLists(dc)));
834       }
835       return null;
836     }
837
838     @NotNull
839     private DnDImage createDraggedImage(@NotNull DnDActionInfo info) {
840       String imageText = VcsBundle.message("unshelve.changes.action");
841       Image image = DnDAwareTree.getDragImage(myTree, imageText, null).getFirst();
842       return new DnDImage(image, new Point(-image.getWidth(null), -image.getHeight(null)));
843     }
844
845     private class MyToggleDetailsAction extends ShowDiffPreviewAction {
846       @Override
847       public void update(@NotNull AnActionEvent e) {
848         super.update(e);
849         e.getPresentation().setEnabledAndVisible(isSplitterPreview() || isOpenEditorDiffPreviewWithSingleClick.asBoolean());
850       }
851
852       @Override
853       public void setSelected(@NotNull AnActionEvent e, boolean state) {
854         myDiffPreview.setPreviewVisible(state);
855         myVcsConfiguration.SHELVE_DETAILS_PREVIEW_SHOWN = state;
856       }
857
858       @Override
859       public boolean isSelected(@NotNull AnActionEvent e) {
860         return myVcsConfiguration.SHELVE_DETAILS_PREVIEW_SHOWN;
861       }
862     }
863   }
864
865   private static class MyShelvedPreviewProcessor extends CacheDiffRequestProcessor<ShelvedWrapper> implements DiffPreviewUpdateProcessor {
866     @NotNull private final Project myProject;
867     @NotNull private final ShelfTree myTree;
868
869     @NotNull private final DiffShelvedChangesActionProvider.PatchesPreloader myPreloader;
870     @Nullable private ShelvedWrapper myCurrentShelvedElement;
871
872     MyShelvedPreviewProcessor(@NotNull Project project, @NotNull ShelfTree tree) {
873       super(project);
874       myProject = project;
875       myTree = tree;
876       myPreloader = new DiffShelvedChangesActionProvider.PatchesPreloader(project);
877     }
878
879     @NotNull
880     @Override
881     protected String getRequestName(@NotNull ShelvedWrapper provider) {
882       return provider.getRequestName();
883     }
884
885     @Override
886     protected ShelvedWrapper getCurrentRequestProvider() {
887       return myCurrentShelvedElement;
888     }
889
890     @CalledInAwt
891     @Override
892     public void clear() {
893       if (myCurrentShelvedElement != null) {
894         myCurrentShelvedElement = null;
895         updateRequest();
896       }
897       dropCaches();
898     }
899
900     @Override
901     @CalledInAwt
902     public void refresh(boolean fromModelRefresh) {
903       DataContext dc = DataManager.getInstance().getDataContext(myTree);
904       List<ShelvedChange> selectedChanges = getShelveChanges(dc);
905       List<ShelvedBinaryFile> selectedBinaryChanges = getBinaryShelveChanges(dc);
906
907       if (selectedChanges.isEmpty() && selectedBinaryChanges.isEmpty()) {
908         clear();
909         return;
910       }
911
912       if (myCurrentShelvedElement != null) {
913         if (keepBinarySelection(selectedBinaryChanges, myCurrentShelvedElement.getBinaryFile()) ||
914             keepShelvedSelection(selectedChanges, myCurrentShelvedElement.getShelvedChange())) {
915           dropCachesIfNeededAndUpdate(myCurrentShelvedElement);
916           return;
917         }
918       }
919       //getFirstSelected
920       myCurrentShelvedElement = !selectedChanges.isEmpty()
921                                 ? new ShelvedWrapper(selectedChanges.get(0))
922                                 : new ShelvedWrapper(selectedBinaryChanges.get(0));
923       dropCachesIfNeededAndUpdate(myCurrentShelvedElement);
924     }
925
926     private void dropCachesIfNeededAndUpdate(@NotNull ShelvedWrapper currentShelvedElement) {
927       ShelvedChange shelvedChange = currentShelvedElement.getShelvedChange();
928       boolean dropCaches = shelvedChange != null && myPreloader.isPatchFileChanged(shelvedChange.getPatchPath());
929       if (dropCaches) {
930         dropCaches();
931       }
932       updateRequest(dropCaches);
933     }
934
935     boolean keepShelvedSelection(@NotNull List<ShelvedChange> selectedChanges, @Nullable ShelvedChange currentShelvedChange) {
936       return currentShelvedChange != null && selectedChanges.contains(currentShelvedChange);
937     }
938
939     boolean keepBinarySelection(@NotNull List<ShelvedBinaryFile> selectedBinaryChanges, @Nullable ShelvedBinaryFile currentBinary) {
940       return currentBinary != null && selectedBinaryChanges.contains(currentBinary);
941     }
942
943     @NotNull
944     @Override
945     protected DiffRequest loadRequest(@NotNull ShelvedWrapper provider, @NotNull ProgressIndicator indicator)
946       throws ProcessCanceledException, DiffRequestProducerException {
947       try {
948         ShelvedChange shelvedChange = provider.getShelvedChange();
949         if (shelvedChange != null) {
950           return new PatchDiffRequest(createAppliedTextPatch(myPreloader.getPatch(shelvedChange, null)));
951         }
952
953         DiffContentFactoryEx factory = DiffContentFactoryEx.getInstanceEx();
954         ShelvedBinaryFile binaryFile = requireNonNull(provider.getBinaryFile());
955         if (binaryFile.AFTER_PATH == null) {
956           throw new DiffRequestProducerException("Content for '" + getRequestName(provider) + "' was removed");
957         }
958         //
959         byte[] binaryContent = binaryFile.createBinaryContentRevision(myProject).getBinaryContent();
960         FileType fileType = VcsUtil.getFilePath(binaryFile.SHELVED_PATH).getFileType();
961         return new SimpleDiffRequest(getRequestName(provider), factory.createEmpty(),
962                                      factory.createBinary(myProject, binaryContent, fileType, getRequestName(provider)), null, null);
963       }
964       catch (VcsException | IOException e) {
965         throw new DiffRequestProducerException("Can't show diff for '" + getRequestName(provider) + "'", e);
966       }
967     }
968   }
969
970   private static class ShelvedListNode extends ChangesBrowserNode<ShelvedChangeList> {
971     private static final Icon PatchIcon = PatchFileType.INSTANCE.getIcon();
972     private static final Icon AppliedPatchIcon =
973       new IconSizeWrapper(Patch_applied, Patch_applied.getIconWidth(), Patch_applied.getIconHeight()) {
974         @Override
975         public void paintIcon(Component c, Graphics g, int x, int y) {
976           GraphicsUtil.paintWithAlpha(g, 0.6f);
977           super.paintIcon(c, g, x, y);
978         }
979       };
980     private static final Icon DisabledToDeleteIcon = IconUtil.desaturate(AllIcons.Actions.GC);
981
982     @NotNull private final ShelvedChangeList myList;
983
984     ShelvedListNode(@NotNull ShelvedChangeList list) {
985       super(list);
986       myList = list;
987     }
988
989     @NotNull
990     public ShelvedChangeList getList() {
991       return myList;
992     }
993
994     @Override
995     public void render(@NotNull ChangesBrowserNodeRenderer renderer, boolean selected, boolean expanded, boolean hasFocus) {
996       if (myList.isRecycled() || myList.isDeleted()) {
997         renderer.appendTextWithIssueLinks(myList.DESCRIPTION, SimpleTextAttributes.GRAYED_BOLD_ATTRIBUTES);
998         renderer.setIcon(myList.isMarkedToDelete() || myList.isDeleted() ? DisabledToDeleteIcon : AppliedPatchIcon);
999       }
1000       else {
1001         renderer.appendTextWithIssueLinks(myList.DESCRIPTION, SimpleTextAttributes.REGULAR_ATTRIBUTES);
1002         renderer.setIcon(PatchIcon);
1003       }
1004       appendCount(renderer);
1005       String date = DateFormatUtil.formatPrettyDateTime(myList.DATE);
1006       renderer.append(", " + date, SimpleTextAttributes.GRAYED_ATTRIBUTES);
1007     }
1008   }
1009
1010   private static class ShelvedChangeNode extends ChangesBrowserNode<ShelvedWrapper> implements Comparable<ShelvedChangeNode> {
1011
1012     @NotNull private final ShelvedWrapper myShelvedChange;
1013     @NotNull private final FilePath myFilePath;
1014     @Nullable private final String myAdditionalText;
1015
1016     protected ShelvedChangeNode(@NotNull ShelvedWrapper shelvedChange,
1017                                 @NotNull FilePath filePath,
1018                                 @Nullable String additionalText) {
1019       super(shelvedChange);
1020       myShelvedChange = shelvedChange;
1021       myFilePath = filePath;
1022       myAdditionalText = additionalText;
1023     }
1024
1025     @Override
1026     public void render(@NotNull ChangesBrowserNodeRenderer renderer, boolean selected, boolean expanded, boolean hasFocus) {
1027       String path = myShelvedChange.getRequestName();
1028       String directory = StringUtil.defaultIfEmpty(PathUtil.getParentPath(path), VcsBundle.message("shelve.default.path.rendering"));
1029       String fileName = StringUtil.defaultIfEmpty(PathUtil.getFileName(path), path);
1030
1031       renderer.append(fileName, new SimpleTextAttributes(SimpleTextAttributes.STYLE_PLAIN, myShelvedChange.getFileStatus().getColor()));
1032       if (myAdditionalText != null) {
1033         renderer.append(spaceAndThinSpace() + myAdditionalText, SimpleTextAttributes.REGULAR_ATTRIBUTES);
1034       }
1035       if (renderer.isShowFlatten()) {
1036         renderer.append(spaceAndThinSpace() + FileUtil.toSystemDependentName(directory), SimpleTextAttributes.GRAYED_ATTRIBUTES);
1037       }
1038       renderer.setIcon(FileTypeManager.getInstance().getFileTypeByFileName(fileName).getIcon());
1039     }
1040
1041     @Override
1042     public String getTextPresentation() {
1043       return PathUtil.getFileName(myShelvedChange.getRequestName());
1044     }
1045
1046     @Override
1047     protected boolean isFile() {
1048       return true;
1049     }
1050
1051     @Override
1052     public int compareTo(@NotNull ShelvedChangeNode o) {
1053       return compareFilePaths(myFilePath, o.myFilePath);
1054     }
1055
1056     @Nullable
1057     @Override
1058     public Color getBackgroundColor(@NotNull Project project) {
1059       return getBackgroundColorFor(project, myFilePath);
1060     }
1061   }
1062
1063   private class MyContentUpdater extends Update {
1064     MyContentUpdater() {
1065       super("ShelfContentUpdate");
1066     }
1067
1068     @Override
1069     public void run() {
1070       updateTreeModel();
1071     }
1072
1073     @Override
1074     public boolean canEat(Update update) {
1075       return true;
1076     }
1077   }
1078
1079   public static class PostStartupActivity implements StartupActivity.Background {
1080     @Override
1081     public void runActivity(@NotNull Project project) {
1082       if (ApplicationManager.getApplication().isHeadlessEnvironment()) return;
1083
1084       getInstance(project).scheduleContentUpdate();
1085     }
1086   }
1087 }