blob: ac5204b0e3ed2123f5a5e079fc10d11310414c84 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2017, 2019 Stephan Wahlbrink and others.
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
# which is available at https://www.apache.org/licenses/LICENSE-2.0.
#
# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
#
# Contributors:
# Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
#=============================================================================*/
package org.eclipse.statet.internal.r.apps.ui.variables;
import static org.eclipse.ui.IWorkbenchCommandConstants.NAVIGATE_COLLAPSE_ALL;
import static org.eclipse.statet.jcommons.lang.ObjectUtils.nonNullAssert;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.IPostSelectionProvider;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ITreeSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.IViewPart;
import org.eclipse.ui.IViewSite;
import org.eclipse.ui.IWorkbenchCommandConstants;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.commands.IElementUpdater;
import org.eclipse.ui.handlers.CollapseAllHandler;
import org.eclipse.ui.handlers.IHandlerService;
import org.eclipse.ui.menus.CommandContributionItemParameter;
import org.eclipse.ui.menus.UIElement;
import org.eclipse.ui.part.ViewPart;
import org.eclipse.ui.services.IServiceLocator;
import org.eclipse.statet.jcommons.collections.CopyOnWriteIdentityListSet;
import org.eclipse.statet.jcommons.lang.NonNullByDefault;
import org.eclipse.statet.jcommons.lang.Nullable;
import org.eclipse.statet.jcommons.ts.core.ActiveToolListener;
import org.eclipse.statet.jcommons.ts.core.ActiveToolListener.ActiveToolEvent;
import org.eclipse.statet.jcommons.ts.core.Tool;
import org.eclipse.statet.jcommons.ts.core.ToolProvider;
import org.eclipse.statet.ecommons.ts.ui.workbench.WorkbenchToolRegistry;
import org.eclipse.statet.ecommons.ts.ui.workbench.WorkbenchToolRegistryListener;
import org.eclipse.statet.ecommons.ts.ui.workbench.WorkbenchToolSessionData;
import org.eclipse.statet.ecommons.ui.actions.HandlerCollection;
import org.eclipse.statet.ecommons.ui.actions.HandlerContributionItem;
import org.eclipse.statet.ecommons.ui.actions.UIActions;
import org.eclipse.statet.ecommons.ui.components.StatusInfo;
import org.eclipse.statet.ecommons.ui.dialogs.DialogUtils;
import org.eclipse.statet.ecommons.ui.util.LayoutUtils;
import org.eclipse.statet.ecommons.ui.util.UIAccess;
import org.eclipse.statet.ecommons.ui.util.ViewActionUtil;
import org.eclipse.statet.ecommons.ui.workbench.ContextHandlers;
import org.eclipse.statet.ecommons.ui.workbench.WorkbenchUIUtils;
import org.eclipse.statet.base.ui.StatetImages;
import org.eclipse.statet.internal.r.apps.ui.RAppUIPlugin;
import org.eclipse.statet.ltk.ui.sourceediting.ISourceEditorCommandIds;
import org.eclipse.statet.ltk.ui.util.ViewerDragSupport;
import org.eclipse.statet.nico.ui.NicoUI;
import org.eclipse.statet.nico.ui.actions.AbstractToolHandler;
import org.eclipse.statet.r.apps.ui.AppRegistry;
import org.eclipse.statet.r.apps.ui.AppRegistry.AppStateEvent;
import org.eclipse.statet.r.apps.ui.RApp;
import org.eclipse.statet.r.apps.ui.VariablesData;
import org.eclipse.statet.r.console.core.RConsoleTool;
import org.eclipse.statet.r.console.core.RProcess;
import org.eclipse.statet.r.console.core.RProcessREnvironment;
import org.eclipse.statet.r.ui.rtool.CopyRElementHandler;
import org.eclipse.statet.r.ui.rtool.PrintRElementHandler;
import org.eclipse.statet.r.ui.rtool.RElementViewerDragSourceListener;
import org.eclipse.statet.r.ui.util.CopyRElementNameHandler;
import org.eclipse.statet.r.ui.util.RElementInputContentProvider;
import org.eclipse.statet.r.ui.util.RElementInputLabelProvider;
import org.eclipse.statet.r.ui.util.RElementInputUtils;
import org.eclipse.statet.rj.data.RReference;
@NonNullByDefault
public class AppVarView extends ViewPart implements ToolProvider {
public static final String VIEW_ID= "org.eclipse.statet.r.apps.views.VariableViewer"; //$NON-NLS-1$
private static final String REFRESH_COMMAND_ID= IWorkbenchCommandConstants.FILE_REFRESH;
private static final String FILTER_INCLUDE_INTERNAL_COMMAND_ID= "Filter.IncludeInternal"; //$NON-NLS-1$
private static final String PRINT_COMMAND_ID= "org.eclipse.statet.r.commands.RunPrintInR"; //$NON-NLS-1$
private static final String FILTER_INCLUDE_INTERNAL_SETTINGS_KEY= "Filter.IncludeInternal.enabled"; //$NON-NLS-1$
private class RefreshHandler extends AbstractToolHandler<RProcess> {
public RefreshHandler() {
super(RConsoleTool.TYPE, null, AppVarView.this, getSite());
init();
}
@Override
protected boolean evaluateIsEnabled(final RProcess tool, final @Nullable Object evaluationContext) {
return (super.evaluateIsEnabled(tool, evaluationContext)
&& getApp() != null );
}
protected void refreshElements() {
WorkbenchUIUtils.refreshCommandElements(REFRESH_COMMAND_ID, this, null);
}
@Override
protected @Nullable Object execute(final RProcess tool, final ExecutionEvent event) {
final RApp app= getApp();
if (app != null) {
app.refreshVariables();
}
return null;
}
}
private class FilterInternalHandler extends AbstractHandler implements IElementUpdater {
@Override
public @Nullable Object execute(final ExecutionEvent event) throws ExecutionException {
AppVarView.this.filterIncludeInternal= !AppVarView.this.filterIncludeInternal;
AppVarView.this.settings.put(FILTER_INCLUDE_INTERNAL_SETTINGS_KEY, AppVarView.this.filterIncludeInternal);
updateFilter();
return null;
}
@Override
public void updateElement(final UIElement element, final Map parameters) {
WorkbenchUIUtils.aboutToUpdateCommandsElements(this, element);
try {
element.setChecked(AppVarView.this.filterIncludeInternal);
}
finally {
WorkbenchUIUtils.finalizeUpdateCommandsElements(this);
}
}
}
private IDialogSettings settings;
final Object sourceLock= new Object();
private WorkbenchToolRegistryListener toolRegistryListener;
private @Nullable RProcess process;
private final CopyOnWriteIdentityListSet<ActiveToolListener> toolListeners= new CopyOnWriteIdentityListSet<>();
private AppRegistry.Listener appRegistryListener;
private @Nullable RApp app;
private TreeViewer treeViewer;
private final ContentJob inputUpdater= new ContentJob(this);
private boolean isUpdating;
private boolean filterIncludeInternal;
private String filterText;
private RElementInputContentProvider<AppVarInput> inputContentProvider;
private ViewActionUtil actionUtil;
private ContextHandlers handlers;
private @Nullable Object currentInfoObject;
private @Nullable RApp shownByLauncher;
@SuppressWarnings("null")
public AppVarView() {
}
@Override
public void dispose() {
if (this.appRegistryListener != null) {
AppRegistry.getInstance().removeListener(this.appRegistryListener);
this.appRegistryListener= null;
}
if (this.toolRegistryListener != null) {
NicoUI.getToolRegistry().removeListener(this.toolRegistryListener);
this.toolRegistryListener= null;
}
setTool(null, false);
if (this.handlers != null) {
this.handlers.dispose();
this.handlers= null;
}
super.dispose();
}
@Override
public void init(final IViewSite site, final @Nullable IMemento memento) throws PartInitException {
super.init(site, memento);
this.settings= DialogUtils.getDialogSettings(RAppUIPlugin.getInstance(), "AppVarBrowser");
this.filterIncludeInternal= this.settings.getBoolean(FILTER_INCLUDE_INTERNAL_SETTINGS_KEY);
}
@Override
public void createPartControl(final Composite parent) {
parent.setLayout(LayoutUtils.newSashGrid());
this.treeViewer= createTreeViewer(parent);
this.treeViewer.getControl().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
final IPostSelectionProvider treeSelectionProvider= this.treeViewer;
treeSelectionProvider.addPostSelectionChangedListener(new ISelectionChangedListener() {
@Override
public void selectionChanged(final SelectionChangedEvent event) {
updateSelectionInfo((ITreeSelection) event.getSelection());
}
});
final IViewSite site= getViewSite();
site.setSelectionProvider(treeSelectionProvider);
this.actionUtil= new ViewActionUtil(this);
this.handlers= new ContextHandlers(site.getService(IHandlerService.class));
initActions(site, this.handlers);
contributeToActionBars(site, site.getActionBars(), this.handlers);
hookContextMenu();
// listen on console changes
final WorkbenchToolRegistry toolRegistry= NicoUI.getToolRegistry();
this.toolRegistryListener= new WorkbenchToolRegistryListener() {
@Override
public void toolSessionActivated(final WorkbenchToolSessionData sessionData) {
final Tool tool= sessionData.getTool();
UIAccess.getDisplay().syncExec(new Runnable() {
@Override
public void run() {
setTool(tool, true);
}
});
}
@Override
public void toolTerminated(final WorkbenchToolSessionData sessionData) {
final Tool tool= sessionData.getTool();
UIAccess.getDisplay().syncExec(new Runnable() {
@Override
public void run() {
if (tool == getTool()) {
setTool(null, true);
}
}
});
}
};
toolRegistry.addListener(this.toolRegistryListener, getViewSite().getPage());
this.appRegistryListener= new AppRegistry.Listener() {
@Override
public void onAppStateChanged(final AppStateEvent event) {
UIAccess.getDisplay(getSite().getShell()).asyncExec(() -> {
switch (event.getType()) {
case AppRegistry.APP_STARTED:
if (event.getApp().getTool() == getTool()) {
setApp(event.getApp(), true);
}
break;
case AppRegistry.APP_STOPPED:
if (event.getApp() == getApp()) {
setApp(null, true);
}
break;
default:
break;
}
});
}
};
AppRegistry.getInstance().addListener(this.appRegistryListener);
setTool(toolRegistry.getActiveToolSession(getViewSite().getPage()).getTool(), true);
}
private TreeViewer createTreeViewer(final Composite parent) {
final TreeViewer viewer= new TreeViewer(parent, SWT.V_SCROLL | SWT.H_SCROLL | SWT.MULTI);
viewer.setLabelProvider(new RElementInputLabelProvider());
viewer.setUseHashlookup(true);
this.inputContentProvider= new RElementInputContentProvider();
viewer.setContentProvider(this.inputContentProvider);
viewer.setInput(this);
return viewer;
}
protected void initActions(final IServiceLocator serviceLocator, final ContextHandlers handlers) {
handlers.addActivate(REFRESH_COMMAND_ID, new RefreshHandler());
final CopyRElementHandler copyHandler= new CopyRElementHandler(this.actionUtil,
(ILabelProvider) this.treeViewer.getLabelProvider() );
handlers.addActivate(IWorkbenchCommandConstants.EDIT_COPY, copyHandler);
handlers.addActivate(ISourceEditorCommandIds.COPY_ELEMENT_NAME,
new CopyRElementNameHandler(this.actionUtil) );
final ViewerDragSupport dragSupport= new ViewerDragSupport(this.treeViewer);
dragSupport.addDragSourceListener(new RElementViewerDragSourceListener(
copyHandler, this.treeViewer ));
dragSupport.init();
handlers.addActivate(PRINT_COMMAND_ID,
new PrintRElementHandler(this.actionUtil) );
handlers.add(FILTER_INCLUDE_INTERNAL_COMMAND_ID,
new FilterInternalHandler() );
handlers.addActivate(NAVIGATE_COLLAPSE_ALL,
new CollapseAllHandler(this.treeViewer) );
RElementInputUtils.addDoubleClickExpansion(this.treeViewer);
}
protected void contributeToActionBars(final IServiceLocator serviceLocator,
final IActionBars actionBars, final HandlerCollection handlers) {
final IMenuManager menuManager= actionBars.getMenuManager();
final IToolBarManager toolbarManager= actionBars.getToolBarManager();
menuManager.add(new HandlerContributionItem(
new CommandContributionItemParameter(serviceLocator,
null, HandlerContributionItem.NO_COMMAND_ID, null,
null, null, null,
"Show &Internal Variables ('.*')", null, null,
HandlerContributionItem.STYLE_CHECK, null, false ),
nonNullAssert(handlers.get(FILTER_INCLUDE_INTERNAL_COMMAND_ID)) ));
menuManager.add(new Separator());
menuManager.add(new HandlerContributionItem(
new CommandContributionItemParameter(serviceLocator,
"Refresh", REFRESH_COMMAND_ID, null, //$NON-NLS-1$
StatetImages.getDescriptor(StatetImages.TOOL_REFRESH), StatetImages.getDescriptor(StatetImages.TOOLD_REFRESH), null,
"&Refresh", null, null,
HandlerContributionItem.STYLE_PUSH, null, false ),
handlers ));
toolbarManager.add(new HandlerContributionItem(
new CommandContributionItemParameter(serviceLocator,
null, CollapseAllHandler.COMMAND_ID, null,
null, null, null,
null, null, null,
HandlerContributionItem.STYLE_PUSH, null, false ),
nonNullAssert(handlers.get(NAVIGATE_COLLAPSE_ALL)) ));
}
private void hookContextMenu() {
final MenuManager menuManager= new MenuManager("ContextMenu", //$NON-NLS-1$
"org.eclipse.statet.r.apps.menus.VariablesViewContextMenu" ); //$NON-NLS-1$
menuManager.setRemoveAllWhenShown(true);
menuManager.addMenuListener(this::fillContextMenu);
final Menu contextMenu= menuManager.createContextMenu(this.treeViewer.getTree());
this.treeViewer.getTree().setMenu(contextMenu);
getSite().registerContextMenu(menuManager, this.treeViewer);
}
private void fillContextMenu(final IMenuManager m) {
final IServiceLocator serviceLocator= getSite();
final ContextHandlers handlers= this.handlers;
m.add(new Separator(UIActions.EDIT_GROUP_ID));
m.add(new HandlerContributionItem(
new CommandContributionItemParameter(serviceLocator,
"Copy", IWorkbenchCommandConstants.EDIT_COPY, null, //$NON-NLS-1$
null, null, null,
null, null, null,
HandlerContributionItem.STYLE_PUSH, null, false ),
handlers ));
m.add(new HandlerContributionItem(
new CommandContributionItemParameter(serviceLocator,
"Copy.ElementName", ISourceEditorCommandIds.COPY_ELEMENT_NAME, null, //$NON-NLS-1$
null, null, null,
null, null, null,
HandlerContributionItem.STYLE_PUSH, null, false ),
handlers ));
m.add(new Separator());
m.add(new HandlerContributionItem(
new CommandContributionItemParameter(serviceLocator,
null, PRINT_COMMAND_ID, null,
null, null, null,
null, null, null,
HandlerContributionItem.STYLE_PUSH, null, false ),
handlers ));
m.add(new Separator(UIActions.ADDITIONS_GROUP_ID));
}
@Override
public void setFocus() {
this.treeViewer.getControl().setFocus();
}
/** UI thread only (called by update job) */
void updateView(final @Nullable AppVarInput input,
final @Nullable List<RProcessREnvironment> updateEnvirs) {
if (!UIAccess.isOkToUse(this.treeViewer)) {
return;
}
this.isUpdating= true;
// this.hoveringController.stop();
this.inputContentProvider.setInput(input);
final Set<RReference> previousReferences= this.inputContentProvider.resetUsedReferences();
if (input != null && updateEnvirs != null) {
for (final RProcessREnvironment entry : updateEnvirs) {
this.treeViewer.refresh(entry, true);
}
if (!previousReferences.isEmpty()) {
final Set<RReference> usedReferences= this.inputContentProvider.getUsedReferences();
ITER_REFS: for (final RReference reference : previousReferences) {
if (!usedReferences.contains(reference)) {
// Update the envir copy in the viewer, if it refers to an updated envir
for (final RProcessREnvironment entry : updateEnvirs) {
if (entry.getHandle() == reference.getHandle()) {
this.treeViewer.refresh(reference, true);
// reference is readded automatically to new set, if necessary
continue ITER_REFS;
}
}
// Keep/readd the reference, if it refers to an envir in the search path
// for (final ICombinedREnvironment entry : input.searchEnvirs) {
// if (entry.getHandle() == reference.getHandle()) {
// usedReferences.add(reference);
// continue ITER_REFS;
// }
// }
}
}
}
}
else {
this.treeViewer.refresh(true);
}
updateSelectionInfo((ITreeSelection) this.actionUtil.getSelectionProvider().getSelection());
}
private void updateFilter() {
this.inputUpdater.schedule();
}
private void clearActionInfo() {
this.actionUtil.getStatusLine().clearAll();
}
private void updateSelectionInfo(final ITreeSelection selection) {
if (this.isUpdating) {
return;
}
final Object infoObject= null;
final String message= null;
// if (tool != null && !selection.isEmpty()) {
// if (selection.size() == 1) {
// final TreePath treePath= selection.getPaths()[0];
// final IElementName elementName= getFQElementName(treePath);
// final String name= (elementName != null) ? elementName.getDisplayName() : null;
// if (name != null) {
// infoObject= selection.getFirstElement();
// message= name;
// }
// }
// else {
// message= NLS.bind("{0} items selected", selection.size());
// }
// if (message != null) {
// message= NLS.bind("{0} \u2012 {1}", message, tool.getLabel(ITool.DEFAULT_LABEL)); //$NON-NLS-1$
// }
// }
if (infoObject == null || !infoObject.equals(this.currentInfoObject)) {
clearActionInfo();
}
this.currentInfoObject= infoObject;
this.actionUtil.getStatusLine().setSelectionMessage(
(message != null) ? new StatusInfo(IStatus.OK, message) : null );
}
@Override
public @Nullable RProcess getTool() {
return this.process;
}
@Override
public void addToolListener(final ActiveToolListener action) {
this.toolListeners.add(action);
}
@Override
public void removeToolListener(final ActiveToolListener action) {
this.toolListeners.remove(action);
}
/** UI thread only */
private void setTool(final @Nullable Tool tool, final boolean update) {
final RProcess process= (tool != null
&& tool.isProvidingFeatureSet(RConsoleTool.R_DATA_FEATURESET_ID)
&& !tool.isTerminated() ) ?
(RProcess) tool : null;
if (this.process == tool) {
return;
}
final RProcess oldProcess= process;
synchronized (this.sourceLock) {
this.process= process;
}
final ActiveToolEvent event= new ActiveToolEvent(ActiveToolEvent.TOOL_ACTIVATED, process);
for (final ActiveToolListener listener : this.toolListeners) {
listener.onToolChanged(event);
}
setApp((process != null) ? AppRegistry.getInstance().getApp(process) : null, update);
}
public @Nullable RApp getApp() {
return this.app;
}
/** UI thread only */
private void setApp(@Nullable RApp app, final boolean update) {
if (app != null && app.getVariables() == null) {
app= null;
}
if (this.app == app) {
return;
}
final RApp oldApp= this.app;
if (oldApp != null) {
oldApp.removeListener(this.inputUpdater);
}
synchronized (this.sourceLock) {
this.app= app;
}
clearActionInfo();
this.inputUpdater.forceUpdate(app);
if (app != null) {
setContentDescription(computeContentDescription(app));
app.addListener(this.inputUpdater);
}
else {
setContentDescription("No app at this time.");
}
if (update) {
this.inputUpdater.schedule();
if (app == null && oldApp == this.shownByLauncher) {
Display.getCurrent().timerExec(200, () -> {
showPreviousView(oldApp);
});
}
}
}
private String computeContentDescription(final RApp app) {
final StringBuilder sb= new StringBuilder();
final VariablesData vars= app.getVariables();
if (vars != null) {
sb.append(vars.getExpression());
}
else {
sb.append("<no available>");
}
final IResource resource= app.getResource();
if (resource != null) {
if (sb.length() > 0) {
sb.append("\u2002\u2013\u2002"); //$NON-NLS-1$
}
sb.append(resource.getFullPath().toString());
}
return sb.toString();
}
public boolean getFilterIncludeInternal() {
return this.filterIncludeInternal;
}
public String getFilterSearchText() {
return this.filterText;
}
public void setShownByLauncher(final RApp app) {
this.shownByLauncher= app;
}
private void showPreviousView(final RApp oldApp) {
if (this.app == null && oldApp == this.shownByLauncher) {
final IWorkbenchPage page= getSite().getPage();
final IViewPart[] viewStack= page.getViewStack(this);
if (viewStack != null && viewStack.length >= 2 && viewStack[0] == this) {
page.bringToTop(viewStack[1]);
}
}
}
@Override
@SuppressWarnings("unchecked")
public <T> T getAdapter(final Class<T> adapterType) {
if (adapterType == Control.class) {
return (T) this.treeViewer.getTree();
}
if (adapterType == Tool.class) {
return (T) this.process;
}
return super.getAdapter(adapterType);
}
}