blob: 758fb999b5fc6b4a4e001458cab015c06689d2f4 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2007, 2021 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.cmd.ui.launching;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import org.eclipse.core.databinding.Binding;
import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.UpdateValueStrategy;
import org.eclipse.core.databinding.beans.typed.BeanProperties;
import org.eclipse.core.databinding.observable.Realm;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.databinding.observable.value.IValueChangeListener;
import org.eclipse.core.databinding.observable.value.ValueChangeEvent;
import org.eclipse.core.databinding.observable.value.WritableValue;
import org.eclipse.core.databinding.validation.IValidator;
import org.eclipse.core.databinding.validation.ValidationStatus;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.debug.ui.ILaunchConfigurationDialog;
import org.eclipse.debug.ui.ILaunchConfigurationTab;
import org.eclipse.jface.databinding.swt.typed.WidgetProperties;
import org.eclipse.jface.databinding.viewers.typed.ViewerProperties;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.TrayDialog;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.ComboViewer;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.statushandlers.StatusManager;
import org.eclipse.statet.jcommons.collections.ImCollections;
import org.eclipse.statet.jcommons.lang.Nullable;
import org.eclipse.statet.ecommons.databinding.core.validation.UpdateableErrorValidator;
import org.eclipse.statet.ecommons.debug.core.util.LaunchUtils;
import org.eclipse.statet.ecommons.debug.ui.config.InputArgumentsComposite;
import org.eclipse.statet.ecommons.debug.ui.config.LaunchConfigTabWithDbc;
import org.eclipse.statet.ecommons.debug.ui.util.HelpRequestor;
import org.eclipse.statet.ecommons.models.AbstractSettingsModelObject;
import org.eclipse.statet.ecommons.ui.SharedMessages;
import org.eclipse.statet.ecommons.ui.SharedUIResources;
import org.eclipse.statet.ecommons.ui.util.LayoutUtils;
import org.eclipse.statet.ecommons.ui.util.VariableFilterUtils;
import org.eclipse.statet.ecommons.ui.workbench.ResourceInputComposite;
import org.eclipse.statet.internal.r.console.ui.RConsoleUIPlugin;
import org.eclipse.statet.r.cmd.ui.launching.RCmdLaunching;
import org.eclipse.statet.r.core.renv.IREnvConfiguration;
import org.eclipse.statet.r.core.renv.IREnvConfiguration.Exec;
import org.eclipse.statet.r.launching.core.RLaunching;
import org.eclipse.statet.r.launching.ui.REnvTab;
/**
* Main tab to configure R CMD tool launch configs.
*/
public class RCmdMainTab extends LaunchConfigTabWithDbc {
public static final String NS= "org.eclipse.statet.r.debug/RCmd/"; //$NON-NLS-1$
private static class Cmd extends AbstractSettingsModelObject {
public final static int PACKAGE_DIR= 1;
public final static int PACKAGE_DIR_OR_ARCHIVE= 2;
public final static int DOC= 3;
public final static int DOC_OR_DIR= 4;
public final static int CUSTOM= 5;
private final String name;
private String command;
private final int type;
public Cmd(final String name, final String command, final int type) {
this.name= name;
this.command= command;
this.type= type;
}
public String getName() {
return this.name;
}
public int getType() {
return this.type;
}
public void setCommand(final String command) {
this.command= command.trim();
}
public String getCommand() {
return this.command;
}
}
private Cmd[] commands;
private Cmd customCommand;
private final IObservableValue<Cmd> cmdValue;
private final IObservableValue<String> argumentsValue;
private final IObservableValue<String> resourceValue;
private final IObservableValue<String> workingDirectoryValue;
private ComboViewer cmdCombo;
private Text cmdText;
private Button helpButton;
private InputArgumentsComposite argumentsControl;
private ResourceInputComposite resourceControl;
private ResourceInputComposite workingDirectoryControl;
boolean withHelp= false;
private ILaunchConfigurationTab rEnvTab;
private ILaunchConfiguration configCache;
public RCmdMainTab() {
super();
createCommands();
final Realm realm= getRealm();
this.cmdValue= new WritableValue<>(realm, null, Cmd.class);
this.argumentsValue= new WritableValue<>(realm, null, String.class);
this.resourceValue= new WritableValue<>(realm, null, String.class);
this.workingDirectoryValue= new WritableValue<>(realm, null, String.class);
}
private void createCommands() {
final List<Cmd> commands= new ArrayList<>();
commands.add(new Cmd(Messages.RCmd_CmdCheck_name, "CMD check", Cmd.PACKAGE_DIR_OR_ARCHIVE)); //$NON-NLS-1$
commands.add(new Cmd(Messages.RCmd_CmdBuild_name, "CMD build", Cmd.PACKAGE_DIR)); //$NON-NLS-1$
commands.add(new Cmd(Messages.RCmd_CmdInstall_name, "CMD INSTALL", Cmd.PACKAGE_DIR_OR_ARCHIVE)); //$NON-NLS-1$
commands.add(new Cmd(Messages.RCmd_CmdRemove_name, "CMD REMOVE", Cmd.PACKAGE_DIR)); //$NON-NLS-1$
commands.add(new Cmd(Messages.RCmd_CmdRdconv_name, "CMD Rdconv", Cmd.DOC)); //$NON-NLS-1$
commands.add(new Cmd(Messages.RCmd_CmdRd2dvi_name, "CMD Rd2dvi", Cmd.DOC_OR_DIR)); //$NON-NLS-1$
commands.add(new Cmd(Messages.RCmd_CmdRd2txt_name, "CMD Rd2txt", Cmd.DOC)); //$NON-NLS-1$
commands.add(new Cmd(Messages.RCmd_CmdSd2Rd_name, "CMD Sd2Rd", Cmd.DOC)); //$NON-NLS-1$
commands.add(new Cmd(Messages.RCmd_CmdRoxygen_name, "CMD roxygen", Cmd.PACKAGE_DIR)); //$NON-NLS-1$
commands.add(new Cmd(Messages.RCmd_CmdSweave_name, "CMD Sweave", Cmd.DOC)); //$NON-NLS-1$
this.customCommand= new Cmd(Messages.RCmd_CmdOther_name, "", Cmd.CUSTOM); //$NON-NLS-1$
commands.add(this.customCommand);
this.commands= commands.toArray(new Cmd[commands.size()]);
resetCommands();
}
private void resetCommands() {
this.customCommand.command= "CMD "; //$NON-NLS-1$
}
@Override
public String getName() {
return Messages.MainTab_name;
}
@Override
public Image getImage() {
return SharedUIResources.getImages().get(SharedUIResources.OBJ_MAIN_TAB_ID);
}
@Override
public void createControl(final Composite parent) {
final Composite mainComposite= new Composite(parent, SWT.NONE);
setControl(mainComposite);
mainComposite.setLayout(LayoutUtils.newTabGrid(1));
Group group;
group= new Group(mainComposite, SWT.NONE);
group.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
group.setText(Messages.MainTab_Cmd_label);
createCommandControls(group);
{ final Composite composite= createWorkingDirectoryGroup(mainComposite);
composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
}
{ final Label note= new Label(mainComposite, SWT.WRAP);
note.setText(SharedMessages.Note_label + ": " + this.argumentsControl.getNoteText()); //$NON-NLS-1$
note.setLayoutData(new GridData(SWT.FILL, SWT.BOTTOM, true, true));
}
Dialog.applyDialogFont(parent);
initBindings();
}
private Composite createWorkingDirectoryGroup(final Composite parent) {
final ResourceInputComposite control= new ResourceInputComposite(parent,
ResourceInputComposite.STYLE_GROUP | ResourceInputComposite.STYLE_TEXT,
ResourceInputComposite.MODE_DIRECTORY | ResourceInputComposite.MODE_OPEN,
Messages.MainTab_WorkingDir_label);
control.setShowInsertVariable(true,
VariableFilterUtils.DEFAULT_INTERACTIVE_FILTERS, null );
this.workingDirectoryControl= control;
return control;
}
private void createCommandControls(final Composite container) {
for (final ILaunchConfigurationTab tab : getLaunchConfigurationDialog().getTabs()) {
if (tab instanceof REnvTab) {
this.rEnvTab= tab;
break;
}
}
this.withHelp= (this.rEnvTab != null) && (getLaunchConfigurationDialog() instanceof TrayDialog);
container.setLayout(LayoutUtils.newGroupGrid(3));
final String[] names= new String[this.commands.length];
for (int i= 0; i < this.commands.length; i++) {
names[i]= this.commands[i].getName();
}
this.cmdCombo= new ComboViewer(container, SWT.DROP_DOWN | SWT.READ_ONLY);
this.cmdCombo.setContentProvider(new ArrayContentProvider());
this.cmdCombo.setLabelProvider(new LabelProvider() {
@Override
public String getText(final Object element) {
final Cmd cmd= (Cmd) element;
return cmd.getName();
}
});
this.cmdCombo.setInput(this.commands);
this.cmdCombo.getCombo().setVisibleItemCount(names.length);
this.cmdCombo.getControl().setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
this.cmdText= new Text(container, SWT.BORDER | SWT.SINGLE);
this.cmdText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, this.withHelp ? 1 : 2, 1));
if (this.withHelp) {
this.helpButton= new Button(container, SWT.PUSH);
this.helpButton.setText(Messages.MainTab_RunHelp_label);
this.helpButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(final SelectionEvent e) {
queryHelp();
}
});
final GridData gd= new GridData(SWT.FILL, SWT.CENTER, false, false);
gd.widthHint= LayoutUtils.hintWidth(this.helpButton);
this.helpButton.setLayoutData(gd);
}
LayoutUtils.addSmallFiller(container, false);
this.argumentsControl= new InputArgumentsComposite(container);
this.argumentsControl.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 3, 1));
this.resourceControl= new ResourceInputComposite(container,
ResourceInputComposite.STYLE_LABEL | ResourceInputComposite.STYLE_TEXT,
ResourceInputComposite.MODE_FILE | ResourceInputComposite.MODE_OPEN,
"" ); //$NON-NLS-1$
this.resourceControl.setShowInsertVariable(true,
ImCollections.newList(VariableFilterUtils.EXCLUDE_JAVA_FILTER),
null );
this.resourceControl.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 3, 1));
}
@Override
protected void addBindings(final DataBindingContext dbc) {
final IObservableValue<Cmd> cmdSelection= ViewerProperties.singleSelection(Cmd.class)
.observe(this.cmdCombo);
dbc.bindValue(
cmdSelection,
this.cmdValue );
final IValidator<@Nullable String> cmdValidator= (final @Nullable String value) -> {
if (value == null || value.trim().isEmpty()) {
return ValidationStatus.warning(Messages.MainTab_error_MissingCMD_message);
}
return ValidationStatus.ok();
};
dbc.bindValue(
WidgetProperties.text(SWT.Modify)
.observe(this.cmdText),
BeanProperties.value(Cmd.class, "command", String.class) //$NON-NLS-1$
.observeDetail(cmdSelection),
new UpdateValueStrategy<String, String>()
.setAfterGetValidator(cmdValidator),
new UpdateValueStrategy<String, String>()
.setBeforeSetValidator(cmdValidator) );
dbc.bindValue(
WidgetProperties.text(SWT.Modify)
.observe(this.argumentsControl.getTextControl()),
this.argumentsValue );
this.resourceControl.getValidator().setOnLateResolve(IStatus.WARNING);
this.resourceControl.getValidator().setOnEmpty(IStatus.OK);
this.resourceControl.getValidator().setIgnoreRelative(true);
final Binding resourceBinding= dbc.bindValue(
this.resourceControl.getObservable(),
this.resourceValue,
new UpdateValueStrategy<String, String>()
.setAfterGetValidator(new UpdateableErrorValidator<>(
this.resourceControl.getValidator() )),
null );
cmdSelection.addValueChangeListener(new IValueChangeListener<Cmd>() {
@Override
public void handleValueChange(final ValueChangeEvent<? extends Cmd> event) {
final Cmd cmd= event.diff.getNewValue();
if (cmd != null) {
RCmdMainTab.this.cmdText.setEditable(cmd.getType() == Cmd.CUSTOM);
String label;
int mode= 0;
switch (cmd.getType()) {
case Cmd.PACKAGE_DIR:
label= Messages.MainTab_Resource_PackageDir_label;
mode= ResourceInputComposite.MODE_DIRECTORY;
break;
case Cmd.PACKAGE_DIR_OR_ARCHIVE:
label= Messages.MainTab_Resource_PackageDirOrArchive_label;
mode= ResourceInputComposite.MODE_FILE | ResourceInputComposite.MODE_DIRECTORY;
break;
case Cmd.DOC:
label= Messages.MainTab_Resource_Doc_label;
mode= ResourceInputComposite.MODE_FILE;
break;
case Cmd.DOC_OR_DIR:
label= Messages.MainTab_Resource_DocOrDir_label;
mode= ResourceInputComposite.MODE_FILE | ResourceInputComposite.MODE_DIRECTORY;
break;
default: // Cmd.CUSTOM:
label= Messages.MainTab_Resource_Other_label;
mode= ResourceInputComposite.MODE_FILE | ResourceInputComposite.MODE_DIRECTORY;
break;
}
RCmdMainTab.this.resourceControl.setResourceLabel(label);
RCmdMainTab.this.resourceControl.setMode(mode | ResourceInputComposite.MODE_OPEN);
resourceBinding.validateTargetToModel();
}
}
});
this.workingDirectoryControl.getValidator().setOnEmpty(IStatus.OK);
dbc.bindValue(
this.workingDirectoryControl.getObservable(),
this.workingDirectoryValue,
new UpdateValueStrategy<String, String>()
.setAfterGetValidator(new UpdateableErrorValidator<>(
this.workingDirectoryControl.getValidator() )),
null );
}
@Override
public void setDefaults(final ILaunchConfigurationWorkingCopy configuration) {
configuration.setAttribute(RCmdLaunching.R_CMD_COMMAND_ATTR_NAME, this.commands[0].getCommand());
configuration.setAttribute(RCmdLaunching.R_CMD_OPTIONS_ATTR_NAME, ""); //$NON-NLS-1$
configuration.setAttribute(RCmdLaunching.R_CMD_RESOURCE_ATTR_NAME, "${resource_loc}"); //$NON-NLS-1$
REnvTab.setWorkingDirectory(configuration, ""); //$NON-NLS-1$
}
@Override
protected void doInitialize(final ILaunchConfiguration configuration) {
resetCommands();
{ final String command= readAttribute(configuration,
RCmdLaunching.R_CMD_COMMAND_ATTR_NAME,
"" ); //$NON-NLS-1$
Cmd cmd= null;
for (final Cmd candidate : this.commands) {
if (candidate.getCommand().equals(command)) {
cmd= candidate;
break;
}
}
if (cmd == null) {
this.customCommand.setCommand(command);
cmd= this.customCommand;
}
this.cmdValue.setValue(cmd);
}
this.argumentsValue.setValue(readAttribute(configuration,
RCmdLaunching.R_CMD_OPTIONS_ATTR_NAME,
"" )); //$NON-NLS-1$
this.resourceValue.setValue(readAttribute(configuration,
RCmdLaunching.R_CMD_RESOURCE_ATTR_NAME,
"" )); //$NON-NLS-1$
{ String value;
try {
value= REnvTab.readWorkingDirectory(configuration);
}
catch (final CoreException e) {
value= ""; //$NON-NLS-1$
logReadingError(e);
}
this.workingDirectoryValue.setValue(value);
}
checkHelp(configuration);
}
@Override
public void activated(final ILaunchConfigurationWorkingCopy workingCopy) {
checkHelp(workingCopy);
super.activated(workingCopy);
}
private void checkHelp(final ILaunchConfiguration configuration) {
this.configCache= configuration;
if (this.withHelp) {
this.helpButton.setEnabled(this.rEnvTab.isValid(this.configCache));
}
}
@Override
protected void doSave(final ILaunchConfigurationWorkingCopy configuration) {
configuration.setAttribute(RCmdLaunching.R_CMD_COMMAND_ATTR_NAME, this.cmdValue.getValue().getCommand());
configuration.setAttribute(RCmdLaunching.R_CMD_OPTIONS_ATTR_NAME, this.argumentsValue.getValue());
configuration.setAttribute(RCmdLaunching.R_CMD_RESOURCE_ATTR_NAME, this.resourceValue.getValue());
REnvTab.setWorkingDirectory(configuration, this.workingDirectoryValue.getValue());
}
private void queryHelp() {
if (!this.withHelp) {
return;
}
try {
final List<String> cmdLine= new ArrayList<>();
final ILaunchConfigurationDialog dialog= getLaunchConfigurationDialog();
// r env
final IREnvConfiguration renv= RLaunching.getREnvConfig(this.configCache, true);
final String cmd= this.cmdValue.getValue().getCommand().trim();
if (cmd.length() != 0) {
cmdLine.addAll(Arrays.asList(cmd.split(" "))); //$NON-NLS-1$
}
String arg1= null;
if (cmdLine.size() > 0) {
arg1= cmdLine.remove(0);
}
cmdLine.addAll(0, renv.getExecCommand(arg1, EnumSet.of(Exec.CMD)));
cmdLine.add("--help"); //$NON-NLS-1$
final ProcessBuilder processBuilder= new ProcessBuilder(cmdLine);
final HelpRequestor helper= new HelpRequestor(processBuilder, (TrayDialog) dialog);
final Map<String, String> envp= processBuilder.environment();
LaunchUtils.configureEnvironment(envp, this.configCache, renv.getEnvironmentsVariables());
dialog.run(true, true, helper);
updateLaunchConfigurationDialog();
}
catch (final CoreException e) {
StatusManager.getManager().handle(new Status(IStatus.ERROR, RConsoleUIPlugin.BUNDLE_ID, -1,
Messages.MainTab_error_CannotRunHelp_message, e ),
StatusManager.LOG | StatusManager.SHOW);
}
catch (final InvocationTargetException e) {
StatusManager.getManager().handle(new Status(IStatus.ERROR, RConsoleUIPlugin.BUNDLE_ID, -1,
Messages.MainTab_error_WhileRunningHelp_message, e.getTargetException()),
StatusManager.LOG | StatusManager.SHOW);
}
catch (final InterruptedException e) {
}
}
@Override
public void dispose() {
if (this.withHelp) {
HelpRequestor.closeHelpTray((TrayDialog) getLaunchConfigurationDialog());
}
super.dispose();
}
}