blob: 6362e764218d118efc805fb0cdefdcb395a95f60 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2008, 2020 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.debug.ui.launcher;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.IStreamListener;
import org.eclipse.debug.core.model.IStreamMonitor;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.TextUtilities;
import org.eclipse.jface.text.source.SourceViewer;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.handlers.HandlerUtil;
import org.eclipse.ui.progress.IWorkbenchSiteProgressService;
import org.eclipse.ui.services.IServiceLocator;
import org.eclipse.ui.statushandlers.StatusManager;
import org.eclipse.statet.jcommons.status.ProgressMonitor;
import org.eclipse.statet.jcommons.status.StatusException;
import org.eclipse.statet.jcommons.ts.core.Tool;
import org.eclipse.statet.jcommons.ts.core.ToolService;
import org.eclipse.statet.ecommons.runtime.core.util.StatusUtils;
import org.eclipse.statet.ecommons.text.IndentUtil;
import org.eclipse.statet.ecommons.text.TextUtil;
import org.eclipse.statet.ecommons.ui.util.UIAccess;
import org.eclipse.statet.internal.r.debug.ui.RLaunchingMessages;
import org.eclipse.statet.ltk.ui.sourceediting.ISourceEditor;
import org.eclipse.statet.ltk.ui.util.LTKWorkbenchUIUtil;
import org.eclipse.statet.nico.core.runtime.ConsoleRunnable;
import org.eclipse.statet.nico.core.runtime.IRequireSynch;
import org.eclipse.statet.nico.core.runtime.SubmitType;
import org.eclipse.statet.nico.core.runtime.ToolController;
import org.eclipse.statet.nico.ui.NicoUI;
import org.eclipse.statet.nico.ui.NicoUITools;
import org.eclipse.statet.r.console.core.IRBasicAdapter;
import org.eclipse.statet.r.console.core.RConsoleTool;
import org.eclipse.statet.r.console.core.RWorkspace;
import org.eclipse.statet.r.core.IRCoreAccess;
import org.eclipse.statet.r.core.RCore;
import org.eclipse.statet.r.core.RUtil;
import org.eclipse.statet.r.ui.RUI;
import org.eclipse.statet.r.ui.editors.IRSourceEditor;
/**
*
*/
public class SubmitSelectionAndPasteOutputHandler extends AbstractHandler {
private static class R implements ConsoleRunnable, Runnable {
private ISourceEditor fEditor;
private IDocument fDocument;
private String[] fLines;
private Position fPosition;
private StringBuilder fOutput;
R(final ISourceEditor editor) {
fEditor = editor;
}
private boolean setupSource(final ITextSelection selection) {
final SourceViewer viewer = fEditor.getViewer();
fDocument = viewer.getDocument();
try {
if (selection.getLength() > 0) {
final ArrayList<String> lines= new ArrayList<>(0);
TextUtil.addLines(fDocument, selection.getOffset(), selection.getLength(), lines);
fLines = lines.toArray(new String[lines.size()]);
final int start = selection.getOffset();
int end = start + selection.getLength();
final char c = fDocument.getChar(end-1);
if (c == '\n') {
end--;
if (end > 0 && fDocument.getChar(end-1) == '\r') {
end--;
}
}
else if (c == '\r') {
end--;
}
fPosition = new Position(start, end - start);
}
else {
final IRegion line = fDocument.getLineInformationOfOffset(selection.getOffset());
fLines = new String[] { fDocument.get(line.getOffset(), line.getLength()) };
fPosition = new Position(line.getOffset(), line.getLength());
}
fDocument.addPosition(fPosition);
return true;
}
catch (final BadLocationException e) {
StatusManager.getManager().handle(new Status(IStatus.ERROR, RUI.BUNDLE_ID, -1, "An error occurred preparing Run and Paste Output", e)); //$NON-NLS-1$
return false;
}
}
public void dispose() {
if (fDocument != null && fPosition != null) {
fDocument.removePosition(fPosition);
}
fEditor = null;
fDocument = null;
fPosition = null;
fOutput = null;
fLines = null;
}
@Override
public String getTypeId() {
return "editor/run-and-paste"; //$NON-NLS-1$
}
@Override
public String getLabel() {
return RLaunchingMessages.SubmitCodeAndPasteOutput_RTask_label;
}
@Override
public SubmitType getSubmitType() {
return SubmitType.EDITOR;
}
@Override
public boolean canRunIn(final Tool tool) {
return (tool.isProvidingFeatureSet(RConsoleTool.R_BASIC_FEATURESET_ID));
}
@Override
public boolean changed(final int event, final Tool process) {
switch (event) {
case REMOVING_FROM:
case BEING_ABANDONED:
UIAccess.getDisplay().asyncExec(new Runnable() {
@Override
public void run() {
dispose();
}
});
break;
}
return true;
}
@Override
public void run(final ToolService service,
final ProgressMonitor m) throws StatusException {
final IRBasicAdapter r = (IRBasicAdapter) service;
fOutput = new StringBuilder(200);
final IStreamListener listener = new IStreamListener() {
@Override
public void streamAppended(final String text, final IStreamMonitor monitor) {
fOutput.append(text);
}
};
final ToolController controller = r.getController();
r.briefAboutToChange();
try {
controller.getStreams().getOutputStreamMonitor().addListener(listener);
controller.getStreams().getErrorStreamMonitor().addListener(listener);
for (int i = 0; i < fLines.length; i++) {
if (m.isCanceled()) {
return;
}
m.beginSubTask(fLines[i]);
r.submitToConsole(fLines[i], m);
}
if (r instanceof IRequireSynch) {
final Pattern pattern = ((IRequireSynch) r).synch(m);
if (pattern != null) {
final Matcher matcher = pattern.matcher(fOutput);
int idx = -1;
while (matcher.find()) {
idx = matcher.start();
}
if (idx >= 0) {
fOutput.delete(idx, fOutput.length());
}
}
}
}
finally {
r.briefChanged(RWorkspace.REFRESH_AUTO);
controller.getStreams().getOutputStreamMonitor().removeListener(listener);
controller.getStreams().getErrorStreamMonitor().removeListener(listener);
UIAccess.getDisplay().asyncExec(this);
}
}
protected IRCoreAccess getRCoreAccess() {
final ISourceEditor editor= fEditor;
return (editor instanceof IRSourceEditor) ?
((IRSourceEditor) editor).getRCoreAccess() :
RCore.WORKBENCH_ACCESS;
}
@Override
public void run() {
// After R in display
final SourceViewer viewer = fEditor.getViewer();
if (!UIAccess.isOkToUse(viewer)
|| (viewer.getDocument() != fDocument)
|| fPosition.isDeleted()) {
return;
}
IWorkbenchSiteProgressService progressService = null;
final ISourceEditor editor = fEditor.getAdapter(ISourceEditor.class);
if (editor != null) {
final IServiceLocator serviceLocator = editor.getServiceLocator();
if (serviceLocator != null) {
progressService = serviceLocator.getService(IWorkbenchSiteProgressService.class);
}
}
if (progressService != null) {
progressService.incrementBusy();
}
try {
final IndentUtil util= new IndentUtil(fDocument, getRCoreAccess().getRCodeStyle());
final int indent = util.getMultilineIndentColumn(fDocument.getLineOfOffset(fPosition.getOffset()),
fDocument.getLineOfOffset(fPosition.getOffset() + fPosition.getLength()));
final String delimiter = TextUtilities.getDefaultLineDelimiter(fDocument);
final String prefix = delimiter + util.createIndentString(indent) + "# "; //$NON-NLS-1$
final String[] lines = RUtil.LINE_SEPARATOR_PATTERN.split(fOutput);
final int size = fOutput.length() + lines.length*(prefix.length()-1) + 2;
fOutput.setLength(0);
fOutput.ensureCapacity(size);
for (int i = 0; i < lines.length; i++) {
fOutput.append(prefix);
fOutput.append(lines[i]);
}
fOutput.append(delimiter);
final int pos = fPosition.getOffset()+fPosition.getLength();
fDocument.replace(pos, 0, fOutput.toString());
viewer.revealRange(pos, fOutput.length());
if (progressService != null) {
progressService.warnOfContentChange();
}
}
catch (final BadLocationException e) {
StatusManager.getManager().handle(new Status(IStatus.ERROR, RUI.BUNDLE_ID, -1,
RLaunchingMessages.SubmitCodeAndPasteOutput_error_WhenPasting_message, e),
StatusManager.LOG | StatusManager.SHOW);
}
finally {
dispose();
if (progressService != null) {
progressService.decrementBusy();
}
}
}
}
public SubmitSelectionAndPasteOutputHandler() {
}
@Override
public Object execute(final ExecutionEvent event) throws ExecutionException {
final IWorkbenchPart workbenchPart = HandlerUtil.getActivePart(event);
final ISourceEditor editor = workbenchPart.getAdapter(ISourceEditor.class);
if (editor != null) {
if (!editor.isEditable(true)) {
cancel(null, new Status(IStatus.ERROR, RUI.BUNDLE_ID,
RLaunchingMessages.SubmitCodeAndPasteOutput_info_WriteProtected_status), event);
return null;
}
final SourceViewer viewer = editor.getViewer();
final ITextSelection selection = (ITextSelection) viewer.getSelection();
final R r = new R(editor);
if (!r.setupSource(selection)) {
cancel(r, new Status(IStatus.ERROR, RUI.BUNDLE_ID,
RLaunchingMessages.SubmitCodeAndPasteOutput_error_Unspecific_status), null);
return null;
}
final Tool tool= NicoUI.getToolRegistry().getActiveToolSession(UIAccess.getActiveWorkbenchPage(true))
.getTool();
try {
NicoUITools.accessTool(RConsoleTool.TYPE, tool);
}
catch (final CoreException e) {
cancel(r, e.getStatus(), event);
return null;
}
final org.eclipse.statet.jcommons.status.Status status= tool.getQueue().add(r);
if (status.getSeverity() >= Status.ERROR) {
cancel(r, StatusUtils.convert(status), event);
}
return null;
}
LaunchShortcutUtil.handleUnsupportedExecution(event);
return null;
}
private void cancel(final R r, final IStatus status, final ExecutionEvent executionEvent) {
if (r != null) {
r.dispose();
}
LTKWorkbenchUIUtil.indicateStatus(status, executionEvent);
}
}