| /*=============================================================================# |
| # Copyright (c) 2008, 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.r.launching; |
| |
| import java.lang.reflect.InvocationTargetException; |
| import java.net.URI; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| |
| import org.eclipse.core.filesystem.EFS; |
| import org.eclipse.core.filesystem.IFileStore; |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.resources.IMarker; |
| import org.eclipse.core.resources.IProject; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IAdaptable; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.variables.IDynamicVariable; |
| import org.eclipse.core.variables.IStringVariable; |
| import org.eclipse.debug.core.DebugPlugin; |
| import org.eclipse.debug.core.IStatusHandler; |
| import org.eclipse.debug.ui.IDebugUIConstants; |
| import org.eclipse.jface.text.AbstractDocument; |
| import org.eclipse.jface.text.BadLocationException; |
| import org.eclipse.ui.ISharedImages; |
| import org.eclipse.ui.PlatformUI; |
| import org.eclipse.ui.statushandlers.StatusManager; |
| |
| import org.eclipse.statet.jcommons.collections.ImCollections; |
| import org.eclipse.statet.jcommons.status.Status; |
| import org.eclipse.statet.jcommons.status.StatusException; |
| import org.eclipse.statet.jcommons.status.eplatform.EStatusUtils; |
| import org.eclipse.statet.jcommons.ts.core.ToolRunnable; |
| |
| import org.eclipse.statet.ecommons.preferences.core.Preference.BooleanPref; |
| import org.eclipse.statet.ecommons.preferences.core.util.PreferenceUtils; |
| import org.eclipse.statet.ecommons.text.TextUtil; |
| import org.eclipse.statet.ecommons.variables.core.DynamicVariable; |
| import org.eclipse.statet.ecommons.variables.core.StringVariable; |
| import org.eclipse.statet.ecommons.variables.core.VariableText; |
| import org.eclipse.statet.ecommons.variables.core.VariableText.LocationProcessor; |
| |
| import org.eclipse.statet.internal.r.debug.ui.RControllerCodeLaunchConnector; |
| import org.eclipse.statet.internal.r.debug.ui.RLaunchingMessages; |
| import org.eclipse.statet.internal.r.debug.ui.launcher.RCodeLaunchRegistry; |
| import org.eclipse.statet.internal.r.debug.ui.launcher.RCodeLaunchRegistry.ContentHandler.FileCommand; |
| import org.eclipse.statet.ltk.model.core.element.SourceUnit; |
| import org.eclipse.statet.ltk.model.core.element.WorkspaceSourceUnit; |
| import org.eclipse.statet.nico.core.runtime.ToolController; |
| import org.eclipse.statet.nico.core.runtime.ToolWorkspace; |
| import org.eclipse.statet.r.console.core.AbstractRController; |
| import org.eclipse.statet.r.core.RUtil; |
| import org.eclipse.statet.r.core.model.RLangSourceElement; |
| import org.eclipse.statet.r.core.model.RSourceUnit; |
| import org.eclipse.statet.r.core.model.RWorkspaceSourceUnit; |
| import org.eclipse.statet.r.core.rsource.ast.GenericVisitor; |
| import org.eclipse.statet.r.core.rsource.ast.RAstNode; |
| import org.eclipse.statet.r.nico.IRModelSrcref; |
| import org.eclipse.statet.r.ui.RUI; |
| |
| |
| /** |
| * Provides methods to submit code to R |
| * |
| * The methods use the code launch connector selected in the preferences |
| * and therefore it supports external consoles too |
| * (in contrast to direct usage of new {@link AbstractRController}). |
| */ |
| public final class RCodeLaunching { |
| |
| |
| public static final String SUBMIT_SELECTION_COMMAND_ID = "org.eclipse.statet.r.commands.SubmitSelectionToR"; //$NON-NLS-1$ |
| |
| public static final String SUBMIT_SELECTION_GOTOCONSOLE_COMMAND_ID = "org.eclipse.statet.r.commands.SubmitSelectionToR_GotoConsole"; //$NON-NLS-1$ |
| |
| public static final String SUBMIT_SELECTION_PASTEOUTPUT_COMMAND_ID = "org.eclipse.statet.r.commands.SubmitSelectionToR_PasteOutput"; //$NON-NLS-1$ |
| |
| public static final String SUBMIT_UPTO_SELECTION_COMMAND_ID = "org.eclipse.statet.r.commands.SubmitUptoSelectionToR"; //$NON-NLS-1$ |
| |
| public static final String SUBMIT_FILEVIACOMMAND_COMMAND_ID = "org.eclipse.statet.r.commands.SubmitFileToRViaCommand"; //$NON-NLS-1$ |
| |
| public static final String SUBMIT_FILEVIACOMMAND_GOTOCONSOLE_COMMAND_ID = "org.eclipse.statet.r.commands.SubmitFileToRViaCommand_GotoConsole"; //$NON-NLS-1$ |
| |
| |
| public static final String FILE_COMMAND_ID_PARAMTER_ID = "fileCommandId"; //$NON-NLS-1$ |
| |
| |
| private static final IStringVariable FILE_NAME_VARIABLE = new StringVariable("resource_loc", "The complete path of the source file"); //$NON-NLS-1$ |
| private static final IStringVariable FILE_ENCODING_VARIABLE = new StringVariable("resource_encoding", "The encoding of the source file"); //$NON-NLS-1$ |
| private static final IStringVariable ECHO_ENABLED_VARIABLE = new StringVariable("echo", "If echo is enabled"); //$NON-NLS-1$ |
| |
| private static final IStatus STATUS_PROMPTER = new org.eclipse.core.runtime.Status(IStatus.INFO, IDebugUIConstants.PLUGIN_ID, 200, "", null); //$NON-NLS-1$ |
| private static final IStatus STATUS_SAVE = new org.eclipse.core.runtime.Status(IStatus.INFO, DebugPlugin.getUniqueIdentifier(), 222, "", null); //$NON-NLS-1$ |
| |
| |
| public static class SourceRegion implements IRModelSrcref, IAdaptable { |
| |
| private static final String MARKER_TYPE = "org.eclipse.core.resources.marker"; |
| |
| private static class ElementSearcher extends GenericVisitor { |
| |
| |
| private List<RLangSourceElement> fList; |
| |
| |
| @Override |
| public void visitNode(final RAstNode node) throws InvocationTargetException { |
| final List<Object> attachments= node.getAttachments(); |
| for (final Object attachment : attachments) { |
| if (attachment instanceof RLangSourceElement) { |
| if (fList == null) { |
| fList= new ArrayList<>(); |
| } |
| fList.add((RLangSourceElement) attachment); |
| } |
| } |
| if (fList == null) { |
| super.visitNode(node); |
| } |
| } |
| |
| } |
| |
| |
| private final RSourceUnit fSourceUnit; |
| private RAstNode fNode; |
| private List<RLangSourceElement> fElements; |
| |
| private final AbstractDocument fDocument; |
| |
| private int fBeginOffset = -1; |
| private int fBeginLine = -1; |
| private int fBeginColumn = -1; |
| private int fEndOffset = -1; |
| private int fEndLine = -1; |
| private int fEndColumn = -1; |
| |
| private String fCode; |
| |
| private IMarker fMarker; |
| |
| |
| public SourceRegion(final RSourceUnit file, final AbstractDocument document) { |
| fSourceUnit = file; |
| fDocument = document; |
| } |
| |
| |
| @Override |
| public RSourceUnit getFile() { |
| return fSourceUnit; |
| } |
| |
| public void setNode(final RAstNode node) { |
| fNode = node; |
| } |
| |
| @Override |
| public List<RLangSourceElement> getElements() { |
| if (fElements == null) { |
| if (fNode != null) { |
| final ElementSearcher searcher = new ElementSearcher(); |
| try { |
| searcher.visitNode(fNode); |
| fElements = searcher.fList; |
| } |
| catch (final InvocationTargetException e) {} |
| } |
| if (fElements == null) { |
| fElements = Collections.emptyList(); |
| } |
| } |
| return fElements; |
| } |
| |
| |
| public void setCode(final String code) { |
| fCode = code; |
| } |
| |
| public void setBegin(final int offset) throws BadLocationException { |
| fBeginOffset = offset; |
| fBeginLine = fDocument.getLineOfOffset(fBeginOffset); |
| fBeginColumn = TextUtil.getColumn(fDocument, fBeginOffset, fBeginLine, 8); |
| } |
| |
| @Override |
| public boolean hasBeginDetail() { |
| return (fBeginLine >= 0 && fBeginColumn >= 0); |
| } |
| |
| @Override |
| public int getOffset() { |
| return fBeginOffset; |
| } |
| |
| @Override |
| public int getFirstLine() { |
| return fBeginLine; |
| } |
| |
| @Override |
| public int getFirstColumn() { |
| return fBeginColumn; |
| } |
| |
| public void setEnd(final int offset) throws BadLocationException { |
| fEndOffset = offset; |
| fEndLine = fDocument.getLineOfOffset(fEndOffset); |
| fEndColumn = TextUtil.getColumn(fDocument, fEndOffset-1, fEndLine, 8); |
| } |
| |
| @Override |
| public boolean hasEndDetail() { |
| return (fEndLine >= 0 && fEndColumn >= 0); |
| } |
| |
| @Override |
| public int getLength() { |
| return fEndOffset-fBeginOffset; |
| } |
| |
| @Override |
| public int getLastLine() { |
| return fEndLine; |
| } |
| |
| @Override |
| public int getLastColumn() { |
| return fEndColumn; |
| } |
| |
| |
| void installMarker() { |
| if (fSourceUnit instanceof RWorkspaceSourceUnit) { |
| final IResource resource = ((RWorkspaceSourceUnit) fSourceUnit).getResource(); |
| try { |
| fMarker = resource.createMarker(MARKER_TYPE); |
| fMarker.setAttribute(IMarker.CHAR_START, fBeginOffset); |
| fMarker.setAttribute(IMarker.CHAR_END, fEndOffset); |
| } |
| catch (final CoreException e) { |
| StatusManager.getManager().handle(new org.eclipse.core.runtime.Status(IStatus.ERROR, RUI.BUNDLE_ID, 0, |
| "An error occurred when creating code position marker.", e)); |
| } |
| } |
| } |
| |
| void disposeMarker() { |
| if (this.fMarker != null) { |
| try { |
| this.fMarker.delete(); |
| this.fMarker = null; |
| } |
| catch (final CoreException e) { |
| StatusManager.getManager().handle(new org.eclipse.core.runtime.Status(IStatus.ERROR, RUI.BUNDLE_ID, 0, |
| "An error occurred when removing code position marker.", e)); |
| } |
| } |
| } |
| |
| @Override |
| public <T> T getAdapter(final Class<T> adapterType) { |
| if (adapterType == IMarker.class) { |
| return (T) this.fMarker; |
| } |
| return null; |
| } |
| |
| } |
| |
| |
| public static void gotoRConsole() throws CoreException { |
| final IRCodeSubmitConnector connector = RCodeLaunchRegistry.getDefault().getConnector(); |
| |
| connector.gotoConsole(); |
| } |
| |
| public static String getFileCommand(final String id) { |
| final FileCommand fileCommand = RCodeLaunchRegistry.getDefault().getFileCommand(id); |
| if (fileCommand != null) { |
| return fileCommand.getCurrentCommand(); |
| } |
| return null; |
| } |
| |
| public static String getPreferredFileCommand(final String contentType) { |
| final FileCommand fileCommand = RCodeLaunchRegistry.getDefault().getContentFileCommand(contentType); |
| return fileCommand.getCurrentCommand(); |
| } |
| |
| public static ICodeSubmitContentHandler getCodeSubmitContentHandler(final String contentType) { |
| return RCodeLaunchRegistry.getDefault().getContentHandler(contentType); |
| } |
| |
| |
| /** |
| * Runs a file related command in R. |
| * Use this method only, if you don't have an IFile or IPath object for your file |
| * (e.g. file on webserver). |
| * <p> |
| * The pattern ${file} in command string is replaced by the path of |
| * the specified file.</p> |
| * |
| * @param command the command, (at moment) should be single line. |
| * @param file the file. |
| * @throws CoreException if running failed. |
| */ |
| public static void runFileUsingCommand(final String command, final URI fileURI, |
| final SourceUnit su, final String encoding, final boolean gotoConsole) throws CoreException { |
| if (su instanceof WorkspaceSourceUnit && su.getResource() instanceof IFile) { |
| final IFile file = (IFile) ((WorkspaceSourceUnit) su).getResource(); |
| |
| // save files |
| final IProject project = file.getProject(); |
| if (project != null) { |
| final IProject[] referencedProjects = project.getReferencedProjects(); |
| final IProject[] allProjects = new IProject[referencedProjects.length+1]; |
| allProjects[0] = project; |
| System.arraycopy(referencedProjects, 0, allProjects, 1, referencedProjects.length); |
| if (!saveBeforeLaunch(allProjects)) { |
| return; |
| } |
| } |
| } |
| |
| final IRCodeSubmitConnector connector = RCodeLaunchRegistry.getDefault().getConnector(); |
| IFileStore fileStore = null; |
| try { |
| fileStore = EFS.getStore(fileURI); |
| } |
| catch (final CoreException e) { |
| fileStore = null; |
| } |
| |
| if (fileStore != null && connector instanceof RControllerCodeLaunchConnector) { |
| final IFileStore store = fileStore; |
| ((RControllerCodeLaunchConnector) connector).submit(new RControllerCodeLaunchConnector.CommandsCreator() { |
| @Override |
| public Status submitTo(final ToolController controller) { |
| final ToolWorkspace workspace = controller.getWorkspaceData(); |
| try { |
| final String path = workspace.toToolPath(store); |
| final String code = resolveVariables(command, path, encoding); |
| final ToolRunnable runnable = new SubmitFileViaCommandRunnable( |
| PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJ_FILE), |
| "Run '" + store.toString() + "'", // TODO custom image and label |
| code, su ); |
| final Status status = controller.getTool().getQueue().add(runnable); |
| if (status.getSeverity() != Status.OK) { |
| runnable.changed(ToolRunnable.BEING_ABANDONED, null); |
| } |
| return status; |
| } |
| catch (final StatusException e) { |
| return e.getStatus(); |
| } |
| catch (final CoreException e) { |
| return EStatusUtils.convert(e.getStatus()); |
| } |
| } |
| }, gotoConsole); |
| } |
| else { |
| String path = null; |
| try { |
| if (EFS.getLocalFileSystem().equals(EFS.getFileSystem(fileURI.getScheme()))) { |
| path = EFS.getLocalFileSystem().getStore(fileURI).toString(); |
| } |
| } catch (final CoreException e) { |
| } |
| if (path == null) { |
| path = fileURI.toString(); |
| } |
| final String code = resolveVariables(command, path, encoding); |
| connector.submit(Collections.singletonList(code), gotoConsole); |
| } |
| } |
| |
| private static String resolveVariables(final String command, final String path, final String encoding) |
| throws CoreException { |
| final List<IDynamicVariable> variables = new ArrayList<>(); |
| variables.add(new DynamicVariable(FILE_NAME_VARIABLE) { |
| @Override |
| public String getValue(final String argument) throws CoreException { |
| return RUtil.escapeCompletely(path); |
| } |
| }); |
| variables.add(new DynamicVariable(FILE_ENCODING_VARIABLE) { |
| @Override |
| public String getValue(final String argument) throws CoreException { |
| return encoding != null ? RUtil.escapeCompletely(encoding) : "unknown"; |
| } |
| }); |
| variables.add(new DynamicVariable(ECHO_ENABLED_VARIABLE) { |
| @Override |
| public String getValue(final String argument) throws CoreException { |
| final Boolean echo = PreferenceUtils.getInstancePrefs().getPreferenceValue( |
| RCodeLaunching.ECHO_ENABLED_PREF); |
| return (echo != null && echo.booleanValue()) ? |
| "TRUE" : "FALSE"; //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| }); |
| final VariableText text = new VariableText(command, variables, true); |
| text.performFinalStringSubstitution(new LocationProcessor() { |
| @Override |
| public String process(final String path) throws CoreException { |
| return RUtil.escapeCompletely(path); |
| } |
| }); |
| return text.getText(); |
| } |
| |
| private static boolean saveBeforeLaunch(final IProject[] projects) throws CoreException { |
| IStatusHandler prompter = null; |
| prompter = DebugPlugin.getDefault().getStatusHandler(STATUS_PROMPTER); |
| if (prompter != null) { |
| return ((Boolean) prompter.handleStatus(STATUS_SAVE, |
| new Object[] { null, projects } )).booleanValue(); |
| } |
| return true; |
| } |
| |
| public static boolean runRCodeDirect(final List<String> lines, final boolean gotoConsole, |
| final IProgressMonitor monitor) throws CoreException { |
| if (monitor != null) { |
| monitor.subTask(RLaunchingMessages.RCodeLaunch_SubmitCode_task); |
| } |
| final IRCodeSubmitConnector connector = RCodeLaunchRegistry.getDefault().getConnector(); |
| |
| return connector.submit(lines, gotoConsole); |
| } |
| |
| public static boolean runRCodeDirect(final List<SourceRegion> codeRegions, |
| final boolean gotoConsole) throws CoreException { |
| final IRCodeSubmitConnector connector = RCodeLaunchRegistry.getDefault().getConnector(); |
| if (connector instanceof RControllerCodeLaunchConnector) { |
| return ((RControllerCodeLaunchConnector) connector).submit( |
| new RControllerCodeLaunchConnector.CommandsCreator() { |
| @Override |
| public Status submitTo(final ToolController controller) { |
| final ToolRunnable[] runnables = new ToolRunnable[codeRegions.size()]; |
| final List<String> lines = new ArrayList<>(); |
| for (int i = 0; i < runnables.length; i++) { |
| final SourceRegion region = codeRegions.get(i); |
| lines.clear(); |
| TextUtil.addLines(region.fCode, lines); |
| runnables[i] = new SubmitEntireCommandRunnable( |
| lines.toArray(new String[lines.size()]), region); |
| } |
| final Status status = controller.getTool().getQueue().add( |
| ImCollections.newList(runnables) ); |
| if (status.getSeverity() != Status.OK) { |
| for (int i = 0; i < runnables.length; i++) { |
| runnables[i].changed(ToolRunnable.BEING_ABANDONED, null); |
| } |
| } |
| return status; |
| } |
| }, gotoConsole); |
| } |
| final List<String> lines = new ArrayList<>(codeRegions.size()*2); |
| for (int i = 0; i < codeRegions.size(); i++) { |
| TextUtil.addLines(codeRegions.get(i).fCode, lines); |
| } |
| return runRCodeDirect(lines, gotoConsole, null); |
| } |
| |
| public static final BooleanPref ECHO_ENABLED_PREF = new BooleanPref( |
| RRunDebugPreferenceConstants.ROOT_QUALIFIER + "/codelaunch", "echo.enabled" ); |
| |
| } |