From 2394bc283028017efd43f503c88db75d7bf4ad1f Mon Sep 17 00:00:00 2001 From: Julia Beliaeva Date: Wed, 14 Jan 2015 16:08:45 +0300 Subject: [PATCH] [vcs-log] show progress in find by hash popup while loading log IDEA-134749 --- .../api/src/com/intellij/vcs/log/VcsLog.java | 4 +- .../com/intellij/vcs/log/impl/VcsLogImpl.java | 7 +- .../vcs/log/ui/FindPopupWithProgress.java | 125 ++++++++++++++++++ .../intellij/vcs/log/ui/GoToRefAction.java | 20 +-- .../PopupWithTextFieldWithAutoCompletion.java | 85 ------------ .../vcs/log/ui/TextFieldWithProgress.java | 104 +++++++++++++++ .../com/intellij/vcs/log/ui/VcsLogUiImpl.java | 26 ++-- .../SelectRevisionInGitLogAction.java | 36 ++++- 8 files changed, 293 insertions(+), 114 deletions(-) create mode 100644 platform/vcs-log/impl/src/com/intellij/vcs/log/ui/FindPopupWithProgress.java delete mode 100644 platform/vcs-log/impl/src/com/intellij/vcs/log/ui/PopupWithTextFieldWithAutoCompletion.java create mode 100644 platform/vcs-log/impl/src/com/intellij/vcs/log/ui/TextFieldWithProgress.java diff --git a/platform/vcs-log/api/src/com/intellij/vcs/log/VcsLog.java b/platform/vcs-log/api/src/com/intellij/vcs/log/VcsLog.java index 29f63e6cdeb3..9636218cd449 100644 --- a/platform/vcs-log/api/src/com/intellij/vcs/log/VcsLog.java +++ b/platform/vcs-log/api/src/com/intellij/vcs/log/VcsLog.java @@ -21,6 +21,7 @@ import org.jetbrains.annotations.Nullable; import java.awt.*; import java.util.Collection; import java.util.List; +import java.util.concurrent.Future; /** * Use this interface to access information available in the VCS Log. @@ -55,7 +56,7 @@ public interface VcsLog { /** * Selects the commit node defined by the given reference (commit hash, branch or tag). */ - void jumpToReference(String reference); + Future jumpToReference(String reference); /** * Returns the VCS log toolbar component. @@ -68,5 +69,4 @@ public interface VcsLog { */ @NotNull Collection getLogProviders(); - } diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/impl/VcsLogImpl.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/impl/VcsLogImpl.java index e80032bbd9ba..a36e871489bd 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/impl/VcsLogImpl.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/impl/VcsLogImpl.java @@ -28,6 +28,7 @@ import org.jetbrains.annotations.Nullable; import java.awt.*; import java.util.Collection; import java.util.List; +import java.util.concurrent.Future; /** * @@ -85,7 +86,7 @@ public class VcsLogImpl implements VcsLog { } @Override - public void jumpToReference(final String reference) { + public Future jumpToReference(final String reference) { Collection references = getAllReferences(); VcsRef ref = ContainerUtil.find(references, new Condition() { @Override @@ -94,10 +95,10 @@ public class VcsLogImpl implements VcsLog { } }); if (ref != null) { - myUi.jumpToCommit(ref.getCommitHash()); + return myUi.jumpToCommit(ref.getCommitHash()); } else { - myUi.jumpToCommitByPartOfHash(reference); + return myUi.jumpToCommitByPartOfHash(reference); } } diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/FindPopupWithProgress.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/FindPopupWithProgress.java new file mode 100644 index 000000000000..0b46160165ad --- /dev/null +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/FindPopupWithProgress.java @@ -0,0 +1,125 @@ +/* + * Copyright 2000-2015 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.vcs.log.ui; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.popup.JBPopup; +import com.intellij.openapi.ui.popup.JBPopupFactory; +import com.intellij.openapi.ui.popup.JBPopupListener; +import com.intellij.openapi.ui.popup.LightweightWindowEvent; +import com.intellij.ui.components.JBTextField; +import com.intellij.util.Function; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.awt.*; +import java.util.Collection; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +public class FindPopupWithProgress { + private static final Logger LOG = Logger.getInstance(FindPopupWithProgress.class); + + @NotNull private final TextFieldWithProgress myTextField; + @NotNull private final Function myFunction; + @NotNull private final JBPopup myPopup; + @Nullable private Future myFuture; + + public FindPopupWithProgress(@NotNull final Project project, + @NotNull Collection variants, + @NotNull Function function) { + myFunction = function; + myTextField = new TextFieldWithProgress(project, variants) { + @Override + public void onOk() { + if (myFuture == null) { + final Future future = myFunction.fun(getText().trim()); + myFuture = future; + showProgress(); + ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { + @Override + public void run() { + try { + future.get(); + ok(); + } + catch (CancellationException ex) { + cancel(); + } + catch (InterruptedException ex) { + cancel(); + } + catch (ExecutionException ex) { + LOG.error(ex); + cancel(); + } + } + }); + } + } + }; + + myPopup = JBPopupFactory.getInstance().createComponentPopupBuilder(myTextField, myTextField.getPreferableFocusComponent()) + .setCancelOnClickOutside(true).setCancelOnWindowDeactivation(true).setCancelKeyEnabled(true).setRequestFocus(true).createPopup(); + myPopup.addListener(new JBPopupListener.Adapter() { + @Override + public void onClosed(LightweightWindowEvent event) { + if (!event.isOk()) { + if (myFuture != null) { + myFuture.cancel(false); + myFuture = null; + } + } + } + }); + + final JBTextField field = new JBTextField(20); + final Dimension size = field.getPreferredSize(); + final Insets insets = myTextField.getBorder().getBorderInsets(myTextField); + size.height += 6 + insets.top + insets.bottom; + size.width += 4 + insets.left + insets.right; + myPopup.setSize(size); + } + + private void cancel() { + ApplicationManager.getApplication().invokeLater(new Runnable() { + @Override + public void run() { + if (myFuture != null) myFuture = null; + myTextField.hideProgress(); + myPopup.cancel(); + } + }); + } + + private void ok() { + ApplicationManager.getApplication().invokeLater(new Runnable() { + @Override + public void run() { + if (myFuture != null) myFuture = null; + myTextField.hideProgress(); + myPopup.closeOk(null); + } + }); + } + + public void showUnderneathOf(@NotNull Component anchor) { + myPopup.showUnderneathOf(anchor); + } +} diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/GoToRefAction.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/GoToRefAction.java index 8b117c72da26..12f6b65a0551 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/GoToRefAction.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/GoToRefAction.java @@ -18,9 +18,6 @@ package com.intellij.vcs.log.ui; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.popup.JBPopup; -import com.intellij.openapi.ui.popup.JBPopupListener; -import com.intellij.openapi.ui.popup.LightweightWindowEvent; import com.intellij.util.Function; import com.intellij.util.containers.ContainerUtil; import com.intellij.vcs.log.VcsLog; @@ -28,6 +25,7 @@ import com.intellij.vcs.log.VcsLogDataKeys; import com.intellij.vcs.log.VcsRef; import java.util.Collection; +import java.util.concurrent.Future; public class GoToRefAction extends DumbAwareAction { @@ -45,16 +43,12 @@ public class GoToRefAction extends DumbAwareAction { return ref.getName(); } }); - final PopupWithTextFieldWithAutoCompletion textField = new PopupWithTextFieldWithAutoCompletion(project, refs); - JBPopup popup = textField.createPopup(); - popup.addListener(new JBPopupListener.Adapter() { - @Override - public void onClosed(LightweightWindowEvent event) { - if (event.isOk()) { - log.jumpToReference(textField.getText().trim()); - } - } - }); + FindPopupWithProgress popup = new FindPopupWithProgress(project, refs, new Function() { + @Override + public Future fun(String text) { + return log.jumpToReference(text); + } + }); popup.showUnderneathOf(log.getToolbar()); } diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/PopupWithTextFieldWithAutoCompletion.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/PopupWithTextFieldWithAutoCompletion.java deleted file mode 100644 index fbfb71de3c04..000000000000 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/PopupWithTextFieldWithAutoCompletion.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2000-2013 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.vcs.log.ui; - -import com.intellij.openapi.editor.ex.EditorEx; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.popup.JBPopup; -import com.intellij.openapi.ui.popup.JBPopupFactory; -import com.intellij.spellchecker.ui.SpellCheckingEditorCustomization; -import com.intellij.ui.TextFieldWithAutoCompletion; -import com.intellij.ui.components.JBTextField; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import javax.swing.border.EmptyBorder; -import java.awt.*; -import java.awt.event.KeyEvent; -import java.util.Collection; - -public class PopupWithTextFieldWithAutoCompletion extends TextFieldWithAutoCompletion { - - @Nullable private JBPopup myPopup; - - public PopupWithTextFieldWithAutoCompletion(@NotNull Project project, @NotNull Collection variants) { - super(project, new StringsCompletionProvider(variants, null), false, null); - setBorder(new EmptyBorder(3, 3, 3, 3)); - } - - public JBPopup createPopup() { - myPopup = JBPopupFactory.getInstance().createComponentPopupBuilder(this, this) - .setCancelOnClickOutside(true) - .setCancelOnWindowDeactivation(true) - .setCancelKeyEnabled(true) - .setRequestFocus(true) - .createPopup(); - - final JBTextField field = new JBTextField(20); - final Dimension size = field.getPreferredSize(); - final Insets insets = getBorder().getBorderInsets(this); - size.height+=6 + insets.top + insets.bottom; - size.width +=4 + insets.left + insets.right; - myPopup.setSize(size); - - return myPopup; - } - - @Override - protected boolean processKeyBinding(KeyStroke ks, KeyEvent e, int condition, boolean pressed) { - if (e.getKeyCode() == KeyEvent.VK_ENTER) { - if (myPopup != null) { - myPopup.closeOk(e); - } - return true; - } - else if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { - if (myPopup != null) { - myPopup.cancel(e); - } - return true; - } - return false; - } - - @Override - protected EditorEx createEditor() { - // spell check is not needed - EditorEx editor = super.createEditor(); - SpellCheckingEditorCustomization.getInstance(false).customize(editor); - return editor; - } -} diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/TextFieldWithProgress.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/TextFieldWithProgress.java new file mode 100644 index 000000000000..70901fe4caaa --- /dev/null +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/TextFieldWithProgress.java @@ -0,0 +1,104 @@ +/* + * Copyright 2000-2015 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.vcs.log.ui; + +import com.intellij.openapi.editor.ex.EditorEx; +import com.intellij.openapi.progress.PerformInBackgroundOption; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.Task; +import com.intellij.openapi.project.Project; +import com.intellij.spellchecker.ui.SpellCheckingEditorCustomization; +import com.intellij.ui.IdeBorderFactory; +import com.intellij.ui.TextFieldWithAutoCompletion; +import com.intellij.util.ui.AsyncProcessIcon; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import java.awt.*; +import java.awt.event.KeyEvent; +import java.util.Collection; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +public abstract class TextFieldWithProgress extends JPanel { + @NotNull private final TextFieldWithAutoCompletion myTextField; + @NotNull private final AsyncProcessIcon myProgressIcon; + + public TextFieldWithProgress(@NotNull Project project, @NotNull Collection variants) { + super(new BorderLayout()); + setBorder(IdeBorderFactory.createEmptyBorder(3)); + + myProgressIcon = new AsyncProcessIcon("Loading commits"); + myTextField = + new TextFieldWithAutoCompletion(project, new TextFieldWithAutoCompletion.StringsCompletionProvider(variants, null), false, + null) { + @Override + public void setBackground(Color bg) { + super.setBackground(bg); + myProgressIcon.setBackground(bg); + } + + @Override + protected EditorEx createEditor() { + // spell check is not needed + EditorEx editor = super.createEditor(); + SpellCheckingEditorCustomization.getInstance(false).customize(editor); + return editor; + } + + @Override + protected boolean processKeyBinding(KeyStroke ks, final KeyEvent e, int condition, boolean pressed) { + if (e.getKeyCode() == KeyEvent.VK_ENTER) { + onOk(); + return true; + } + return false; + } + }; + myTextField.setBorder(IdeBorderFactory.createEmptyBorder()); + + myProgressIcon.setOpaque(true); + myProgressIcon.setBackground(myTextField.getBackground()); + + add(myTextField, BorderLayout.CENTER); + add(myProgressIcon, BorderLayout.EAST); + + hideProgress(); + } + + public JComponent getPreferableFocusComponent() { + return myTextField; + } + + public void showProgress() { + myTextField.setEnabled(false); + myProgressIcon.setVisible(true); + } + + public void hideProgress() { + myTextField.setEnabled(true); + myProgressIcon.setVisible(false); + } + + public String getText() { + return myTextField.getText(); + } + + public abstract void onOk(); +} diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/VcsLogUiImpl.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/VcsLogUiImpl.java index 78ce9caeb2a1..7e098af05ee9 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/VcsLogUiImpl.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/VcsLogUiImpl.java @@ -1,5 +1,6 @@ package com.intellij.vcs.log.ui; +import com.google.common.util.concurrent.SettableFuture; import com.intellij.openapi.Disposable; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; @@ -32,6 +33,7 @@ import javax.swing.table.TableModel; import java.awt.*; import java.util.ArrayList; import java.util.Collection; +import java.util.concurrent.Future; public class VcsLogUiImpl implements VcsLogUi, Disposable { @@ -192,22 +194,26 @@ public class VcsLogUiImpl implements VcsLogUi, Disposable { return myUiProperties.isShowRootNames(); } - public void jumpToCommit(@NotNull Hash commitHash) { + public Future jumpToCommit(@NotNull Hash commitHash) { + SettableFuture future = SettableFuture.create(); jumpTo(commitHash, new PairFunction() { @Override public Integer fun(GraphTableModel model, Hash hash) { return model.getRowOfCommit(hash); } - }); + }, future); + return future; } - public void jumpToCommitByPartOfHash(@NotNull String commitHash) { + public Future jumpToCommitByPartOfHash(@NotNull String commitHash) { + SettableFuture future = SettableFuture.create(); jumpTo(commitHash, new PairFunction() { @Override public Integer fun(GraphTableModel model, String hash) { return model.getRowOfCommitByPartOfHash(hash); } - }); + }, future); + return future; } public void handleAnswer(@Nullable GraphAnswer answer, boolean dataCouldChange) { @@ -235,13 +241,15 @@ public class VcsLogUiImpl implements VcsLogUi, Disposable { } } - private void jumpTo(@NotNull final T commitId, @NotNull final PairFunction rowGetter) { + private void jumpTo(@NotNull final T commitId, @NotNull final PairFunction rowGetter, @NotNull final SettableFuture future) { + if (future.isCancelled()) return; + GraphTableModel model = getModel(); if (model == null) { invokeOnChange(new Runnable() { @Override public void run() { - jumpTo(commitId, rowGetter); + jumpTo(commitId, rowGetter, future); } }); return; @@ -250,12 +258,13 @@ public class VcsLogUiImpl implements VcsLogUi, Disposable { int row = rowGetter.fun(model, commitId); if (row >= 0) { myMainFrame.getGraphTable().jumpToRow(row); + future.set(true); } else if (model.canRequestMore()) { model.requestToLoadMore(new Runnable() { @Override public void run() { - jumpTo(commitId, rowGetter); + jumpTo(commitId, rowGetter, future); } }); } @@ -263,12 +272,13 @@ public class VcsLogUiImpl implements VcsLogUi, Disposable { invokeOnChange(new Runnable() { @Override public void run() { - jumpTo(commitId, rowGetter); + jumpTo(commitId, rowGetter, future); } }); } else { commitNotFound(commitId.toString()); + future.set(false); } } diff --git a/plugins/git4idea/src/git4idea/history/wholeTree/SelectRevisionInGitLogAction.java b/plugins/git4idea/src/git4idea/history/wholeTree/SelectRevisionInGitLogAction.java index 1d7db804019b..0d4c66a44f8e 100644 --- a/plugins/git4idea/src/git4idea/history/wholeTree/SelectRevisionInGitLogAction.java +++ b/plugins/git4idea/src/git4idea/history/wholeTree/SelectRevisionInGitLogAction.java @@ -2,6 +2,11 @@ package git4idea.history.wholeTree; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.CommonDataKeys; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.progress.PerformInBackgroundOption; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.MessageType; @@ -22,7 +27,12 @@ import git4idea.i18n.GitBundle; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + public class SelectRevisionInGitLogAction extends DumbAwareAction { + private static final Logger LOG = Logger.getInstance(SelectRevisionInGitLogAction.class); public SelectRevisionInGitLogAction() { super(GitBundle.getString("vcs.history.action.gitlog"), GitBundle.getString("vcs.history.action.gitlog"), null); @@ -30,7 +40,7 @@ public class SelectRevisionInGitLogAction extends DumbAwareAction { @Override public void actionPerformed(@NotNull AnActionEvent event) { - Project project = event.getRequiredData(CommonDataKeys.PROJECT); + final Project project = event.getRequiredData(CommonDataKeys.PROJECT); final VcsRevisionNumber revision = getRevisionNumber(event); if (revision == null) { return; @@ -60,7 +70,7 @@ public class SelectRevisionInGitLogAction extends DumbAwareAction { Runnable selectCommit = new Runnable() { @Override public void run() { - log.jumpToReference(revision.asString()); + jumpToRevisionUnderProgress(project, log, revision); } }; @@ -128,5 +138,25 @@ public class SelectRevisionInGitLogAction extends DumbAwareAction { return null; } - + private static void jumpToRevisionUnderProgress(@NotNull Project project, @NotNull VcsLog log, @NotNull VcsRevisionNumber revision) { + final Future future = log.jumpToReference(revision.asString()); + if (!future.isDone()) { + ProgressManager.getInstance().run(new Task.Backgroundable(project, "Searching for revision " + revision.asString(), false/*can not cancel*/, + PerformInBackgroundOption.ALWAYS_BACKGROUND) { + @Override + public void run(@NotNull ProgressIndicator indicator) { + try { + future.get(); + } + catch (CancellationException ignored) { + } + catch (InterruptedException ignored) { + } + catch (ExecutionException e) { + LOG.error(e); + } + } + }); + } + } }