[vcs-log] show progress in find by hash popup while loading log IDEA-134749

This commit is contained in:
Julia Beliaeva
2015-01-14 16:09:40 +03:00
parent 8a29094361
commit 2394bc2830
8 changed files with 293 additions and 114 deletions
@@ -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<Boolean> jumpToReference(String reference);
/**
* Returns the VCS log toolbar component.
@@ -68,5 +69,4 @@ public interface VcsLog {
*/
@NotNull
Collection<VcsLogProvider> getLogProviders();
}
@@ -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<Boolean> jumpToReference(final String reference) {
Collection<VcsRef> references = getAllReferences();
VcsRef ref = ContainerUtil.find(references, new Condition<VcsRef>() {
@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);
}
}
@@ -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<String, Future> myFunction;
@NotNull private final JBPopup myPopup;
@Nullable private Future myFuture;
public FindPopupWithProgress(@NotNull final Project project,
@NotNull Collection<String> variants,
@NotNull Function<String, Future> 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);
}
}
@@ -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<String, Future>() {
@Override
public Future fun(String text) {
return log.jumpToReference(text);
}
});
popup.showUnderneathOf(log.getToolbar());
}
@@ -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<String> {
@Nullable private JBPopup myPopup;
public PopupWithTextFieldWithAutoCompletion(@NotNull Project project, @NotNull Collection<String> 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;
}
}
@@ -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<String> myTextField;
@NotNull private final AsyncProcessIcon myProgressIcon;
public TextFieldWithProgress(@NotNull Project project, @NotNull Collection<String> variants) {
super(new BorderLayout());
setBorder(IdeBorderFactory.createEmptyBorder(3));
myProgressIcon = new AsyncProcessIcon("Loading commits");
myTextField =
new TextFieldWithAutoCompletion<String>(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();
}
@@ -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<Boolean> jumpToCommit(@NotNull Hash commitHash) {
SettableFuture<Boolean> future = SettableFuture.create();
jumpTo(commitHash, new PairFunction<GraphTableModel, Hash, Integer>() {
@Override
public Integer fun(GraphTableModel model, Hash hash) {
return model.getRowOfCommit(hash);
}
});
}, future);
return future;
}
public void jumpToCommitByPartOfHash(@NotNull String commitHash) {
public Future<Boolean> jumpToCommitByPartOfHash(@NotNull String commitHash) {
SettableFuture<Boolean> future = SettableFuture.create();
jumpTo(commitHash, new PairFunction<GraphTableModel, String, Integer>() {
@Override
public Integer fun(GraphTableModel model, String hash) {
return model.getRowOfCommitByPartOfHash(hash);
}
});
}, future);
return future;
}
public void handleAnswer(@Nullable GraphAnswer<Integer> answer, boolean dataCouldChange) {
@@ -235,13 +241,15 @@ public class VcsLogUiImpl implements VcsLogUi, Disposable {
}
}
private <T> void jumpTo(@NotNull final T commitId, @NotNull final PairFunction<GraphTableModel, T, Integer> rowGetter) {
private <T> void jumpTo(@NotNull final T commitId, @NotNull final PairFunction<GraphTableModel, T, Integer> rowGetter, @NotNull final SettableFuture<Boolean> 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);
}
}
@@ -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<Boolean> 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);
}
}
});
}
}
}