| /*=============================================================================# |
| # Copyright (c) 2010, 2018 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.ui.rhelp; |
| |
| import java.util.List; |
| |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.jface.text.IInformationControlCreator; |
| |
| import org.eclipse.statet.jcommons.text.core.TextRegion; |
| |
| import org.eclipse.statet.ltk.ast.core.util.AstSelection; |
| import org.eclipse.statet.ltk.model.core.elements.ISourceUnit; |
| import org.eclipse.statet.ltk.ui.sourceediting.assist.AssistInvocationContext; |
| import org.eclipse.statet.ltk.ui.sourceediting.assist.IInfoHover; |
| import org.eclipse.statet.r.core.IRCoreAccess; |
| import org.eclipse.statet.r.core.RCore; |
| import org.eclipse.statet.r.core.model.IRFrame; |
| import org.eclipse.statet.r.core.model.IRFrameInSource; |
| import org.eclipse.statet.r.core.model.IRSourceUnit; |
| import org.eclipse.statet.r.core.model.RElementAccess; |
| import org.eclipse.statet.r.core.model.RElementName; |
| import org.eclipse.statet.r.core.model.RModel; |
| import org.eclipse.statet.r.core.renv.IREnv; |
| import org.eclipse.statet.r.core.rhelp.IREnvHelp; |
| import org.eclipse.statet.r.core.rhelp.IRHelpManager; |
| import org.eclipse.statet.r.core.rhelp.IRHelpPage; |
| import org.eclipse.statet.r.core.rsource.ast.FCall; |
| import org.eclipse.statet.r.core.rsource.ast.NodeType; |
| import org.eclipse.statet.r.core.rsource.ast.RAstNode; |
| |
| |
| public class RHelpHover implements IInfoHover { |
| |
| |
| private final int mode; |
| |
| |
| public RHelpHover() { |
| this(MODE_TOOLTIP); |
| } |
| |
| public RHelpHover(final int mode) { |
| this.mode= mode; |
| } |
| |
| |
| @Override |
| public Object getHoverInfo(final AssistInvocationContext context) { |
| final AstSelection selection= context.getAstSelection(); |
| if (!(selection.getCovering() instanceof RAstNode)) { |
| return null; |
| } |
| final RAstNode rNode= (RAstNode) selection.getCovering(); |
| RElementName name= searchName(rNode, context, true); |
| if (Thread.interrupted()) { |
| return null; |
| } |
| if ((this.mode & MODE_FOCUS) != 0 && name == null) { |
| RAstNode parent; |
| switch (rNode.getNodeType()) { |
| case SYMBOL: |
| case STRING_CONST: |
| parent= rNode.getRParent(); |
| if (parent != null && parent.getNodeType() == NodeType.F_CALL_ARG |
| && ((FCall.Arg) parent).getNameChild() == rNode) { |
| name= searchNameOfFunction(parent, context); |
| } |
| break; |
| case F_CALL: |
| case F_CALL_ARGS: |
| case F_CALL_ARG: |
| name= searchNameOfFunction(rNode, context); |
| break; |
| default: |
| break; |
| } |
| if (Thread.interrupted()) { |
| return null; |
| } |
| } |
| if (name == null) { |
| return null; |
| } |
| |
| final IRCoreAccess rCoreAccess= (context.getSourceUnit() instanceof IRSourceUnit) ? |
| ((IRSourceUnit) context.getSourceUnit()).getRCoreAccess() : |
| RCore.WORKBENCH_ACCESS; |
| IREnv rEnv= rCoreAccess.getREnv(); |
| if (rEnv == null) { |
| rEnv= RCore.WORKBENCH_ACCESS.getREnv(); |
| } |
| Object helpObject= null; |
| |
| final IRHelpManager rHelpManager= RCore.getRHelpManager(); |
| final IREnvHelp help= rHelpManager.getHelp(rEnv); |
| if (help != null) { |
| try { |
| if (RElementName.isPackageFacetScopeType(name.getType())) { |
| helpObject= help.getPkgHelp(name.getSegmentName()); |
| } |
| else { |
| if (name.getScope() != null |
| && RElementName.isPackageFacetScopeType(name.getScope().getType()) ) { |
| helpObject= help.getPageForTopic(name.getScope().getSegmentName(), |
| name.getSegmentName() ); |
| } |
| if (helpObject== null) { |
| final List<IRHelpPage> topics= help.getPagesForTopic(name.getSegmentName()); |
| if (topics == null || topics.isEmpty()) { |
| return null; |
| } |
| if (topics.size() == 1) { |
| helpObject= topics.get(0); |
| } |
| else { |
| final IRFrameInSource frame= RModel.searchFrame((RAstNode) selection.getCovering()); |
| if (frame == null) { |
| return null; |
| } |
| helpObject= searchFrames(topics, RModel.createDirectFrameList(frame)); |
| final ISourceUnit su= context.getSourceUnit(); |
| if (helpObject == null && su instanceof IRSourceUnit) { |
| helpObject= searchFrames(topics, |
| RModel.createProjectFrameList(null, (IRSourceUnit) su) ); |
| } |
| } |
| } |
| } |
| } |
| catch (final CoreException e) { |
| // CANCELLED |
| return null; |
| } |
| finally { |
| help.unlock(); |
| } |
| } |
| if (Thread.interrupted() || helpObject == null) { |
| return null; |
| } |
| final String httpUrl= rHelpManager.toHttpUrl(helpObject, RHelpUIServlet.INFO_TARGET); |
| if (httpUrl != null) { |
| return new RHelpInfoHoverCreator.Data(context.getSourceViewer().getTextWidget(), |
| helpObject, httpUrl); |
| } |
| return null; |
| } |
| |
| private IRHelpPage searchFrames(final List<IRHelpPage> helpPages, final List<IRFrame> frames) { |
| if (frames == null) { |
| return null; |
| } |
| for (final IRFrame frame : frames) { |
| if (frame.getFrameType() == IRFrame.PACKAGE) { |
| for (final IRHelpPage helpPage : helpPages) { |
| if (helpPage.getPackage().getName().equals( |
| frame.getElementName().getSegmentName() )) { |
| return helpPage; |
| } |
| } |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public IInformationControlCreator getHoverControlCreator() { |
| return new RHelpInfoHoverCreator(this.mode); |
| } |
| |
| static RElementName searchName(RAstNode rNode, final TextRegion region, final boolean checkInterrupted) { |
| RElementAccess access= null; |
| while (rNode != null && access == null) { |
| if (checkInterrupted && Thread.currentThread().isInterrupted()) { |
| return null; |
| } |
| final List<Object> attachments= rNode.getAttachments(); |
| for (final Object attachment : attachments) { |
| if (attachment instanceof RElementAccess) { |
| access= (RElementAccess) attachment; |
| final IRFrame frame= access.getFrame(); |
| if ((frame != null && frame.getFrameType() != IRFrame.FUNCTION) |
| || (RElementName.isPackageFacetScopeType(access.getType())) ) { |
| final RElementName e= getElementAccessOfRegion(access, region); |
| if (e != null) { |
| return e; |
| } |
| } |
| } |
| } |
| rNode= rNode.getRParent(); |
| } |
| return null; |
| } |
| |
| static RElementName searchNameOfFunction(RAstNode rNode, final TextRegion region) { |
| while (rNode != null) { |
| if (rNode.getNodeType() == NodeType.F_CALL) { |
| final FCall fcall= (FCall) rNode; |
| if (fcall.getArgsOpenOffset() != Integer.MIN_VALUE |
| && fcall.getArgsOpenOffset() <= region.getStartOffset()) { |
| final List<Object> attachments= ((FCall) rNode).getAttachments(); |
| for (final Object attachment : attachments) { |
| if (attachment instanceof RElementAccess) { |
| final RElementAccess access= (RElementAccess) attachment; |
| final IRFrame frame= access.getFrame(); |
| if (access.getNode() == fcall |
| && frame != null && frame.getFrameType() != IRFrame.FUNCTION |
| && access.getNextSegment() == null) { |
| final RElementName fName= RElementName.normalize(access); |
| if (RElementName.isRegularMainType(fName.getType())) { |
| return fName; |
| } |
| } |
| } |
| } |
| } |
| return null; |
| } |
| rNode= rNode.getRParent(); |
| } |
| return null; |
| } |
| |
| static RElementName getElementAccessOfRegion(final RElementAccess access, final TextRegion region) { |
| if (access.getSegmentName() == null) { |
| return null; |
| } |
| |
| int segmentCount= 0; |
| RElementAccess current= access; |
| while (current != null) { |
| segmentCount++; |
| final RAstNode nameNode= current.getNameNode(); |
| if (nameNode != null |
| && nameNode.getStartOffset() <= region.getStartOffset() |
| && nameNode.getEndOffset() >= region.getEndOffset() ) { |
| if (RElementName.isRegularMainType(access.getType())) { |
| return access; |
| } |
| if (segmentCount == 1) { |
| if (RElementName.isPackageFacetScopeType(access.getType())) { |
| return access; |
| } |
| } |
| else /* (segmentCount > 1) */ { |
| if (RElementName.isPackageFacetScopeType(access.getType()) |
| && RElementName.isRegularMainType(access.getNextSegment().getType()) |
| && access.getNextSegment().getSegmentName() != null) { |
| return RElementName.normalize(access); |
| } |
| } |
| return null; |
| } |
| current= current.getNextSegment(); |
| } |
| |
| return null; |
| } |
| |
| } |