/*=============================================================================# # Copyright (c) 2011-2016 Stephan Wahlbrink (WalWare.de) and others. # All rights reserved. This program and the accompanying materials # are made available under the terms of the Eclipse Public License v1.0 # which accompanies this distribution, and is available at # http://www.eclipse.org/legal/epl-v10.html # # Contributors: # Stephan Wahlbrink - initial API and implementation #=============================================================================*/ package de.walware.statet.r.ui.rtool; import java.util.ArrayList; import java.util.List; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.swt.widgets.Control; import org.eclipse.ui.IWorkbenchPart; import de.walware.ecommons.text.TextUtil; import de.walware.ecommons.ts.ISystemRunnable; import de.walware.ecommons.ts.ITool; import de.walware.statet.nico.core.runtime.SubmitType; import de.walware.statet.nico.ui.NicoUITools; import de.walware.rj.data.RCharacterStore; import de.walware.rj.data.RDataUtil; import de.walware.rj.data.RLanguage; import de.walware.rj.data.RList; import de.walware.rj.data.RObject; import de.walware.rj.data.RObjectFactory; import de.walware.rj.data.RReference; import de.walware.rj.data.UnexpectedRDataException; import de.walware.rj.data.defaultImpl.RLanguageImpl; import de.walware.rj.data.defaultImpl.RListImpl; import de.walware.rj.data.defaultImpl.RReferenceImpl; import de.walware.rj.services.FunctionCall; import de.walware.rj.services.RService; import de.walware.statet.r.console.core.AbstractRDataRunnable; import de.walware.statet.r.console.core.IRDataAdapter; import de.walware.statet.r.console.core.RConsoleTool; import de.walware.statet.r.console.core.RWorkspace; import de.walware.statet.r.console.core.RWorkspace.ICombinedREnvironment; import de.walware.statet.r.console.core.util.LoadReferenceRunnable; import de.walware.statet.r.core.data.ICombinedRElement; import de.walware.statet.r.core.model.RElementName; import de.walware.statet.r.nico.impl.RjsController; public class RElementInfoTask extends AbstractRDataRunnable implements ISystemRunnable { public static class RElementInfoData { private Control control; public IWorkbenchPart workbenchPart; private ITool tool; private ICombinedRElement 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 ITool getTool() { return this.tool; } public ICombinedRElement 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 ITool tool, final Control control, final IWorkbenchPart workbenchPart) { if (!NicoUITools.isToolReady(RConsoleTool.TYPE, RConsoleTool.R_DATA_FEATURESET_ID, tool)) { return null; } try { synchronized (this) { final IStatus status= tool.getQueue().addHot(this); if (status.getSeverity() >= IStatus.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 ITool 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 IProgressMonitor monitor) throws CoreException { if (this.status != 0 || monitor.isCanceled()) { throw new CoreException(Status.CANCEL_STATUS); } if (!(r instanceof RjsController) || !preCheck()) { return; // TODO } this.data= new RElementInfoData(); final ICombinedREnvironment environment= getEnv(r, monitor); final String name; final RReference envir; if (environment != null) { name= RElementName.createDisplayName(this.elementName, RElementName.DISPLAY_EXACT); envir= new RReferenceImpl(environment.getHandle(), RObject.TYPE_ENV, null); } else if (this.data.elementName != null && this.data.elementName.getScope() != null && this.data.elementName.getScope().getType() == RElementName.SCOPE_NS) { name= RElementName.createDisplayName(this.elementName, RElementName.DISPLAY_EXACT | RElementName.DISPLAY_FQN); envir= 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, envir, 0, RService.DEPTH_ONE, this.elementName, monitor ); if (this.data.element == null) { return; } } catch (final CoreException 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 (envir != null) { try { final FunctionCall fcall= r.createFunctionCall("base::bindingIsActive"); //$NON-NLS-1$ fcall.addChar("sym", this.elementName.getSegmentName()); //$NON-NLS-1$ fcall.add("env", envir); //$NON-NLS-1$ final RObject data= fcall.evalData(monitor); this.data.isActiveBinding= RDataUtil.checkSingleLogiValue(data); } catch (final CoreException | 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, envir, null, 0, RService.DEPTH_INFINITE, monitor ); if (robject != null) { this.data.elementAttr= new RListImpl(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(), envir, null, 0, RService.DEPTH_INFINITE, monitor ); try { final RCharacterStore data= RDataUtil.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 ICombinedREnvironment getEnv(final IRDataAdapter r, final IProgressMonitor monitor) throws CoreException { RElementName envName= this.elementName.getScope(); boolean inherits= false; if (envName == null) { final int position= getFramePosition(r, monitor); 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 ICombinedRElement element= r.getWorkspaceData().resolve(envName, RWorkspace.RESOLVE_UPTODATE, 0, monitor ); if (element instanceof ICombinedREnvironment) { final ICombinedREnvironment env= (ICombinedREnvironment) 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 RObject[] data= ((RjsController) r).findData(mainName.getSegmentName(), new RLanguageImpl(RLanguage.CALL, name, null), inherits, "combined", RObjectFactory.F_ONLY_STRUCT, depth, monitor ); if (data == null) { return null; } final ICombinedREnvironment foundEnv= (ICombinedREnvironment) data[1]; if (!updateName(foundEnv.getElementName())) { final ICombinedRElement altElement= r.getWorkspaceData().resolve(new RReferenceImpl( foundEnv.getHandle(), RObject.TYPE_ENV, null ), 0 ); if (!(altElement instanceof ICombinedREnvironment) || !updateName(altElement.getElementName()) ) { this.data.elementName= this.elementName; } } if (depth == RService.DEPTH_ONE) { this.data.element= (ICombinedRElement) data[0]; return (ICombinedREnvironment) data[1]; } else { if (!(data[0] instanceof RReference) || ((RReference) data[0]).getReferencedRObjectType() == RObject.TYPE_PROMISE || ((RReference) data[0]).getReferencedRObjectType() == RObject.TYPE_MISSING) { return null; } return (ICombinedREnvironment) data[1]; } } 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 IProgressMonitor monitor) throws CoreException { return 0; } }