| /*=============================================================================# |
| # 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; |
| } |
| |
| } |