blob: 0d2d8ca547129029a750d806a5062d2666655876 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2011, 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.r.ui.rtool;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.swt.widgets.Control;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.statet.jcommons.status.ProgressMonitor;
import org.eclipse.statet.jcommons.status.Status;
import org.eclipse.statet.jcommons.status.StatusException;
import org.eclipse.statet.jcommons.status.Statuses;
import org.eclipse.statet.jcommons.ts.core.SystemRunnable;
import org.eclipse.statet.jcommons.ts.core.Tool;
import org.eclipse.statet.ecommons.text.TextUtil;
import org.eclipse.statet.nico.core.runtime.SubmitType;
import org.eclipse.statet.nico.ui.NicoUITools;
import org.eclipse.statet.r.console.core.AbstractRDataRunnable;
import org.eclipse.statet.r.console.core.IRDataAdapter;
import org.eclipse.statet.r.console.core.RConsoleTool;
import org.eclipse.statet.r.console.core.RProcessREnvironment;
import org.eclipse.statet.r.console.core.RWorkspace;
import org.eclipse.statet.r.console.core.util.LoadReferenceRunnable;
import org.eclipse.statet.r.core.data.CombinedRElement;
import org.eclipse.statet.r.core.model.RElementName;
import org.eclipse.statet.r.nico.impl.RjsController;
import org.eclipse.statet.rj.data.RCharacterStore;
import org.eclipse.statet.rj.data.RDataUtils;
import org.eclipse.statet.rj.data.RLanguage;
import org.eclipse.statet.rj.data.RList;
import org.eclipse.statet.rj.data.RObject;
import org.eclipse.statet.rj.data.RObjectFactory;
import org.eclipse.statet.rj.data.RReference;
import org.eclipse.statet.rj.data.UnexpectedRDataException;
import org.eclipse.statet.rj.data.impl.RLanguageImpl;
import org.eclipse.statet.rj.data.impl.RList32Impl;
import org.eclipse.statet.rj.data.impl.RReferenceImpl;
import org.eclipse.statet.rj.services.FQRObject;
import org.eclipse.statet.rj.services.FunctionCall;
import org.eclipse.statet.rj.services.RService;
import org.eclipse.statet.rj.ts.core.RTool;
public class RElementInfoTask extends AbstractRDataRunnable implements SystemRunnable {
public static class RElementInfoData {
private Control control;
public IWorkbenchPart workbenchPart;
private RTool tool;
private CombinedRElement element;
private RElementName elementName;
private boolean isActiveBinding;
private RList elementAttr;
private String detailTitle;
private String detailInfo;
private RElementInfoData() {
}
public Control getControl() {
return this.control;
}
public IWorkbenchPart getWorkbenchPart() {
return this.workbenchPart;
}
public RTool getTool() {
return this.tool;
}
public CombinedRElement getElement() {
return this.element;
}
public RElementName getElementName() {
return this.elementName;
}
public boolean isElementOfActiveBinding() {
return this.isActiveBinding;
}
public RList getElementAttr() {
return this.elementAttr;
}
public boolean hasDetail() {
return (this.detailTitle != null);
}
public String getDetailTitle() {
return this.detailTitle;
}
public String getDetailInfo() {
return this.detailInfo;
}
}
private final RElementName elementName;
private int status;
private RElementInfoData data;
public RElementInfoTask(final RElementName name) {
super("reditor/hover", "Collecting Element Detail for Hover"); //$NON-NLS-1$
this.elementName= RElementName.normalize(name);
}
public boolean preCheck() {
if (this.elementName == null) {
return false;
}
if (this.elementName.getType() != RElementName.MAIN_DEFAULT) {
return false;
}
return true;
}
public RElementInfoData load(final RTool tool, final Control control,
final IWorkbenchPart workbenchPart) {
if (!NicoUITools.isToolReady(RConsoleTool.TYPE, RConsoleTool.R_DATA_FEATURESET_ID, tool)) {
return null;
}
try {
synchronized (this) {
final Status status= tool.getQueue().addHot(this);
if (status.getSeverity() >= Status.ERROR) {
return null;
}
while (this.status == 0) {
if (Thread.interrupted()) {
this.status= -1;
}
wait(200);
}
}
}
catch (final InterruptedException e) {
this.status= -1;
}
if (this.status != 1) {
tool.getQueue().removeHot(this);
return null;
}
final RElementInfoData data= this.data;
if (data != null && data.element != null) {
data.control= control;
data.workbenchPart= workbenchPart;
data.tool= tool;
return data;
}
return null;
}
public SubmitType getSubmitType() {
return SubmitType.OTHER;
}
@Override
public boolean changed(final int event, final Tool tool) {
switch (event) {
case MOVING_FROM:
return false;
case REMOVING_FROM:
case BEING_ABANDONED:
case FINISHING_CANCEL:
case FINISHING_ERROR:
this.status= -1;
synchronized (this) {
notifyAll();
}
break;
case FINISHING_OK:
this.status= 1;
synchronized (this) {
notifyAll();
}
break;
default:
break;
}
return true;
}
@Override
protected void run(final IRDataAdapter r, final ProgressMonitor m) throws StatusException {
if (this.status != 0 || m.isCanceled()) {
throw new StatusException(Statuses.CANCEL_STATUS);
}
if (!(r instanceof RjsController) || !preCheck()) {
return; // TODO
}
this.data= new RElementInfoData();
final RProcessREnvironment env= getEnv(r, m);
final String name;
final RReference envRef;
if (env != null) {
name= this.elementName.getDisplayName(RElementName.DISPLAY_EXACT);
envRef= new RReferenceImpl(env.getHandle(), RObject.TYPE_ENVIRONMENT, null);
}
else if (this.data.elementName != null && this.data.elementName.getScope() != null
&& this.data.elementName.getScope().getType() == RElementName.SCOPE_NS) {
name= this.elementName.getDisplayName(RElementName.DISPLAY_EXACT | RElementName.DISPLAY_FQN);
envRef= null;
}
else {
return;
}
if (name == null) {
return;
}
if (this.data.element == null || this.data.element.getRObjectType() == RObject.TYPE_MISSING) {
try {
this.data.element= ((RjsController) r).evalCombinedStruct(name, envRef,
0, RService.DEPTH_ONE, this.elementName, m );
if (this.data.element == null) {
return;
}
}
catch (final StatusException e) {
if (this.data.element == null) {
throw e;
}
// RObject.TYPE_MISSING
final String message= e.getMessage();
final int idxBegin= message.indexOf('<');
final int idxEnd= message.lastIndexOf('>');
if (idxBegin >= 0 && idxEnd > idxBegin + 1) {
this.data.detailTitle= "error";
this.data.detailInfo= message.substring(idxBegin + 1, idxEnd);
}
}
}
if (envRef != null) {
try {
final FunctionCall fcall= r.createFunctionCall("base::bindingIsActive"); //$NON-NLS-1$
fcall.addChar("sym", this.elementName.getSegmentName()); //$NON-NLS-1$
fcall.add("env", envRef); //$NON-NLS-1$
final RObject data= fcall.evalData(m);
this.data.isActiveBinding= RDataUtils.checkSingleLogiValue(data);
}
catch (final StatusException | UnexpectedRDataException e) {
}
}
if (this.data.element.getRObjectType() != RObject.TYPE_PROMISE
&& this.data.element.getRObjectType() != RObject.TYPE_MISSING) {
final String cmd= "class("+name+")";
final RObject robject= ((RjsController) r).evalData(cmd, envRef,
null, 0, RService.DEPTH_INFINITE, m );
if (robject != null) {
this.data.elementAttr= new RList32Impl(new RObject[] { robject }, null, new String[] { "class" });
}
}
if (this.data.element.getRObjectType() != RObject.TYPE_MISSING) {
final String title= "str";
final StringBuilder cmd= new StringBuilder("rj:::.statet.captureStr(");
if (this.data.element.getRObjectType() == RObject.TYPE_PROMISE) {
cmd.append("substitute(");
cmd.append(name);
cmd.append(")");
}
else {
cmd.append(name);
}
cmd.append(")");
final RObject robject= ((RjsController) r).evalData(cmd.toString(), envRef,
null, 0, RService.DEPTH_INFINITE, m );
try {
final RCharacterStore data= RDataUtils.checkRCharVector(robject).getData();
final StringBuilder sb= new StringBuilder((int) data.getLength()*30);
final String ln= TextUtil.getPlatformLineDelimiter();
for (int i= 0; i < data.getLength(); i++) {
if (!data.isNA(i)) {
sb.append(data.getChar(i));
sb.append(ln);
}
}
if (sb.length() > 0) {
sb.setLength(sb.length()-ln.length());
}
this.data.detailTitle= title;
this.data.detailInfo= sb.toString();
}
catch (final UnexpectedRDataException e) {}
}
}
private RProcessREnvironment getEnv(
final IRDataAdapter r, final ProgressMonitor m) throws StatusException {
RElementName envName= this.elementName.getScope();
boolean inherits= false;
if (envName == null) {
final int position= getFramePosition(r, m);
if (position > 0) {
envName= RElementName.create(RElementName.SCOPE_SYSFRAME, Integer.toString(position));
}
else {
envName= RElementName.create(RElementName.SCOPE_SEARCH_ENV, ".GlobalEnv");
}
inherits= true;
}
if (envName == null
|| !LoadReferenceRunnable.isAccessAllowed(envName, r.getWorkspaceData()) ) {
return null;
}
if (envName.getType() == RElementName.SCOPE_NS) {
this.data.elementName= this.elementName;
return null;
}
if (envName.getType() == RElementName.SCOPE_NS_INT) {
final CombinedRElement element= r.getWorkspaceData().resolve(envName,
RWorkspace.RESOLVE_UPTODATE, 0, m );
if (element instanceof RProcessREnvironment) {
final RProcessREnvironment env= (RProcessREnvironment) element;
if (env.getNames() != null && env.getNames().contains(this.elementName.getSegmentName())) {
this.data.elementName= this.elementName;
if (this.elementName.getNextSegment() == null) {
this.data.element= env.get(this.elementName.getSegmentName());
}
return env;
}
}
return null;
}
final String name= envName.getDisplayName(RElementName.DISPLAY_FQN);
if (name == null) {
return null;
}
final RElementName mainName= RElementName.cloneSegment(this.elementName);
final int depth= (this.elementName.getNextSegment() != null) ?
RService.DEPTH_REFERENCE : RService.DEPTH_ONE;
final FQRObject data= r.findData(mainName.getSegmentName(),
new RLanguageImpl(RLanguage.CALL, name, null), inherits,
"combined", RObjectFactory.F_ONLY_STRUCT, depth, m );
if (data == null) {
return null;
}
final RProcessREnvironment foundEnv= (RProcessREnvironment) data.getEnv();
if (!updateName(foundEnv.getElementName())) {
final CombinedRElement altElement= r.getWorkspaceData().resolve(new RReferenceImpl(
foundEnv.getHandle(), RObject.TYPE_ENVIRONMENT, null ), 0 );
if (!(altElement instanceof RProcessREnvironment)
|| !updateName(altElement.getElementName()) ) {
this.data.elementName= this.elementName;
}
}
if (depth == RService.DEPTH_ONE) {
this.data.element= (CombinedRElement) data.getObject();
return foundEnv;
}
else {
final RObject object= data.getObject();
if (!(object instanceof RReference)
|| ((RReference) object).getReferencedRObjectType() == RObject.TYPE_PROMISE
|| ((RReference) object).getReferencedRObjectType() == RObject.TYPE_MISSING) {
return null;
}
return foundEnv;
}
}
private boolean updateName(final RElementName envName) {
if (envName == null || envName.getSegmentName() == null
|| envName.getSegmentName().isEmpty()) {
return false;
}
final List<RElementName> segments= new ArrayList<>();
segments.add(envName);
RElementName.addSegments(segments, this.elementName);
final RElementName name= RElementName.create(segments);
if (name.getScope() == null) {
return false;
}
this.data.elementName= name;
return true;
}
protected int getFramePosition(final IRDataAdapter r,
final ProgressMonitor m) throws StatusException {
return 0;
}
}