From eb251f61f768b224918d1ffaac0796d82342e3bc Mon Sep 17 00:00:00 2001 From: irengrig Date: Tue, 5 Jul 2011 01:37:12 +0400 Subject: [PATCH] git log refactoring, common way to details loading. --- .../src/com/intellij/ui/CheckBoxList.java | 12 +- .../openapi/vcs/GenericDetailsLoader.java | 60 ++ .../intellij/openapi/vcs/ValueConsumer.java | 45 ++ .../src/com/intellij/vcsUtil/UIVcsUtil.java | 41 ++ .../vcs/changes/ChangesViewManager.java | 9 +- .../vcs/changes/VcsChangeDetailsManager.java | 18 +- plugins/git4idea/src/META-INF/plugin.xml | 2 + .../history/browser/LowLevelAccessImpl.java | 6 +- .../history/browser/SymbolicRefs.java | 6 + .../ContainedInBranchesConfigDialog.java | 192 ++++++ .../history/wholeTree/DetailsCache.java | 31 +- .../history/wholeTree/GitLogDetailsPanel.java | 289 ++++++++ .../history/wholeTree/GitLogSettings.java | 101 +++ .../git4idea/history/wholeTree/GitLogUI.java | 622 +++++++----------- .../wholeTree/GroupHeaderCommitDecorator.java | 20 + .../GroupHeaderDatePseudoCommit.java | 20 + .../wholeTree/HighlightingRendererBase.java | 72 ++ .../MultipleRepositoryCommitDecorator.java | 20 + .../wholeTree/WireNumberCommitDecoration.java | 20 + 19 files changed, 1181 insertions(+), 405 deletions(-) create mode 100644 platform/vcs-api/src/com/intellij/openapi/vcs/GenericDetailsLoader.java create mode 100644 platform/vcs-api/src/com/intellij/openapi/vcs/ValueConsumer.java create mode 100644 platform/vcs-api/src/com/intellij/vcsUtil/UIVcsUtil.java create mode 100644 plugins/git4idea/src/git4idea/history/wholeTree/ContainedInBranchesConfigDialog.java create mode 100644 plugins/git4idea/src/git4idea/history/wholeTree/GitLogDetailsPanel.java create mode 100644 plugins/git4idea/src/git4idea/history/wholeTree/GitLogSettings.java create mode 100644 plugins/git4idea/src/git4idea/history/wholeTree/HighlightingRendererBase.java diff --git a/platform/platform-api/src/com/intellij/ui/CheckBoxList.java b/platform/platform-api/src/com/intellij/ui/CheckBoxList.java index 837a963e26cd..dd171f42d83a 100644 --- a/platform/platform-api/src/com/intellij/ui/CheckBoxList.java +++ b/platform/platform-api/src/com/intellij/ui/CheckBoxList.java @@ -12,6 +12,7 @@ import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; +import java.util.Map; /** * @author oleg @@ -75,6 +76,12 @@ public class CheckBoxList extends JBList { }); } + public void setStringItems(final Map items) { + for (Map.Entry entry : items.entrySet()) { + ((DefaultListModel) getModel()).addElement(new JCheckBox(entry.getKey(), entry.getValue())); + } + } + public boolean isItemSelected(int index) { return ((JCheckBox)getModel().getElementAt(index)).isSelected(); } @@ -99,6 +106,9 @@ public class CheckBoxList extends JBList { this.checkBoxListListener = checkBoxListListener; } + protected void adjustRendering(final JCheckBox checkBox, final boolean selected, final boolean hasFocus) { + } + private class CellRenderer implements ListCellRenderer { private final Border mySelectedBorder; private final Border myBorder; @@ -119,8 +129,8 @@ public class CheckBoxList extends JBList { checkbox.setFont(getFont(checkbox)); checkbox.setFocusPainted(false); checkbox.setBorderPainted(true); - checkbox.setBorder(isSelected ? mySelectedBorder : myBorder); + adjustRendering(checkbox, isSelected, cellHasFocus); return checkbox; } } diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/GenericDetailsLoader.java b/platform/vcs-api/src/com/intellij/openapi/vcs/GenericDetailsLoader.java new file mode 100644 index 000000000000..838dc375406c --- /dev/null +++ b/platform/vcs-api/src/com/intellij/openapi/vcs/GenericDetailsLoader.java @@ -0,0 +1,60 @@ +/* + * Copyright 2000-2011 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.vcs; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.util.Comparing; +import com.intellij.util.Consumer; +import com.intellij.util.PairConsumer; +import org.jetbrains.annotations.Nullable; + +import java.util.concurrent.atomic.AtomicReference; + +/** + * @author irengrig + * Date: 6/29/11 + * Time: 11:38 PM + */ +public class GenericDetailsLoader implements PairConsumer { + private final Consumer myLoader; + private final ValueConsumer myValueConsumer; + private final AtomicReference myCurrentlySelected; + + public GenericDetailsLoader(final Consumer loader, final PairConsumer valueConsumer) { + myLoader = loader; + myValueConsumer = new ValueConsumer(valueConsumer); + myCurrentlySelected = new AtomicReference(null); + } + + public void updateSelection(@Nullable final Id id) { + ApplicationManager.getApplication().assertIsDispatchThread(); + myValueConsumer.setId(id); + + if (! Comparing.equal(id, myCurrentlySelected.getAndSet(id))) { + myLoader.consume(id); + } + } + + @Override + public void consume(Id id, Data data) { + ApplicationManager.getApplication().assertIsDispatchThread(); + myValueConsumer.consume(id, data); + } + + public Id getCurrentlySelected() { + return myCurrentlySelected.get(); + } +} diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/ValueConsumer.java b/platform/vcs-api/src/com/intellij/openapi/vcs/ValueConsumer.java new file mode 100644 index 000000000000..368cc53462d7 --- /dev/null +++ b/platform/vcs-api/src/com/intellij/openapi/vcs/ValueConsumer.java @@ -0,0 +1,45 @@ +/* + * Copyright 2000-2011 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.vcs; + +import com.intellij.util.PairConsumer; + +/** + * @author irengrig + * Date: 6/29/11 + * Time: 11:51 PM + */ +public class ValueConsumer { + private Id myId; + private Id mySetId; + private final PairConsumer myConsumer; + + protected ValueConsumer(PairConsumer consumer) { + myConsumer = consumer; + } + + public void consume(final Id id, final Data data) { + if (id.equals(mySetId)) return; // already set + if (! id.equals(myId)) return; + mySetId = id; + myConsumer.consume(id, data); + } + + public void setId(Id id) { + myId = id; + mySetId = null; + } +} diff --git a/platform/vcs-api/src/com/intellij/vcsUtil/UIVcsUtil.java b/platform/vcs-api/src/com/intellij/vcsUtil/UIVcsUtil.java new file mode 100644 index 000000000000..b4c63c922027 --- /dev/null +++ b/platform/vcs-api/src/com/intellij/vcsUtil/UIVcsUtil.java @@ -0,0 +1,41 @@ +/* + * Copyright 2000-2011 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.vcsUtil; + +import com.intellij.ui.SimpleTextAttributes; +import com.intellij.util.ui.UIUtil; + +import javax.swing.*; +import java.awt.*; + +/** + * @author irengrig + * Date: 7/1/11 + * Time: 2:49 PM + */ +public class UIVcsUtil { + private UIVcsUtil() { + } + + public static JPanel errorPanel(final String text, boolean isError) { + final JLabel label = new JLabel(text); + label.setForeground(isError ? SimpleTextAttributes.ERROR_ATTRIBUTES.getFgColor() : UIUtil.getInactiveTextColor()); + final JPanel wrapper = new JPanel(new GridBagLayout()); + wrapper.add(label, new GridBagConstraints(0,0,1,1,0,0,GridBagConstraints.CENTER, GridBagConstraints.NONE, + new Insets(1,1,1,1), 0,0)); + return wrapper; + } +} diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ChangesViewManager.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ChangesViewManager.java index 44638133f29d..6bd689dbdf25 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ChangesViewManager.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ChangesViewManager.java @@ -59,6 +59,7 @@ import com.intellij.util.containers.SLRUMap; import com.intellij.util.messages.MessageBusConnection; import com.intellij.util.ui.UIUtil; import com.intellij.util.ui.tree.TreeUtil; +import com.intellij.vcsUtil.UIVcsUtil; import org.jdom.Element; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; @@ -120,9 +121,9 @@ public class ChangesViewManager implements ChangesViewI, JDOMExternalizable, Pro myContentManager = contentManager; myVcsChangeDetailsManager = vcsChangeDetailsManager; myView = new ChangesListView(project); - myNoDetailsPanel = VcsChangeDetailsManager.errorPanel("No details available", false); - myNothingSelected = VcsChangeDetailsManager.errorPanel("Nothing selected", false); - myNotLoadedYet = VcsChangeDetailsManager.errorPanel("Changes content is not loaded yet", false); + myNoDetailsPanel = UIVcsUtil.errorPanel("No details available", false); + myNothingSelected = UIVcsUtil.errorPanel("Nothing selected", false); + myNotLoadedYet = UIVcsUtil.errorPanel("Changes content is not loaded yet", false); Disposer.register(project, myView); myRepaintAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD, project); @@ -263,7 +264,7 @@ public class ChangesViewManager implements ChangesViewI, JDOMExternalizable, Pro } else { final String freezed = ChangeListManager.getInstance(myProject).isFreezed(); if (freezed != null) { - setChangeDetailsPanel(VcsChangeDetailsManager.errorPanel(freezed, false)); + setChangeDetailsPanel(UIVcsUtil.errorPanel(freezed, false)); return; } Pair details = null; diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/VcsChangeDetailsManager.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/VcsChangeDetailsManager.java index 15631c851bcb..48f683863833 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/VcsChangeDetailsManager.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/VcsChangeDetailsManager.java @@ -27,12 +27,11 @@ import com.intellij.openapi.util.Pair; import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vcs.VcsKey; import com.intellij.openapi.vcs.changes.actions.DiffRequestFromChange; -import com.intellij.ui.SimpleTextAttributes; import com.intellij.ui.components.JBScrollPane; import com.intellij.util.BeforeAfter; import com.intellij.util.containers.Convertor; import com.intellij.util.containers.HashMap; -import com.intellij.util.ui.UIUtil; +import com.intellij.vcsUtil.UIVcsUtil; import org.jetbrains.annotations.Nullable; import javax.swing.*; @@ -104,7 +103,7 @@ public class VcsChangeDetailsManager { contents = myRequestFromChange.createRequestForChange(o, 0); } catch (VcsException e) { - return new Pair(errorPanel(e.getMessage(), true), null); + return new Pair(UIVcsUtil.errorPanel(e.getMessage(), true), null); } if (contents == null || contents.isEmpty()) return null; assert contents.size() == 1; @@ -155,11 +154,11 @@ public class VcsChangeDetailsManager { if (requestForChange == null) return null; if (requestForChange.isEmpty()) { return new Pair( - errorPanel(DiffBundle.message("diff.contents.have.differences.only.in.line.separators.message.text"), false), null); + UIVcsUtil.errorPanel(DiffBundle.message("diff.contents.have.differences.only.in.line.separators.message.text"), false), null); } } catch (VcsException e) { - return new Pair(errorPanel(e.getMessage(), true), null); + return new Pair(UIVcsUtil.errorPanel(e.getMessage(), true), null); } final ChangesFragmentedDiffPanel panel = @@ -175,13 +174,4 @@ public class VcsChangeDetailsManager { o.getBeforeRevision() == null ? "New" : o.getBeforeRevision().getRevisionNumber().asString()).append(")").toString(); } - - public static JPanel errorPanel(final String text, boolean isError) { - final JLabel label = new JLabel(text); - label.setForeground(isError ? SimpleTextAttributes.ERROR_ATTRIBUTES.getFgColor() : UIUtil.getInactiveTextColor()); - final JPanel wrapper = new JPanel(new GridBagLayout()); - wrapper.add(label, new GridBagConstraints(0,0,1,1,0,0,GridBagConstraints.CENTER, GridBagConstraints.NONE, - new Insets(1,1,1,1), 0,0)); - return wrapper; - } } diff --git a/plugins/git4idea/src/META-INF/plugin.xml b/plugins/git4idea/src/META-INF/plugin.xml index 0caef5759a5a..22f7f1f54447 100644 --- a/plugins/git4idea/src/META-INF/plugin.xml +++ b/plugins/git4idea/src/META-INF/plugin.xml @@ -112,6 +112,8 @@ + getBranchesWithCommit(final SHAHash hash) throws VcsException { + return getBranchesWithCommit(hash.getValue()); + } + + public List getBranchesWithCommit(final String hash) throws VcsException { final List result = new ArrayList(); - GitBranch.listAsStrings(myProject, myRoot, true, true, result, hash.getValue()); + GitBranch.listAsStrings(myProject, myRoot, true, true, result, hash); //GitBranch.listAsStrings(myProject, myRoot, true, false, result, hash.getValue()); return result; } diff --git a/plugins/git4idea/src/git4idea/history/browser/SymbolicRefs.java b/plugins/git4idea/src/git4idea/history/browser/SymbolicRefs.java index cbef146be03f..7d0c32b092f5 100644 --- a/plugins/git4idea/src/git4idea/history/browser/SymbolicRefs.java +++ b/plugins/git4idea/src/git4idea/history/browser/SymbolicRefs.java @@ -13,6 +13,7 @@ package git4idea.history.browser; import git4idea.GitBranch; +import org.jetbrains.annotations.Nullable; import java.util.Collection; import java.util.TreeSet; @@ -66,6 +67,11 @@ public class SymbolicRefs { return myTags; } + @Nullable + public String getCurrentName() { + return myCurrent == null ? null : myCurrent.getName(); + } + public GitBranch getCurrent() { return myCurrent; } diff --git a/plugins/git4idea/src/git4idea/history/wholeTree/ContainedInBranchesConfigDialog.java b/plugins/git4idea/src/git4idea/history/wholeTree/ContainedInBranchesConfigDialog.java new file mode 100644 index 000000000000..c00c9d582408 --- /dev/null +++ b/plugins/git4idea/src/git4idea/history/wholeTree/ContainedInBranchesConfigDialog.java @@ -0,0 +1,192 @@ +/* + * Copyright 2000-2011 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package git4idea.history.wholeTree; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.util.Disposer; +import com.intellij.ui.CheckBoxList; +import com.intellij.ui.ListSpeedSearch; +import com.intellij.ui.ScrollPaneFactory; + +import javax.swing.*; +import javax.swing.border.Border; +import javax.swing.border.EmptyBorder; +import java.awt.*; +import java.awt.event.FocusAdapter; +import java.awt.event.FocusEvent; +import java.util.*; + +/** + * @author irengrig + * Date: 6/30/11 + * Time: 8:06 PM + */ +public class ContainedInBranchesConfigDialog extends DialogWrapper { + private JPanel myPanel; + private CheckBoxList myLocalBranches; + private CheckBoxList myRemoteBranches; + private final Project myProject; + private boolean myChanged; + + public ContainedInBranchesConfigDialog(final Project project, final Collection localBranches, + Collection remoteBranches, final String currentLocal, final String currentRemote) { + super(project, true); + myProject = project; + setTitle("Configure branches to check reachability for"); + initUi(project, localBranches, remoteBranches, currentLocal, currentRemote); + Disposer.register(myProject, getDisposable()); + init(); + } + + @Override + protected String getDimensionServiceKey() { + return "git4idea.history.wholeTree.ContainedInBranchesConfigDialog"; + } + + private void initUi(Project project, Collection localBranches, Collection remoteBranches, final String currentLocal, + final String currentRemote) { + myPanel = new JPanel(new GridBagLayout()); + final GridBagConstraints gb = + new GridBagConstraints(0, 0, 1, 1, 0, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(1, 1, 1, 1), 0, 0); + myLocalBranches = new MyCheckBoxList(currentLocal); + myRemoteBranches = new MyCheckBoxList(currentRemote); + + gb.gridwidth = 2; + myPanel.add(new JLabel("Selected branches that will be used to check whether commit is reachable from (contained in)."), gb); + + gb.insets.top = 10; + gb.gridwidth = 1; + ++ gb.gridy; + myPanel.add(new JLabel("Local branches"), gb); + ++ gb.gridx; + myPanel.add(new JLabel("Remote branches"), gb); + + gb.insets.top = 0; + ++ gb.gridy; + gb.gridx = 0; + gb.fill = GridBagConstraints.BOTH; + gb.weightx = 0.5; + gb.weighty = 1; + myLocalBranches.setBorder(null); + myRemoteBranches.setBorder(null); + myPanel.add(ScrollPaneFactory.createScrollPane(myLocalBranches), gb); + ++ gb.gridx; + myPanel.add(ScrollPaneFactory.createScrollPane(myRemoteBranches), gb); + + final GitLogSettings gitLogSettings = GitLogSettings.getInstance(project); + setItems(localBranches, gitLogSettings.getLocalBranchesCopy(), myLocalBranches); + setItems(remoteBranches, gitLogSettings.getRemoteBranchesCopy(), myRemoteBranches); + + new ListSpeedSearch(myLocalBranches); + new ListSpeedSearch(myRemoteBranches); + + focus(myLocalBranches); + focus(myRemoteBranches); + } + + private void focus(final CheckBoxList list) { + list.addFocusListener(new FocusAdapter() { + @Override + public void focusGained(FocusEvent e) { + if (list.getItemsCount() > 0 && list.getSelectedIndex() == -1) { + list.setSelectedIndex(0); + //list.removeSelectionInterval(0, 0); + list.removeFocusListener(this); + } + } + }); + } + + public boolean isChanged() { + return myChanged; + } + + private static void setItems(Collection localBranches, Set localBranchesCopy, final CheckBoxList list) { + // order + final Map localBranchesState = new TreeMap(); + for (String localBranch : localBranches) { + localBranchesState.put(localBranch, localBranchesCopy.contains(localBranch)); + } + list.setStringItems(localBranchesState); + } + + @Override + public JComponent getPreferredFocusedComponent() { + return myLocalBranches; + } + + @Override + public Dimension getPreferredSize() { + return new Dimension(300, 400); + } + + @Override + protected JComponent createCenterPanel() { + return myPanel; + } + + @Override + protected void doOKAction() { + final GitLogSettings gitLogSettings = GitLogSettings.getInstance(myProject); + final ArrayList local = gatherSelected((DefaultListModel)myLocalBranches.getModel()); + final ArrayList remote = gatherSelected((DefaultListModel)myRemoteBranches.getModel()); + if (gitLogSettings.setIfChanged(local, remote)) { + myChanged = true; + } + super.doOKAction(); + } + + private ArrayList gatherSelected(DefaultListModel localModel) { + final ArrayList selected = new ArrayList(); + final Enumeration elements = localModel.elements(); + while (elements.hasMoreElements()) { + final JCheckBox cb = (JCheckBox)elements.nextElement(); + if (cb.isSelected()) { + selected.add(cb.getText()); + } + } + return selected; + } + + private static class MyCheckBoxList extends CheckBoxList { + private final String myBold; + private final static Border FOCUSED_BORDER = UIManager.getBorder("List.focusCellHighlightBorder"); + private final EmptyBorder myEmptyBorder; + + private MyCheckBoxList(String bold) { + myBold = bold; + final Insets borderInsets = FOCUSED_BORDER.getBorderInsets(new JCheckBox()); + myEmptyBorder = new EmptyBorder(borderInsets); + setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); + } + + @Override + protected void adjustRendering(JCheckBox checkBox, final boolean selected, final boolean hasFocus) { + checkBox.setFocusPainted(false); + if (myBold != null && myBold.equals(checkBox.getText())) { + checkBox.setFont(checkBox.getFont().deriveFont(Font.BOLD)); + } else { + checkBox.setFont(checkBox.getFont().deriveFont(Font.PLAIN)); + } + if (hasFocus) { + checkBox.setBorder(FOCUSED_BORDER); + } else { + checkBox.setBorder(myEmptyBorder); + } + } + } +} diff --git a/plugins/git4idea/src/git4idea/history/wholeTree/DetailsCache.java b/plugins/git4idea/src/git4idea/history/wholeTree/DetailsCache.java index eb4f52f0a608..a0a33d775df7 100644 --- a/plugins/git4idea/src/git4idea/history/wholeTree/DetailsCache.java +++ b/plugins/git4idea/src/git4idea/history/wholeTree/DetailsCache.java @@ -21,14 +21,13 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Pair; import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vcs.changes.BackgroundFromStartOption; -import com.intellij.openapi.vcs.changes.committed.AbstractCalledLater; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.Consumer; +import com.intellij.util.Processor; import com.intellij.util.containers.MultiMap; import com.intellij.util.containers.SLRUMap; import git4idea.history.browser.GitCommit; import git4idea.history.browser.LowLevelAccessImpl; -import git4idea.history.browser.SHAHash; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -49,7 +48,7 @@ public class DetailsCache { private final DetailsLoaderImpl myDetailsLoader; private final ModalityState myModalityState; private final BackgroundTaskQueue myQueue; - private AbstractCalledLater myRefresh; + private UIRefresh myRefresh; private final Map> myStash; private final Object myLock; private final static Logger LOG = Logger.getInstance("git4idea.history.wholeTree.DetailsCache"); @@ -64,12 +63,7 @@ public class DetailsCache { myModalityState = modalityState; myQueue = queue; myStash = new HashMap>(); - myRefresh = new AbstractCalledLater(project, myModalityState) { - @Override - public void run() { - uiRefresh.detailsLoaded(); - } - }; + myRefresh = uiRefresh; myLock = new Object(); myCache = new SLRUMap, GitCommit>(ourSize, 50); myBranches = new SLRUMap, List>(10, 10); @@ -92,7 +86,12 @@ public class DetailsCache { myCache.put(new Pair(root, commit.getShortHash()), commit); } } - myRefresh.callMe(); + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + myRefresh.detailsLoaded(); + } + }); } public void rootsChanged(final Collection roots) { @@ -133,17 +132,23 @@ public class DetailsCache { } } + public void clearBranches() { + synchronized (myLock) { + myBranches.clear(); + } + } + public void loadAndPutBranches(final VirtualFile root, - final SHAHash hash, final AbstractHash abstractHash, - final Consumer> continuation) { + final Consumer> continuation, final Processor recheck) { myQueue.run(new Task.Backgroundable(myProject, "Load contained in branches", true, BackgroundFromStartOption.getInstance()) { @Override public void run(@NotNull ProgressIndicator indicator) { + if (! recheck.process(abstractHash)) return; if (getBranches(root, abstractHash) != null) return; final List branches; try { - branches = new LowLevelAccessImpl(myProject, root).getBranchesWithCommit(hash); + branches = new LowLevelAccessImpl(myProject, root).getBranchesWithCommit(abstractHash.getString()); } catch (VcsException e) { LOG.info(e); diff --git a/plugins/git4idea/src/git4idea/history/wholeTree/GitLogDetailsPanel.java b/plugins/git4idea/src/git4idea/history/wholeTree/GitLogDetailsPanel.java new file mode 100644 index 000000000000..19144a4c7158 --- /dev/null +++ b/plugins/git4idea/src/git4idea/history/wholeTree/GitLogDetailsPanel.java @@ -0,0 +1,289 @@ +/* + * Copyright 2000-2011 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package git4idea.history.wholeTree; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vcs.changes.issueLinks.IssueLinkHtmlRenderer; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.ui.BrowserHyperlinkListener; +import com.intellij.ui.components.JBScrollPane; +import com.intellij.util.containers.Convertor; +import com.intellij.util.text.DateFormatUtil; +import com.intellij.util.ui.UIUtil; +import com.intellij.vcsUtil.UIVcsUtil; +import git4idea.history.browser.CaptionIcon; +import git4idea.history.browser.GitCommit; +import git4idea.history.browser.SymbolicRefs; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import javax.swing.event.HyperlinkEvent; +import java.awt.*; + +/** + * @author irengrig + * Date: 7/1/11 + * Time: 2:27 PM + */ +public class GitLogDetailsPanel { + public static final String CONFIGURE_BRANCHES = "configure_branches"; + private final static String NOTHING_SELECTED = "nothingSelected"; + private final static String LOADING = "loading"; + private final static String DATA = "data"; + private static final String MULTIPLE_SELECTED = "multiple_selected"; + + private final JPanel myPanel; + private JPanel myMarksPanel; + + private final MyPresentationData myPresentationData; + + private final HtmlHighlighter myHtmlHighlighter; + private JEditorPane myJEditorPane; + private VirtualFile myRoot; + + public GitLogDetailsPanel(final Project myProject, final DetailsCache detailsCache, final Convertor refsProvider) { + myPanel = new JPanel(new CardLayout()); + myPanel.add(UIVcsUtil.errorPanel("Nothing selected", false), NOTHING_SELECTED); + myPanel.add(UIVcsUtil.errorPanel("Loading...", false), LOADING); + myPanel.add(UIVcsUtil.errorPanel("Several commits selected", false), MULTIPLE_SELECTED); + + myHtmlHighlighter = new HtmlHighlighter(); + myPresentationData = new MyPresentationData(myProject, detailsCache, myHtmlHighlighter); + + final JPanel wrapper = new JPanel(new GridBagLayout()); + final GridBagConstraints gb = new GridBagConstraints(0, 0, 1, 1, 0, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, + new Insets(1, 1, 1, 1), 0, 0); + + myJEditorPane = new JEditorPane(UIUtil.HTML_MIME, ""); + myJEditorPane.setPreferredSize(new Dimension(150, 100)); + myJEditorPane.setEditable(false); + myJEditorPane.setBackground(UIUtil.getComboBoxDisabledBackground()); + myJEditorPane.addHyperlinkListener(new BrowserHyperlinkListener() { + @Override + public void hyperlinkUpdate(HyperlinkEvent e) { + /*if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED && CONFIGURE_BRANCHES.equals(e.getDescription())) { + if (myRoot == null) return; + final SymbolicRefs symbolicRefs = refsProvider.convert(myRoot); + if (symbolicRefs == null) return; + final TreeSet localBranches = symbolicRefs.getLocalBranches(); + if (localBranches == null || localBranches.isEmpty()) { + VcsBalloonProblemNotifier.showOverChangesView(myProject, "Branches is not loaded yet", MessageType.WARNING); + } + final ContainedInBranchesConfigDialog dialog = + new ContainedInBranchesConfigDialog(myProject, localBranches, symbolicRefs.getRemoteBranches(), + symbolicRefs.getCurrentName(), symbolicRefs.getTrackedRemoteName()); + dialog.show(); + if (dialog.isChanged()) { + detailsCache.clearBranches(); + } + return; + }*/ + super.hyperlinkUpdate(e); + } + }); + + myMarksPanel = new JPanel(); + final BoxLayout boxLayout = new BoxLayout(myMarksPanel, BoxLayout.X_AXIS); + myMarksPanel.setLayout(boxLayout); + gb.weightx = 1; + wrapper.add(myMarksPanel, gb); + ++ gb.gridy; + gb.weighty = 1; + gb.fill = GridBagConstraints.BOTH; + final JBScrollPane tableScroll = new JBScrollPane(myJEditorPane); + tableScroll.setBorder(null); + myJEditorPane.setBorder(null); + wrapper.add(tableScroll, gb); + myJEditorPane.setBackground(UIUtil.getTableBackground()); + myMarksPanel.setBackground(UIUtil.getTableBackground()); + wrapper.setBackground(UIUtil.getTableBackground()); + + myPanel.add(wrapper, DATA); + ((CardLayout) myPanel.getLayout()).show(myPanel, NOTHING_SELECTED); + } + + public HtmlHighlighter getHtmlHighlighter() { + return myHtmlHighlighter; + } + + public void severalSelected() { + myPresentationData.clear(); + ((CardLayout) myPanel.getLayout()).show(myPanel, MULTIPLE_SELECTED); + } + + public void nothingSelected() { + myPresentationData.clear(); + ((CardLayout) myPanel.getLayout()).show(myPanel, NOTHING_SELECTED); + } + + public void setBranches(final java.util.List branchesList) { + myPresentationData.setBranches(branchesList); + changeDetailsText(); + } + + public void loading(VirtualFile root) { + myRoot = root; + myPresentationData.clear(); + ((CardLayout) myPanel.getLayout()).show(myPanel, LOADING); + } + + public void setData(VirtualFile root, @NotNull final GitCommit commit) { + myRoot = root; + redrawBranchLabels(commit); + + myPresentationData.setCommit(root, commit); + ((CardLayout) myPanel.getLayout()).show(myPanel, DATA); + + changeDetailsText(); + } + + private void changeDetailsText() { + if (myPresentationData.isReady()) { + myJEditorPane.setText(myPresentationData.getText()); + myPanel.revalidate(); + myPanel.repaint(); + } + } + + private void redrawBranchLabels(GitCommit commit) { + final Font tableFont = myJEditorPane.getFont(); + final Font font = tableFont.deriveFont((float)(tableFont.getSize() - 1)); + final String currentBranch = commit.getCurrentBranch(); + myMarksPanel.removeAll(); + for (String s : commit.getLocalBranches()) { + myMarksPanel.add(new JLabel(new CaptionIcon(GitLogUI.Colors.local, font, s, myMarksPanel, CaptionIcon.Form.SQUARE, false, + s.equals(currentBranch)))); + } + for (String s : commit.getRemoteBranches()) { + myMarksPanel.add(new JLabel(new CaptionIcon(GitLogUI.Colors.remote, font, s, myMarksPanel, CaptionIcon.Form.SQUARE, false, + s.equals(currentBranch)))); + } + for (String s : commit.getTags()) { + myMarksPanel.add(new JLabel(new CaptionIcon(GitLogUI.Colors.tag, font, s, myMarksPanel, CaptionIcon.Form.ROUNDED, false, + s.equals(currentBranch)))); + } + } + + public JPanel getComponent() { + return myPanel; + } + + private static class HtmlHighlighter extends HighlightingRendererBase { + private final StringBuilder mySb; + + private HtmlHighlighter() { + super(); + mySb = new StringBuilder(); + } + + @Override + protected void highlight(String s) { + mySb.append("").append(s).append(""); + } + + @Override + protected void usual(String s) { + mySb.append(s); + } + + public String getResult(final String text) { + mySb.setLength(0); + tryHighlight(text); + return mySb.toString(); + } + } + + private static class MyPresentationData { + private String myStartPattern; + private final String myEndPattern = ""; + private String myBranches; + private final Project myProject; + private final DetailsCache myDetailsCache; + private final HtmlHighlighter myHighlighter; + + private MyPresentationData(final Project project, final DetailsCache detailsCache, final HtmlHighlighter highlighter) { + myProject = project; + myDetailsCache = detailsCache; + myHighlighter = highlighter; + } + + public void setCommit(final VirtualFile root, final GitCommit c) { + final String hash = myHighlighter.getResult(c.getHash().getValue()); + final String author = myHighlighter.getResult(c.getAuthor()); + final String committer = myHighlighter.getResult(c.getCommitter()); + final String comment = IssueLinkHtmlRenderer.formatTextWithLinks(myProject, c.getDescription(), + new Convertor() { + @Override + public String convert(String o) { + return myHighlighter.getResult(o); + } + }); + + final StringBuilder sb = new StringBuilder().append("").append(UIUtil.getCssFontDeclaration(UIUtil.getLabelFont())) + .append(""); + final String stashName = myDetailsCache.getStashName(root, c.getShortHash()); + if (! StringUtil.isEmptyOrSpaces(stashName)) { + sb.append(""); + } + sb.append("" + "" + "" + ""); + sb.append("
").append(stashName).append("
Hash:").append( + hash).append("
Author:") + .append(author).append(" (").append(c.getAuthorEmail()).append(") at ") + .append(DateFormatUtil.formatPrettyDateTime(c.getAuthorTime())) + .append("
Commiter:") + .append(committer).append(" (").append(c.getComitterEmail()).append(") at ") + .append(DateFormatUtil.formatPrettyDateTime(c.getDate())).append( + "
Description:") + .append(comment).append("
Contained in branches:"); + myStartPattern = sb.toString(); + } + + public void setBranches(final java.util.List branches) { + final StringBuilder sb = new StringBuilder(); + if (branches != null && (! branches.isEmpty())) { + for (int i = 0; i < branches.size(); i++) { + String s = branches.get(i); + sb.append(s); + if (i + 1 < branches.size()) { + sb.append(", "); + } + } + myBranches = sb.toString(); + } else if (branches != null && branches.isEmpty()) { + myBranches = "<no branches>
"; + } else { + myBranches = "Loading...
"; + } + } + + protected String getBranches() { + return myBranches == null ? "Loading..." : myBranches; + } + + public boolean isReady() { + return myStartPattern != null; + } + + public String getText() { + return myStartPattern + getBranches() + myEndPattern; + } + + public void clear() { + myBranches = null; + } + } +} diff --git a/plugins/git4idea/src/git4idea/history/wholeTree/GitLogSettings.java b/plugins/git4idea/src/git4idea/history/wholeTree/GitLogSettings.java new file mode 100644 index 000000000000..a7d355c1cd20 --- /dev/null +++ b/plugins/git4idea/src/git4idea/history/wholeTree/GitLogSettings.java @@ -0,0 +1,101 @@ +/* + * Copyright 2000-2011 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package git4idea.history.wholeTree; + +import com.intellij.openapi.components.*; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Comparing; +import com.intellij.util.PairConsumer; +import com.intellij.util.containers.HashSet; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +/** + * @author irengrig + * Date: 7/1/11 + * Time: 1:11 PM + */ +@State( + name = "GitLogSettings", + storages = { + @Storage( + id ="other", + file = "$PROJECT_FILE$" + ) + ,@Storage(id = "dir", file = "$PROJECT_CONFIG_DIR$/vcs.xml", scheme = StorageScheme.DIRECTORY_BASED) + } +) +public class GitLogSettings implements PersistentStateComponent { + private MyState myState = new MyState(); + + public boolean setIfChanged(ArrayList local, ArrayList remote) { + if (Comparing.haveEqualElements(myState.myContainedLocalBranches, local) && Comparing.haveEqualElements(myState.myContainedRemoteBranches, remote)) { + return false; + } + setLocal(local); + setRemote(remote); + return true; + } + + public static class MyState { + public List myContainedLocalBranches = new ArrayList(); + public List myContainedRemoteBranches = new ArrayList(); + } + + @Override + public MyState getState() { + return myState; + } + + @Override + public void loadState(MyState state) { + myState = state; + } + + public static GitLogSettings getInstance(final Project project) { + return ServiceManager.getService(project, GitLogSettings.class); + } + + public Set getLocalBranchesCopy() { + return myState.myContainedLocalBranches == null ? Collections.emptySet() : new HashSet(myState.myContainedLocalBranches); + } + + public Set getRemoteBranchesCopy() { + return myState.myContainedRemoteBranches == null ? Collections.emptySet() : new HashSet(myState.myContainedRemoteBranches); + } + + public void iterateBranches(final PairConsumer consumer) { + for (String item : myState.myContainedLocalBranches) { + consumer.consume(item, true); + } + for (String item : myState.myContainedRemoteBranches) { + consumer.consume(item, false); + } + } + + public void setLocal(final List local) { + Collections.sort(local); + myState.myContainedLocalBranches = local; + } + + public void setRemote(final List branches) { + Collections.sort(branches); + myState.myContainedRemoteBranches = branches; + } +} diff --git a/plugins/git4idea/src/git4idea/history/wholeTree/GitLogUI.java b/plugins/git4idea/src/git4idea/history/wholeTree/GitLogUI.java index a3db2df61d5e..470b7d3acedf 100644 --- a/plugins/git4idea/src/git4idea/history/wholeTree/GitLogUI.java +++ b/plugins/git4idea/src/git4idea/history/wholeTree/GitLogUI.java @@ -28,22 +28,23 @@ import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.IconLoader; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.vcs.AbstractFilterChildren; -import com.intellij.openapi.vcs.CheckSamePattern; -import com.intellij.openapi.vcs.ComparableComparator; -import com.intellij.openapi.vcs.VcsDataKeys; +import com.intellij.openapi.vcs.*; import com.intellij.openapi.vcs.changes.Change; import com.intellij.openapi.vcs.changes.committed.CommittedChangesTreeBrowser; import com.intellij.openapi.vcs.changes.committed.RepositoryChangesBrowser; -import com.intellij.openapi.vcs.changes.issueLinks.IssueLinkHtmlRenderer; import com.intellij.openapi.vcs.changes.issueLinks.IssueLinkRenderer; import com.intellij.openapi.vcs.changes.issueLinks.TableLinkMouseListener; import com.intellij.openapi.vcs.ui.SearchFieldAction; import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.ui.*; +import com.intellij.ui.ColoredTableCellRenderer; +import com.intellij.ui.PopupHandler; +import com.intellij.ui.ScrollPaneFactory; +import com.intellij.ui.SimpleTextAttributes; import com.intellij.ui.table.JBTable; import com.intellij.util.Consumer; +import com.intellij.util.PairConsumer; +import com.intellij.util.Processor; import com.intellij.util.containers.Convertor; import com.intellij.util.containers.MultiMap; import com.intellij.util.text.DateFormatUtil; @@ -86,19 +87,19 @@ public class GitLogUI implements Disposable { private JBTable myJBTable; private RepositoryChangesBrowser myRepositoryChangesBrowser; private boolean myDataBeingAdded; - private boolean myMissingSelectionData; private CardLayout myRepoLayout; private JPanel myRepoPanel; private boolean myStarted; -// private JTextField myFilterField; private String myPreviousFilter; - private List mySearchContext; + private final CommentSearchContext myCommentSearchContext; private List myUsersSearchContext; private String mySelectedBranch; private BranchSelectorAction myBranchSelectorAction; - private MySpecificDetails myDetails; private final DescriptionRenderer myDescriptionRenderer; - private final Speedometer mySelectionSpeedometer; + + private GenericDetailsLoader myDetailsLoader; + private GenericDetailsLoader> myBranchesLoader; + private GitLogDetailsPanel myDetailsPanel; private StepType myState; private MoreAction myMoreAction; @@ -107,24 +108,47 @@ public class GitLogUI implements Disposable { private MyCherryPick myCherryPickAction; private MyRefreshAction myRefreshAction; private AnAction myCopyHashAction; + // todo group somewhere?? + private Consumer myDetailsLoaderImpl; + private Consumer myBranchesLoaderImpl; + private final RequestsMerger mySelectionRequestsMerger; + + private final TableCellRenderer myAuthorRenderer; public GitLogUI(Project project, final Mediator mediator) { myProject = project; myMediator = mediator; - mySearchContext = new ArrayList(); + myCommentSearchContext = new CommentSearchContext(); myUsersSearchContext = new ArrayList(); myRefs = new HashMap(); myRecalculatedCommon = new SymbolicRefs(); myPreviousFilter = ""; - myDetails = new MySpecificDetails(myProject); myDescriptionRenderer = new DescriptionRenderer(); - mySelectionSpeedometer = new Speedometer(20, 400); + myCommentSearchContext.addHighlighter(myDescriptionRenderer.myInner.myWorker); + + mySelectionRequestsMerger = new RequestsMerger(new Runnable() { + @Override + public void run() { + selectionChanged(); + } + }, new Consumer() { + @Override + public void consume(Runnable runnable) { + SwingUtilities.invokeLater(runnable); + } + }); createTableModel(); myState = StepType.CONTINUE; + initUiRefresh(); + myAuthorRenderer = new HighLightingRenderer(HIGHLIGHT_TEXT_ATTRIBUTES, SimpleTextAttributes.REGULAR_ATTRIBUTES); + } + + private void initUiRefresh() { myUIRefresh = new UIRefresh() { @Override public void detailsLoaded() { + tryRefreshDetails(); fireTableRepaint(); } @@ -273,31 +297,70 @@ public class GitLogUI implements Disposable { } }); - /*final JComponent specificDetails = myDetails.create(); - final Content specificDetailsContent = myUi.createContent("Specific0", specificDetails, "Details", PlatformIcons.UNSELECT_ALL_ICON, null); - myUi.addContent(specificDetailsContent, 0, PlaceInGrid.bottom, false); - repoContent.setCloseable(false); - repoContent.setPinned(true); + createDetailLoaders(); + } - myUi.getDefaults().initTabDefaults(0, "Git log", null);*/ - - // todo should look like it, but the behaviour of search differs - /*new TableSpeedSearch(myJBTable, new Convertor() { + private void createDetailLoaders() { + myDetailsLoaderImpl = new Consumer() { @Override - public String convert(Object o) { - if (o instanceof CommitI) { - return ((CommitI) o).getDecorationString(); + public void consume(final CommitI commitI) { + if (commitI == null) return; + final GitCommit gitCommit = fullCommitPresentation(commitI); + + if (gitCommit == null) { + final MultiMap question = new MultiMap(); + question.putValue(commitI.selectRepository(myRootsUnderVcs), commitI.getHash()); + myDetailsCache.acceptQuestion(question); + } else { + myDetailsLoader.consume(commitI, gitCommit); } - return o == null ? null : o.toString(); } - });*/ + }; + myDetailsLoader = new GenericDetailsLoader(myDetailsLoaderImpl, new PairConsumer() { + @Override + public void consume(CommitI commitI, GitCommit commit) { + myDetailsPanel.setData(commitI.selectRepository(myRootsUnderVcs), commit); + } + }); - //myUi.getDefaults().initTabDefaults(0, "Git log", ); - /* myUi = RunnerLayoutUi.Factory.getInstance(project).create("Debug", "unknown!", sessionName, this); - myUi.getDefaults().initTabDefaults(0, "Debug", null); + myBranchesLoaderImpl = new Consumer() { + private Processor myRecheck; - myUi.getOptions().setTopToolbar(createTopToolbar(), ActionPlaces.DEBUGGER_TOOLBAR); -*/ + { + myRecheck = new Processor() { + @Override + public boolean process(AbstractHash abstractHash) { + if (myBranchesLoader.getCurrentlySelected() == null) return false; + return Comparing.equal(myBranchesLoader.getCurrentlySelected().getHash(), abstractHash); + } + }; + } + + @Override + public void consume(final CommitI commitI) { + if (commitI == null) return; + final VirtualFile root = commitI.selectRepository(myRootsUnderVcs); + final List branches = myDetailsCache.getBranches(root, commitI.getHash()); + if (branches != null) { + myBranchesLoader.consume(commitI, branches); + return; + } + + myDetailsCache.loadAndPutBranches(root, commitI.getHash(), new Consumer>() { + @Override + public void consume(List strings) { + if (myProject.isDisposed() || strings == null) return; + myBranchesLoader.consume(commitI, strings); + } + }, myRecheck); + } + }; + myBranchesLoader = new GenericDetailsLoader>(myBranchesLoaderImpl, new PairConsumer>() { + @Override + public void consume(CommitI commitI, List strings) { + myDetailsPanel.setBranches(strings); + } + }); } private JComponent createRepositoryBrowserDetails() { @@ -308,9 +371,7 @@ public class GitLogUI implements Disposable { myJBTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent e) { - if (! myDataBeingAdded && (! e.getValueIsAdjusting())) { - selectionChanged(); - } + mySelectionRequestsMerger.request(); } }); myRepoPanel.add("main", myRepositoryChangesBrowser); @@ -323,24 +384,87 @@ public class GitLogUI implements Disposable { } private void selectionChanged() { - mySelectionSpeedometer.event(); - + if (myDataBeingAdded) { + mySelectionRequestsMerger.request(); + return; + } final int[] rows = myJBTable.getSelectedRows(); - myDetails.refresh(null, null); + selectionChangedForDetails(rows); + if (rows.length == 0) { myRepoLayout.show(myRepoPanel, "empty"); myRepoPanel.repaint(); - myMissingSelectionData = false; return; } else if (rows.length >= 10) { myRepoLayout.show(myRepoPanel, "tooMuch"); myRepoPanel.repaint(); - myMissingSelectionData = false; return; } myRepoLayout.show(myRepoPanel, "loading"); myRepoPanel.repaint(); - updateDetailsFromSelection(); + + gatherNotLoadedData(); + } + + private static class MeaningfulSelection { + private CommitI myCommitI; + private int myMeaningfulRows; + + private MeaningfulSelection(int[] rows, final BigTableTableModel tableModel) { + myMeaningfulRows = 0; + for (int row : rows) { + myCommitI = tableModel.getCommitAt(row); + if (!myCommitI.holdsDecoration()) { + ++myMeaningfulRows; + if (myMeaningfulRows > 1) break; + } + } + } + + public CommitI getCommit() { + return myCommitI; + } + + public int getMeaningfulRows() { + return myMeaningfulRows; + } + } + + private void tryRefreshDetails() { + MeaningfulSelection meaningfulSelection = new MeaningfulSelection(myJBTable.getSelectedRows(), myTableModel); + if (meaningfulSelection.getMeaningfulRows() == 1) { + // still have one item selected which probably was not loaded + final CommitI commit = meaningfulSelection.getCommit(); + myDetailsLoaderImpl.consume(commit); + myBranchesLoaderImpl.consume(commit); + } + } + + private void selectionChangedForDetails(int[] rows) { + MeaningfulSelection meaningfulSelection = new MeaningfulSelection(rows, myTableModel); + int meaningfulRows = meaningfulSelection.getMeaningfulRows(); + CommitI commitAt = meaningfulSelection.getCommit(); + + if (meaningfulRows == 0) { + myDetailsPanel.nothingSelected(); + myDetailsLoader.updateSelection(null); + myBranchesLoader.updateSelection(null); + } else if (meaningfulRows == 1) { + final GitCommit commit = fullCommitPresentation(commitAt); + if (commit == null) { + myDetailsPanel.loading(commitAt.selectRepository(myRootsUnderVcs)); + } + myDetailsLoader.updateSelection(commitAt); + myBranchesLoader.updateSelection(commitAt); + } else { + myDetailsPanel.severalSelected(); + myDetailsLoader.updateSelection(null); + myBranchesLoader.updateSelection(null); + } + } + + private GitCommit fullCommitPresentation(CommitI commitAt) { + return myDetailsCache.convert(commitAt.selectRepository(myRootsUnderVcs), commitAt.getHash()); } private static JPanel panelWithCenteredText(final String text) { @@ -353,71 +477,27 @@ public class GitLogUI implements Disposable { return jPanel; } - public void updateSelection() { - updateBranchesFor(); - if (! myMissingSelectionData) return; - updateDetailsFromSelection(); + public void updateByScroll() { + gatherNotLoadedData(); } - private void updateBranchesFor() { - final int[] rows = myJBTable.getSelectedRows(); - if (rows.length == 1 && myDetails.isMissingBranchesInfo() && mySelectionSpeedometer.getSpeed() < 0.1) { - final CommitI commit = myTableModel.getCommitAt(rows[0]); - final VirtualFile root = commit.selectRepository(myRootsUnderVcs); - if (commit.holdsDecoration()) return; - final GitCommit gitCommit = myDetailsCache.convert(root, commit.getHash()); - if (gitCommit == null) return; - final List branches = myDetailsCache.getBranches(root, commit.getHash()); - if (branches != null) { - myDetails.putBranches(root, gitCommit, branches); - } - - final CommitI commitI = myTableModel.getCommitAt(rows[0]); - if (! commit.equals(commitI)) return; - myDetailsCache.loadAndPutBranches(root, gitCommit.getHash(), commit.getHash(), new Consumer>() { - @Override - public void consume(List strings) { - if (myProject.isDisposed()) return; - - final int[] afterRows = myJBTable.getSelectedRows(); - if (myDetails.isMissingBranchesInfo() && afterRows.length == 1 && afterRows[0] == rows[0]) { - final CommitI afterCommit = myTableModel.getCommitAt(rows[0]); - if (afterCommit.holdsDecoration() || (! afterCommit.equals(commit))) return; - myDetails.putBranches(root, gitCommit, strings); - } - } - }); - } - } - - private void updateDetailsFromSelection() { + private void gatherNotLoadedData() { if (myDataBeingAdded) return; - myMissingSelectionData = false; final int[] rows = myJBTable.getSelectedRows(); - if (rows.length == 1) { - final CommitI commitI = myTableModel.getCommitAt(rows[0]); - if (commitI != null && (! commitI.holdsDecoration())) { - final VirtualFile root = commitI.selectRepository(myRootsUnderVcs); - final GitCommit convert = myDetailsCache.convert(root, commitI.getHash()); - if (convert != null) { - myDetails.refresh(root, convert); - } - } - } final List commits = new ArrayList(); final MultiMap missingHashes = new MultiMap(); for (int i = rows.length - 1; i >= 0; --i) { final int row = rows[i]; final CommitI commitI = myTableModel.getCommitAt(row); if (commitI == null || commitI.holdsDecoration()) continue; - VirtualFile root = commitI.selectRepository(myRootsUnderVcs); - AbstractHash hash = commitI.getHash(); - final GitCommit details = myDetailsCache.convert(root, hash); - commits.add(details); - myMissingSelectionData |= details == null; - missingHashes.putValue(root, hash); + final GitCommit details = fullCommitPresentation(commitI); + if (details == null) { + missingHashes.putValue(commitI.selectRepository(myRootsUnderVcs), commitI.getHash()); + } else if (missingHashes.isEmpty()) { // no sense in collecting commits when s + commits.add(details); + } } - if (myMissingSelectionData) { + if (! missingHashes.isEmpty()) { myDetailsCache.acceptQuestion(missingHashes); return; } @@ -481,7 +561,7 @@ public class GitLogUI implements Disposable { myMyChangeListener = new GitTableScrollChangeListener(myJBTable, myDetailsCache, myTableModel, new Runnable() { @Override public void run() { - updateSelection(); + updateByScroll(); } }); scrollPane.getViewport().addChangeListener(myMyChangeListener); @@ -492,10 +572,15 @@ public class GitLogUI implements Disposable { mainBorderWrapper.add(scrollPane, BorderLayout.CENTER); mainBorderWrapper.setBorder(BorderFactory.createLineBorder(UIUtil.getBorderColor())); wrapper.add(mainBorderWrapper, BorderLayout.CENTER); - final JComponent specificDetails = myDetails.create(); + myDetailsPanel = new GitLogDetailsPanel(myProject, myDetailsCache, new Convertor() { + @Override + public SymbolicRefs convert(VirtualFile o) { + return myRefs.get(o); + } + }); final JPanel borderWrapper = new JPanel(new BorderLayout()); borderWrapper.setBorder(BorderFactory.createLineBorder(UIUtil.getBorderColor())); - borderWrapper.add(specificDetails, BorderLayout.CENTER); + borderWrapper.add(myDetailsPanel.getComponent(), BorderLayout.CENTER); final Splitter splitter = new Splitter(true, 0.6f); splitter.setFirstComponent(wrapper); @@ -604,7 +689,7 @@ public class GitLogUI implements Disposable { if (rows.length != 1) return; final CommitI commitAt = myTableModel.getCommitAt(rows[0]); if (commitAt == null) return; - final GitCommit gitCommit = myDetailsCache.convert(commitAt.selectRepository(myRootsUnderVcs), commitAt.getHash()); + final GitCommit gitCommit = fullCommitPresentation(commitAt); if (gitCommit == null) return; sink.put(key, gitCommit.getDescription()); } @@ -615,7 +700,7 @@ public class GitLogUI implements Disposable { private GitCommit getCommitAtRow(int row) { final CommitI commitAt = myTableModel.getCommitAt(row); if (commitAt == null) return null; - final GitCommit gitCommit = myDetailsCache.convert(commitAt.selectRepository(myRootsUnderVcs), commitAt.getHash()); + final GitCommit gitCommit = fullCommitPresentation(commitAt); if (gitCommit == null) return null; return gitCommit; } @@ -647,20 +732,42 @@ public class GitLogUI implements Disposable { return true; } - private Pair> preparse(String previousFilter) { - final String[] strings = previousFilter.split("[\\s]"); - StringBuilder sb = new StringBuilder(); - mySearchContext.clear(); - final List words = new ArrayList(); - for (String string : strings) { - if (string.trim().length() == 0) continue; - mySearchContext.add(string.toLowerCase()); - final String word = StringUtil.escapeToRegexp(string); - sb.append(word).append(".*"); - words.add(word); + private static class CommentSearchContext { + private final List myListeners; + private final List mySearchContext; + + private CommentSearchContext() { + mySearchContext = new ArrayList(); + myListeners = new ArrayList(); + } + + public void addHighlighter(final HighlightingRendererBase renderer) { + myListeners.add(renderer); + } + + public void clear() { + mySearchContext.clear(); + for (HighlightingRendererBase listener : myListeners) { + listener.setSearchContext(Collections.emptyList()); + } + } + + public String preparse(String previousFilter) { + final String[] strings = previousFilter.split("[\\s]"); + StringBuilder sb = new StringBuilder(); + mySearchContext.clear(); + for (String string : strings) { + if (string.trim().length() == 0) continue; + mySearchContext.add(string.toLowerCase()); + final String word = StringUtil.escapeToRegexp(string); + sb.append(word).append(".*"); + } + new SubstringsFilter().doFilter(mySearchContext); + for (HighlightingRendererBase listener : myListeners) { + listener.setSearchContext(mySearchContext); + } + return sb.toString(); } - new SubstringsFilter().doFilter(mySearchContext); - return new Pair>(sb.toString(), words); } public static class SubstringsFilter extends AbstractFilterChildren { @@ -729,103 +836,17 @@ public class GitLogUI implements Disposable { } }; - private static abstract class HighlightingRendererBase { - private final List mySearchContext; - - protected HighlightingRendererBase(List searchContext) { - mySearchContext = searchContext; - } - - protected abstract void usual(final String s); - protected abstract void highlight(final String s); - - void tryHighlight(String text) { - final String lower = text.toLowerCase(); - int idxFrom = 0; - while (idxFrom < text.length()) { - boolean adjusted = false; - int adjustedIdx = text.length() + 1; - int adjLen = 0; - for (String word : mySearchContext) { - final int next = lower.indexOf(word, idxFrom); - if ((next != -1) && (adjustedIdx > next)) { - adjustedIdx = next; - adjLen = word.length(); - adjusted = true; - } - } - if (adjusted) { - if (idxFrom != adjustedIdx) { - usual(text.substring(idxFrom, adjustedIdx)); - } - idxFrom = adjustedIdx + adjLen; - highlight(text.substring(adjustedIdx, idxFrom)); - continue; - } - usual(text.substring(idxFrom)); - return; - } - } - } - - private class AuthorRenderer extends ColoredTableCellRenderer { - private final SimpleTextAttributes myCurrentUserAttributes; - private final SimpleTextAttributes myHighlightAttributes; - private final List mySearchContext; - private final SimpleTextAttributes myUsualAttributes; - protected final HighlightingRendererBase myWorker; - - private AuthorRenderer(SimpleTextAttributes currentUserAttributes, - SimpleTextAttributes highlightAttributes, - List searchContext, - SimpleTextAttributes usualAttributes) { - myCurrentUserAttributes = currentUserAttributes; - myHighlightAttributes = highlightAttributes; - mySearchContext = searchContext; - myUsualAttributes = usualAttributes; - myWorker = new HighlightingRendererBase(searchContext) { - @Override - protected void usual(String s) { - append(s, myUsualAttributes); - } - - @Override - protected void highlight(String s) { - append(s, myHighlightAttributes); - } - }; - } - - @Override - protected void customizeCellRenderer(JTable table, Object value, boolean selected, boolean hasFocus, int row, int column) { - setBackground(getLogicBackground(selected, row)); - if (BigTableTableModel.LOADING == value) { - return; - } - final String text = value.toString(); - - if (mySearchContext.isEmpty()) { - append(text, myUsualAttributes); - return; - } - myWorker.tryHighlight(text); - } - } - private class HighLightingRenderer extends ColoredTableCellRenderer { private final SimpleTextAttributes myHighlightAttributes; - private final List mySearchContext; private final SimpleTextAttributes myUsualAttributes; private SimpleTextAttributes myUsualAttributesForRun; protected final HighlightingRendererBase myWorker; - public HighLightingRenderer(SimpleTextAttributes highlightAttributes, SimpleTextAttributes usualAttributes, - final List searchContext) { + public HighLightingRenderer(SimpleTextAttributes highlightAttributes, SimpleTextAttributes usualAttributes) { myHighlightAttributes = highlightAttributes; - mySearchContext = searchContext; myUsualAttributes = usualAttributes == null ? SimpleTextAttributes.REGULAR_ATTRIBUTES : usualAttributes; myUsualAttributesForRun = myUsualAttributes; - myWorker = new HighlightingRendererBase(searchContext) { + myWorker = new HighlightingRendererBase() { @Override protected void usual(String s) { append(s, myUsualAttributesForRun); @@ -838,6 +859,10 @@ public class GitLogUI implements Disposable { }; } + public HighlightingRendererBase getWorker() { + return myWorker; + } + @Override protected void customizeCellRenderer(JTable table, Object value, boolean selected, boolean hasFocus, int row, int column) { setBackground(getLogicBackground(selected, row)); @@ -847,7 +872,7 @@ public class GitLogUI implements Disposable { final String text = value.toString(); myUsualAttributesForRun = isCurrentUser(row, text) ? SimpleTextAttributes.merge(myUsualAttributes, SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES) : myUsualAttributes; - if (mySearchContext.isEmpty()) { + if (myWorker.isEmpty()) { append(text, myUsualAttributesForRun); return; } @@ -931,12 +956,7 @@ public class GitLogUI implements Disposable { CaptionIcon.Form.SQUARE, plus, branch.equals(commit.getCurrentBranch())); myBranchMap.put(branch, icon); } - myCurrentWidth = icon.getIconWidth(); - myPanel.removeAll(); - myPanel.setBackground(getLogicBackground(isSelected, row)); - myPanel.add(new JLabel(icon)); - myInner.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); - myPanel.add(myInner); + addOneIcon(table, value, isSelected, hasFocus, row, column, icon); return myPanel; } if ((localSize + remoteSize == 0) && (tagsSize > 0)) { @@ -947,12 +967,7 @@ public class GitLogUI implements Disposable { tag, table, CaptionIcon.Form.ROUNDED, tagsSize > 1, false); myTagMap.put(tag, icon); } - myCurrentWidth = icon.getIconWidth(); - myPanel.removeAll(); - myPanel.setBackground(getLogicBackground(isSelected, row)); - myPanel.add(new JLabel(icon)); - myInner.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); - myPanel.add(myInner); + addOneIcon(table, value, isSelected, hasFocus, row, column, icon); return myPanel; } } @@ -960,12 +975,21 @@ public class GitLogUI implements Disposable { return myInner; } + private void addOneIcon(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column, Icon icon) { + myCurrentWidth = icon.getIconWidth(); + myPanel.removeAll(); + myPanel.setBackground(getLogicBackground(isSelected, row)); + myPanel.add(new JLabel(icon)); + myInner.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + myPanel.add(myInner); + } + private class Inner extends HighLightingRenderer { private final IssueLinkRenderer myIssueLinkRenderer; private final Consumer myConsumer; private Inner() { - super(HIGHLIGHT_TEXT_ATTRIBUTES, null, mySearchContext); + super(HIGHLIGHT_TEXT_ATTRIBUTES, null); myIssueLinkRenderer = new IssueLinkRenderer(myProject, this); myConsumer = new Consumer() { @Override @@ -992,10 +1016,8 @@ public class GitLogUI implements Disposable { Color bkgColor; final CommitI commitAt = myTableModel.getCommitAt(row); GitCommit gitCommit = null; - VirtualFile root = null; - if (commitAt != null & (! commitAt.holdsDecoration())) { - root = commitAt.selectRepository(myRootsUnderVcs); - gitCommit = myDetailsCache.convert(root, commitAt.getHash()); + if (commitAt != null && (! commitAt.holdsDecoration())) { + gitCommit = fullCommitPresentation(commitAt); } if (isSelected) { @@ -1003,7 +1025,7 @@ public class GitLogUI implements Disposable { } else { bkgColor = UIUtil.getTableBackground(); if (gitCommit != null) { - if (myDetailsCache.getStashName(root, gitCommit.getShortHash()) != null) { + if (myDetailsCache.getStashName(commitAt.selectRepository(myRootsUnderVcs), gitCommit.getShortHash()) != null) { bkgColor = Colors.stashed; } else if (gitCommit.isOnLocal() && gitCommit.isOnTracked()) { bkgColor = Colors.commonThisBranch; @@ -1019,9 +1041,6 @@ public class GitLogUI implements Disposable { private void initAuthor() { AUTHOR = new ColumnInfo("Author") { - private final TableCellRenderer myRenderer = new HighLightingRenderer(HIGHLIGHT_TEXT_ATTRIBUTES, - SimpleTextAttributes.REGULAR_ATTRIBUTES, myUsersSearchContext); - @Override public String valueOf(Object o) { if (o instanceof GitCommit) { @@ -1032,7 +1051,7 @@ public class GitLogUI implements Disposable { @Override public TableCellRenderer getRenderer(Object o) { - return myRenderer; + return myAuthorRenderer; } }; } @@ -1096,7 +1115,7 @@ public class GitLogUI implements Disposable { final Collection startingPoints = mySelectedBranch == null ? Collections.emptyList() : Collections.singletonList(mySelectedBranch); myDescriptionRenderer.resetIcons(); final boolean commentFilterEmpty = StringUtil.isEmptyOrSpaces(myPreviousFilter); - mySearchContext.clear(); + myCommentSearchContext.clear(); myUsersSearchContext.clear(); if (commentFilterEmpty && (myUserFilterI.myFilter == null)) { @@ -1105,9 +1124,8 @@ public class GitLogUI implements Disposable { } else { ChangesFilter.Comment comment = null; if (! commentFilterEmpty) { - final Pair> preparse = preparse(myPreviousFilter); - final String first = preparse.getFirst(); - comment = new ChangesFilter.Comment(first); + final String commentFilter = myCommentSearchContext.preparse(myPreviousFilter); + comment = new ChangesFilter.Comment(commentFilter); } Set userFilters = null; if (myUserFilterI.myFilter != null) { @@ -1127,13 +1145,14 @@ public class GitLogUI implements Disposable { myMediator.reload(new RootsHolder(myRootsUnderVcs), startingPoints, new GitLogFilters(comment, userFilters, null, possibleReferencies)); } + myCommentSearchContext.addHighlighter(myDetailsPanel.getHtmlHighlighter()); updateMoreVisibility(); - selectionChanged(); + mySelectionRequestsMerger.request(); fireTableRepaint(); myTableModel.fireTableRowsDeleted(0, was); } - private interface Colors { + interface Colors { Color tag = new Color(241, 239, 158); Color remote = new Color(188,188,252); Color local = new Color(117,238,199); @@ -1142,147 +1161,6 @@ public class GitLogUI implements Disposable { Color stashed = new Color(225,225,225); } - private class MySpecificDetails { - private JEditorPane myJEditorPane; - private JPanel myMarksPanel; - private BoxLayout myBoxLayout; - private final Project myProject; - private boolean myMissingBranchesInfo; - - private MySpecificDetails(final Project project) { - myProject = project; - } - - public JComponent create() { - myJEditorPane = new JEditorPane(UIUtil.HTML_MIME, ""); - myJEditorPane.setPreferredSize(new Dimension(150, 100)); - myJEditorPane.setEditable(false); - myJEditorPane.setBackground(UIUtil.getComboBoxDisabledBackground()); - myJEditorPane.addHyperlinkListener(new BrowserHyperlinkListener()); - myMarksPanel = new JPanel(); - myBoxLayout = new BoxLayout(myMarksPanel, BoxLayout.X_AXIS); - myMarksPanel.setLayout(myBoxLayout); - final JPanel wrapper = new JPanel(new BorderLayout()); - wrapper.add(myMarksPanel, BorderLayout.NORTH); - wrapper.add(myJEditorPane, BorderLayout.CENTER); - final Color color = UIUtil.getTableBackground(); - myJEditorPane.setBackground(color); - wrapper.setBackground(color); - myMarksPanel.setBackground(color); - final JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(wrapper); - return scrollPane; - } - - public void putBranches(VirtualFile root, final GitCommit commit, final List branches) { - myMissingBranchesInfo = branches == null; - myJEditorPane.setText(parseDetails(root, commit, branches)); - } - - public boolean isMissingBranchesInfo() { - return myMissingBranchesInfo; - } - - public void refresh(VirtualFile root, final GitCommit commit) { - if (commit == null) { - myJEditorPane.setText(""); - myMarksPanel.removeAll(); - myMissingBranchesInfo = false; - } else { - final List branches = myDetailsCache.getBranches(root, commit.getShortHash()); - myMissingBranchesInfo = branches == null; - final Font font = myJEditorPane.getFont().deriveFont((float) (myJEditorPane.getFont().getSize() - 1)); - final String currentBranch = commit.getCurrentBranch(); - myMarksPanel.removeAll(); - for (String s : commit.getLocalBranches()) { - myMarksPanel.add(new JLabel(new CaptionIcon(Colors.local, font, s, myMarksPanel, CaptionIcon.Form.SQUARE, false, - s.equals(currentBranch)))); - } - for (String s : commit.getRemoteBranches()) { - myMarksPanel.add(new JLabel(new CaptionIcon(Colors.remote, font, s, myMarksPanel, CaptionIcon.Form.SQUARE, false, - s.equals(currentBranch)))); - } - for (String s : commit.getTags()) { - myMarksPanel.add(new JLabel(new CaptionIcon(Colors.tag, font, s, myMarksPanel, CaptionIcon.Form.ROUNDED, false, - s.equals(currentBranch)))); - } - myMarksPanel.repaint(); - myJEditorPane.setText(parseDetails(root, commit, branches)); - } - } - - private String parseDetails(VirtualFile root, final GitCommit c, final List branches) { - final String hash = new HtmlHighlighter(c.getHash().getValue()).getResult(); - final String author = new HtmlHighlighter(c.getAuthor()).getResult(); - final String committer = new HtmlHighlighter(c.getCommitter()).getResult(); - final String comment = IssueLinkHtmlRenderer.formatTextWithLinks(myProject, c.getDescription(), - new Convertor() { - @Override - public String convert(String o) { - return new HtmlHighlighter(o).getResult(); - } - }); - - final StringBuilder sb = new StringBuilder().append("").append(UIUtil.getCssFontDeclaration(UIUtil.getLabelFont())) - .append(""); - final String stashName = myDetailsCache.getStashName(root, c.getShortHash()); - if (! StringUtil.isEmptyOrSpaces(stashName)) { - sb.append(""); - } - sb.append("" + "" + "" + ""); - sb.append(""); - sb.append("
").append(stashName).append("
Hash:").append( - hash).append("
Author:") - .append(author).append(" (").append(c.getAuthorEmail()).append(") at ") - .append(DateFormatUtil.formatPrettyDateTime(c.getAuthorTime())) - .append("
Commiter:") - .append(committer).append(" (").append(c.getComitterEmail()).append(") at ") - .append(DateFormatUtil.formatPrettyDateTime(c.getDate())).append( - "
Description:") - .append(comment).append("
Contained in branches:"); - if (branches != null && (! branches.isEmpty())) { - for (int i = 0; i < branches.size(); i++) { - String s = branches.get(i); - sb.append(s); - if (i + 1 < branches.size()) { - sb.append(", "); - } - } - } else if (branches != null && branches.isEmpty()) { - sb.append("<no branches>"); - } else { - sb.append("Loading..."); - } - sb.append("
"); - return sb.toString(); - } - } - - private class HtmlHighlighter extends HighlightingRendererBase { - private final String myText; - private final StringBuilder mySb; - - private HtmlHighlighter(String text) { - super(mySearchContext); - myText = text; - mySb = new StringBuilder(); - } - - @Override - protected void highlight(String s) { - mySb.append("").append(s).append(""); - } - - @Override - protected void usual(String s) { - mySb.append(s); - } - - public String getResult() { - tryHighlight(myText); - return mySb.toString(); - } - } - private class MyCherryPick extends DumbAwareAction { private final Set myIdsInProgress; diff --git a/plugins/git4idea/src/git4idea/history/wholeTree/GroupHeaderCommitDecorator.java b/plugins/git4idea/src/git4idea/history/wholeTree/GroupHeaderCommitDecorator.java index 47ddaf21f591..16dd255488a9 100644 --- a/plugins/git4idea/src/git4idea/history/wholeTree/GroupHeaderCommitDecorator.java +++ b/plugins/git4idea/src/git4idea/history/wholeTree/GroupHeaderCommitDecorator.java @@ -69,4 +69,24 @@ public class GroupHeaderCommitDecorator implements CommitI { public CommitI getDelegate() { return myDelegate; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + GroupHeaderCommitDecorator that = (GroupHeaderCommitDecorator)o; + + if (!myDecoration.equals(that.myDecoration)) return false; + if (!myDelegate.equals(that.myDelegate)) return false; + + return true; + } + + @Override + public int hashCode() { + int result = myDecoration.hashCode(); + result = 31 * result + myDelegate.hashCode(); + return result; + } } diff --git a/plugins/git4idea/src/git4idea/history/wholeTree/GroupHeaderDatePseudoCommit.java b/plugins/git4idea/src/git4idea/history/wholeTree/GroupHeaderDatePseudoCommit.java index 830b4cb1ff9b..7be91932402d 100644 --- a/plugins/git4idea/src/git4idea/history/wholeTree/GroupHeaderDatePseudoCommit.java +++ b/plugins/git4idea/src/git4idea/history/wholeTree/GroupHeaderDatePseudoCommit.java @@ -61,4 +61,24 @@ public class GroupHeaderDatePseudoCommit implements CommitI { @Override public void setWireNumber(int wireNumber) { } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + GroupHeaderDatePseudoCommit that = (GroupHeaderDatePseudoCommit)o; + + if (myTime != that.myTime) return false; + if (!myGroupName.equals(that.myGroupName)) return false; + + return true; + } + + @Override + public int hashCode() { + int result = (int)(myTime ^ (myTime >>> 32)); + result = 31 * result + myGroupName.hashCode(); + return result; + } } diff --git a/plugins/git4idea/src/git4idea/history/wholeTree/HighlightingRendererBase.java b/plugins/git4idea/src/git4idea/history/wholeTree/HighlightingRendererBase.java new file mode 100644 index 000000000000..37825be4b46d --- /dev/null +++ b/plugins/git4idea/src/git4idea/history/wholeTree/HighlightingRendererBase.java @@ -0,0 +1,72 @@ +/* + * Copyright 2000-2011 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package git4idea.history.wholeTree; + +import java.util.ArrayList; +import java.util.List; + +/** +* @author irengrig +* Date: 7/1/11 +* Time: 2:37 PM +*/ +abstract class HighlightingRendererBase { + private final List mySearchContext; + + HighlightingRendererBase() { + mySearchContext = new ArrayList(); + } + + public void setSearchContext(final List list) { + mySearchContext.clear(); + mySearchContext.addAll(list); + } + + protected abstract void usual(final String s); + protected abstract void highlight(final String s); + + void tryHighlight(String text) { + final String lower = text.toLowerCase(); + int idxFrom = 0; + while (idxFrom < text.length()) { + boolean adjusted = false; + int adjustedIdx = text.length() + 1; + int adjLen = 0; + for (String word : mySearchContext) { + final int next = lower.indexOf(word, idxFrom); + if ((next != -1) && (adjustedIdx > next)) { + adjustedIdx = next; + adjLen = word.length(); + adjusted = true; + } + } + if (adjusted) { + if (idxFrom != adjustedIdx) { + usual(text.substring(idxFrom, adjustedIdx)); + } + idxFrom = adjustedIdx + adjLen; + highlight(text.substring(adjustedIdx, idxFrom)); + continue; + } + usual(text.substring(idxFrom)); + return; + } + } + + public boolean isEmpty() { + return mySearchContext == null || mySearchContext.isEmpty(); + } +} diff --git a/plugins/git4idea/src/git4idea/history/wholeTree/MultipleRepositoryCommitDecorator.java b/plugins/git4idea/src/git4idea/history/wholeTree/MultipleRepositoryCommitDecorator.java index 3367bcc46076..2f96c1ac82ec 100644 --- a/plugins/git4idea/src/git4idea/history/wholeTree/MultipleRepositoryCommitDecorator.java +++ b/plugins/git4idea/src/git4idea/history/wholeTree/MultipleRepositoryCommitDecorator.java @@ -65,4 +65,24 @@ public class MultipleRepositoryCommitDecorator implements CommitI { public T selectRepository(List repositories) { return repositories.get(myRepositoryIdx); } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + MultipleRepositoryCommitDecorator that = (MultipleRepositoryCommitDecorator)o; + + if (myRepositoryIdx != that.myRepositoryIdx) return false; + if (!myDelegate.equals(that.myDelegate)) return false; + + return true; + } + + @Override + public int hashCode() { + int result = myDelegate.hashCode(); + result = 31 * result + myRepositoryIdx; + return result; + } } diff --git a/plugins/git4idea/src/git4idea/history/wholeTree/WireNumberCommitDecoration.java b/plugins/git4idea/src/git4idea/history/wholeTree/WireNumberCommitDecoration.java index 517d156631fe..5ddc6691b7c3 100644 --- a/plugins/git4idea/src/git4idea/history/wholeTree/WireNumberCommitDecoration.java +++ b/plugins/git4idea/src/git4idea/history/wholeTree/WireNumberCommitDecoration.java @@ -64,4 +64,24 @@ public class WireNumberCommitDecoration implements CommitI { public void setWireNumber(int wireNumber) { myWireNumber = wireNumber; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + WireNumberCommitDecoration that = (WireNumberCommitDecoration)o; + + if (myWireNumber != that.myWireNumber) return false; + if (!myDelegate.equals(that.myDelegate)) return false; + + return true; + } + + @Override + public int hashCode() { + int result = myDelegate.hashCode(); + result = 31 * result + myWireNumber; + return result; + } }