| /*=============================================================================# |
| # Copyright (c) 2008, 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.sourceediting; |
| |
| import static org.eclipse.statet.jcommons.lang.ObjectUtils.nonNullAssert; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.function.BiFunction; |
| |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.jface.text.BadLocationException; |
| import org.eclipse.jface.text.BadPartitioningException; |
| import org.eclipse.jface.text.IDocument; |
| |
| import org.eclipse.statet.jcommons.collections.CollectionUtils; |
| import org.eclipse.statet.jcommons.collections.ImCollections; |
| import org.eclipse.statet.jcommons.lang.Nullable; |
| |
| import org.eclipse.statet.ecommons.text.core.IFragmentDocument; |
| import org.eclipse.statet.ecommons.text.core.IPartitionConstraint; |
| |
| import org.eclipse.statet.internal.r.ui.FCallNamePattern; |
| import org.eclipse.statet.internal.r.ui.editors.RElementCompletionProposal; |
| import org.eclipse.statet.internal.r.ui.editors.RKeywordCompletionProposal; |
| import org.eclipse.statet.internal.r.ui.editors.RSimpleCompletionProposal; |
| import org.eclipse.statet.ltk.ast.core.AstNode; |
| import org.eclipse.statet.ltk.model.core.elements.IModelElement; |
| import org.eclipse.statet.ltk.ui.IElementLabelProvider; |
| import org.eclipse.statet.ltk.ui.sourceediting.ISourceEditor; |
| import org.eclipse.statet.ltk.ui.sourceediting.assist.AssistInvocationContext; |
| import org.eclipse.statet.ltk.ui.sourceediting.assist.AssistProposalCollector; |
| import org.eclipse.statet.ltk.ui.sourceediting.assist.ContentAssist; |
| import org.eclipse.statet.ltk.ui.sourceediting.assist.IAssistCompletionProposal; |
| import org.eclipse.statet.ltk.ui.sourceediting.assist.IContentAssistComputer; |
| import org.eclipse.statet.r.core.IRCoreAccess; |
| import org.eclipse.statet.r.core.RCore; |
| import org.eclipse.statet.r.core.RSymbolComparator; |
| import org.eclipse.statet.r.core.RSymbolComparator.PrefixPattern; |
| import org.eclipse.statet.r.core.data.CombinedRElement; |
| import org.eclipse.statet.r.core.model.ArgsDefinition; |
| import org.eclipse.statet.r.core.model.IRElement; |
| import org.eclipse.statet.r.core.model.IRFrame; |
| import org.eclipse.statet.r.core.model.IRFrameInSource; |
| import org.eclipse.statet.r.core.model.IRLangElement; |
| import org.eclipse.statet.r.core.model.IRMethod; |
| import org.eclipse.statet.r.core.model.RCoreFunctions; |
| 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.pkgmanager.IRPkgManager; |
| import org.eclipse.statet.r.core.rlang.RTokens; |
| import org.eclipse.statet.r.core.rsource.ast.FCall; |
| import org.eclipse.statet.r.core.rsource.ast.RAst; |
| import org.eclipse.statet.r.core.rsource.ast.RAst.FCallArgMatch; |
| import org.eclipse.statet.r.core.rsource.ast.RAstNode; |
| import org.eclipse.statet.r.core.source.IRDocumentConstants; |
| import org.eclipse.statet.r.core.source.RHeuristicTokenScanner; |
| import org.eclipse.statet.r.ui.RLabelProvider; |
| import org.eclipse.statet.r.ui.editors.IRSourceEditor; |
| import org.eclipse.statet.r.ui.sourceediting.RAssistInvocationContext.FCallInfo; |
| import org.eclipse.statet.r.ui.sourceediting.RFrameSearchPath.RFrameIterator; |
| import org.eclipse.statet.rhelp.core.REnvHelp; |
| import org.eclipse.statet.rhelp.core.RHelpManager; |
| import org.eclipse.statet.rhelp.core.RHelpTopicEntry; |
| import org.eclipse.statet.rhelp.core.RPkgHelp; |
| import org.eclipse.statet.rj.data.RObject; |
| import org.eclipse.statet.rj.data.RReference; |
| import org.eclipse.statet.rj.renv.core.REnv; |
| import org.eclipse.statet.rj.renv.core.RPkgBuilt; |
| import org.eclipse.statet.rj.renv.core.RPkgCompilation; |
| import org.eclipse.statet.rj.renv.core.RPkgUtils; |
| import org.eclipse.statet.rj.renv.runtime.RPkgManagerDataset; |
| |
| |
| public class RElementCompletionComputer implements IContentAssistComputer { |
| |
| |
| private static final IPartitionConstraint NO_R_COMMENT_CONSTRAINT= new IPartitionConstraint() { |
| @Override |
| public boolean matches(final String partitionType) { |
| return (partitionType != IRDocumentConstants.R_COMMENT_CONTENT_TYPE); |
| }; |
| }; |
| |
| |
| private static final List<String> fgKeywords; |
| static { |
| final ArrayList<String> list= new ArrayList<>(); |
| Collections.addAll(list, RTokens.CONSTANT_WORDS); |
| Collections.addAll(list, RTokens.FLOWCONTROL_WORDS); |
| Collections.sort(list, RSymbolComparator.R_NAMES_COLLATOR); |
| list.trimToSize(); |
| fgKeywords= Collections.unmodifiableList(list); |
| } |
| |
| private static String toString(final Object value) { |
| if (value instanceof List) { |
| final List<String> list= (List<String>) value; |
| Collections.sort(list, RPkgUtils.NAMES_COLLATOR); |
| return CollectionUtils.toString(list, ", "); //$NON-NLS-1$ |
| } |
| return value.toString(); |
| } |
| |
| |
| public static class CompleteRuntime extends RElementCompletionComputer { |
| |
| public CompleteRuntime() { |
| super(RFrameSearchPath.ENGINE_MODE); |
| } |
| |
| } |
| |
| |
| protected static final int NA_PRIO= Integer.MIN_VALUE; |
| |
| protected static final int ARG_NAME_PRIO= 80; |
| |
| protected static final int ARG_TYPE_PRIO= 40; |
| protected static final int ARG_TYPE_NO_PRIO= -40; |
| |
| |
| private final IElementLabelProvider labelProvider= new RLabelProvider(RLabelProvider.NAMESPACE); |
| |
| private final int mode; |
| |
| private IRSourceEditor editor; |
| private ContentAssist assist; |
| |
| private final RFrameSearchPath searchPath= new RFrameSearchPath(); |
| |
| private boolean inDefault; |
| private boolean inString; |
| |
| private int pkgNamePrio; |
| private int helpTopicPrio; |
| |
| private IStatus resultStatus; |
| |
| |
| public RElementCompletionComputer() { |
| this(0); |
| } |
| |
| protected RElementCompletionComputer(final int mode) { |
| this.mode= mode; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void sessionStarted(final ISourceEditor editor, final ContentAssist assist) { |
| this.editor= (editor instanceof IRSourceEditor) ? (IRSourceEditor) editor : null; |
| |
| this.assist= assist; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void sessionEnded() { |
| this.searchPath.clear(); |
| |
| this.assist= null; |
| this.resultStatus= null; |
| } |
| |
| protected final void setStatus(final IStatus status) { |
| this.resultStatus= status; |
| } |
| |
| |
| protected final int getSearchMode(final RAssistInvocationContext context) { |
| if (this.mode != 0) { |
| return this.mode; |
| } |
| return context.getDefaultRFrameSearchMode(); |
| } |
| |
| |
| protected final boolean isSymbolCandidate(final String name) { |
| for (int i= 0; i < name.length(); i++) { |
| if (RTokens.isRobustSeparator(name.charAt(i))) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| private boolean isCompletable(RElementName elementName) { |
| if (elementName == null) { |
| return false; |
| } |
| do { |
| switch (elementName.getType()) { |
| case RElementName.SUB_INDEXED_S: |
| case RElementName.SUB_INDEXED_D: |
| return false; |
| } |
| if (elementName.getSegmentName() == null) { |
| return false; |
| } |
| elementName= elementName.getNextSegment(); |
| } |
| while (elementName != null); |
| return true; |
| } |
| |
| private RAstNode getRAstNode(final RAssistInvocationContext context) { |
| AstNode node= context.getInvocationAstSelection().getCovering(); |
| if (node == null) { |
| node= context.getAstInfo().getRoot(); |
| } |
| return (node instanceof RAstNode) ? (RAstNode) node : null; |
| } |
| |
| |
| @Override |
| public IStatus computeCompletionProposals(final AssistInvocationContext context, |
| final int mode, final AssistProposalCollector proposals, |
| final IProgressMonitor monitor) { |
| this.resultStatus= null; |
| |
| if (context instanceof RAssistInvocationContext) { |
| computeCompletionProposals((RAssistInvocationContext) context, mode, proposals, |
| monitor ); |
| } |
| |
| return this.resultStatus; |
| } |
| |
| @Override |
| public IStatus computeInformationProposals(final AssistInvocationContext context, |
| final AssistProposalCollector proposals, final IProgressMonitor monitor) { |
| this.resultStatus= null; |
| |
| if (context instanceof RAssistInvocationContext) { |
| doComputeContextProposals((RAssistInvocationContext) context, proposals, monitor); |
| } |
| |
| return this.resultStatus; |
| } |
| |
| protected void computeCompletionProposals(final RAssistInvocationContext context, final int mode, |
| final AssistProposalCollector proposals, final IProgressMonitor monitor) { |
| if (context.getModelInfo() == null) { |
| return; |
| } |
| |
| // Get node |
| final RAstNode node= getRAstNode(context); |
| |
| // Get prefix |
| final RElementName prefixName= context.getIdentifierElementName(); |
| if (prefixName == null) { |
| return; |
| } |
| |
| this.inString= (context.getInvocationContentType() == IRDocumentConstants.R_STRING_CONTENT_TYPE); |
| this.inDefault= !this.inString; |
| |
| this.pkgNamePrio= NA_PRIO; |
| this.helpTopicPrio= NA_PRIO; |
| |
| if (prefixName.getNextSegment() == null) { |
| final String help= checkHelp(context); |
| if (help != null) { |
| doComputeHelpTopicProposals(context, help, node, ARG_TYPE_PRIO, proposals); |
| |
| if (prefixName.getScope() == null) { |
| doComputePkgNameProposals(context, ARG_TYPE_NO_PRIO, proposals); |
| } |
| return; |
| } |
| |
| doComputeArgumentProposals(context, proposals, monitor); |
| if (this.inDefault) { |
| doComputeMainProposals(context, node, proposals, monitor); |
| } |
| |
| if (this.mode == 0 && this.inDefault) { |
| doComputeKeywordProposals(context, proposals, monitor); |
| } |
| |
| if (this.mode == 0) { |
| if (isPackageName(prefixName)) { |
| if (this.pkgNamePrio > 0) { |
| doComputePkgNameProposals(context, this.pkgNamePrio, proposals); |
| } |
| else if (!prefixName.getSegmentName().isEmpty()) { |
| doComputePkgNameProposals(context, ARG_TYPE_NO_PRIO, proposals); |
| } |
| } |
| { if (this.helpTopicPrio > 0) { |
| doComputeHelpTopicProposals(context, null, node, this.helpTopicPrio, proposals); |
| } |
| } |
| } |
| } |
| else { |
| doComputeSubProposals(context, node, proposals, monitor); |
| } |
| } |
| |
| |
| private String checkHelp(final RAssistInvocationContext context) { |
| try { |
| if (context.getIdentifierOffset() > 0 |
| && context.getDocument().getChar(context.getIdentifierOffset() - 1) == '?') { |
| final String prefix= context.computeIdentifierPrefix(context.getIdentifierOffset() - 1); |
| if (prefix != null && !prefix.isEmpty()) { |
| if (prefix.equals("class") || prefix.equals("methods")) { //$NON-NLS-1$ //$NON-NLS-2$ |
| return prefix; |
| } |
| return null; |
| } |
| return ""; //$NON-NLS-1$ |
| } |
| return null; |
| } |
| catch (final BadPartitioningException | BadLocationException e) { |
| return null; |
| } |
| } |
| |
| protected List<? extends IRLangElement> getChildren(final RAssistInvocationContext context, |
| IRLangElement e) { |
| if (e instanceof RReference) { |
| final RReference ref= (RReference) e; |
| final RObject rObject= ref.getResolvedRObject(); |
| if (rObject == null && e instanceof CombinedRElement && context.getTool() != null) { |
| context.getToolReferencesUtil().resolve(ref, 0); |
| } |
| if (rObject instanceof CombinedRElement) { |
| e= (CombinedRElement) rObject; |
| } |
| } |
| return e.getModelChildren(null); |
| } |
| |
| protected boolean doComputeArgumentProposals(final RAssistInvocationContext context, |
| final AssistProposalCollector proposals, |
| final IProgressMonitor monitor) { |
| final FCallInfo fCallInfo= context.getFCallInfo(); |
| if (fCallInfo != null) { |
| boolean argName= false; |
| boolean argValue= false; |
| final int argIdx= fCallInfo.getArgIdx(context.getInvocationOffset()); |
| if (argIdx >= 0) { |
| final FCall.Arg arg= fCallInfo.getArg(argIdx); |
| final int argBeginOffset= fCallInfo.getArgBeginOffset(argIdx); |
| |
| IDocument document= context.getDocument(); |
| int offsetShift= 0; |
| if (document instanceof IFragmentDocument) { |
| final IFragmentDocument inputDoc= (IFragmentDocument) document; |
| document= inputDoc.getMasterDocument(); |
| offsetShift= inputDoc.getOffsetInMasterDocument(); |
| } |
| |
| if (argBeginOffset != Integer.MIN_VALUE) { |
| final RHeuristicTokenScanner scanner= context.getRHeuristicTokenScanner(); |
| scanner.configure(document, NO_R_COMMENT_CONSTRAINT); |
| final int offset= context.getIdentifierOffset(); |
| if (argBeginOffset == offset |
| || scanner.findNonBlankForward( |
| argBeginOffset + offsetShift, offset + offsetShift, true) < 0 ) { |
| argName= (this.inDefault |
| && (context.getIdentifierElementName().getScope() == null) ); |
| argValue= true; |
| } |
| } |
| |
| if (!argValue && arg != null && arg.getAssignOffset() != AstNode.NA_OFFSET) { |
| final RHeuristicTokenScanner scanner= context.getRHeuristicTokenScanner(); |
| scanner.configure(document, NO_R_COMMENT_CONSTRAINT); |
| final int offset= context.getIdentifierOffset(); |
| if (argBeginOffset == offset |
| || scanner.findNonBlankForward( |
| arg.getAssignOffset() + offsetShift + 1, offset + offsetShift, true) < 0 ) { |
| argValue= true; |
| } |
| } |
| } |
| if (argName || argValue) { |
| doComputeFCallArgumentProposals(context, fCallInfo, argIdx, argName, argValue, |
| proposals); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| protected void doComputeMainProposals(final RAssistInvocationContext context, |
| final RAstNode node, |
| final AssistProposalCollector proposals, |
| final IProgressMonitor monitor) { |
| final RElementName prefixName= context.getIdentifierElementName(); |
| final String prefixSegmentName= nonNullAssert(prefixName.getSegmentName()); |
| |
| this.searchPath.init(context, node, getSearchMode(context), prefixName.getScope()); |
| |
| final RSymbolComparator.PrefixPattern pattern= new RSymbolComparator.PrefixPattern(prefixSegmentName); |
| final int offset= context.getIdentifierLastSegmentOffset(); |
| final Set<String> mainNames= new HashSet<>(); |
| final List<String> methodNames= new ArrayList<>(); |
| |
| for (final RFrameIterator iter= this.searchPath.iterator(); iter.hasNext();) { |
| final IRFrame envir= iter.next(); |
| |
| final List<? extends IRElement> elements= envir.getModelChildren(null); |
| for (final IRElement element : elements) { |
| final RElementName elementName= element.getElementName(); |
| final int c1type= (element.getElementType() & IModelElement.MASK_C1); |
| final boolean isRich= (c1type == IModelElement.C1_METHOD); |
| if ((isRich || c1type == IModelElement.C1_VARIABLE) |
| && isCompletable(elementName) |
| && pattern.matches(elementName.getSegmentName())) { |
| final int relevance= iter.getRelevance(); |
| if ((relevance < 0) && !isRich |
| && mainNames.contains(elementName.getSegmentName()) ) { |
| continue; |
| } |
| final IAssistCompletionProposal proposal= createProposal(context, |
| offset, elementName, element, relevance ); |
| if (proposal != null) { |
| if (elementName.getNextSegment() == null) { |
| if (isRich) { |
| methodNames.add(elementName.getSegmentName()); |
| } |
| else { |
| mainNames.add(elementName.getSegmentName()); |
| } |
| } |
| proposals.add(proposal); |
| } |
| } |
| } |
| } |
| |
| mainNames.addAll(methodNames); |
| for (final RFrameIterator iter= this.searchPath.iterator(); iter.hasNext();) { |
| final IRFrame envir= iter.next(); |
| if (envir instanceof IRFrameInSource) { |
| final IRFrameInSource sframe= (IRFrameInSource) envir; |
| final Set<String> elementNames= sframe.getAllAccessNames(); |
| for (final String candidate : elementNames) { |
| if (candidate != null |
| && pattern.matches(candidate) |
| && !mainNames.contains(candidate) |
| && !(candidate.equals(prefixSegmentName) |
| && (sframe.getAllAccessOf(candidate, false).size() <= 1) )) { |
| final IAssistCompletionProposal proposal= createProposal(context, |
| offset, candidate ); |
| if (proposal != null) { |
| mainNames.add(candidate); |
| proposals.add(proposal); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| private void doComputeKeywordProposals(final RAssistInvocationContext context, |
| final AssistProposalCollector proposals, |
| final IProgressMonitor monitor) { |
| final RElementName prefixName= context.getIdentifierElementName(); |
| final String prefixSegmentName= nonNullAssert(prefixName.getSegmentName()); |
| |
| if (prefixName.getScope() != null) { |
| return; |
| } |
| |
| final String prefixSource= context.getIdentifierPrefix(); |
| if (!prefixSegmentName.isEmpty() && prefixSource.charAt(0) != '`') { |
| final int offset= context.getIdentifierOffset(); |
| final List<String> keywords= fgKeywords; |
| for (final String keyword : keywords) { |
| if (keyword.regionMatches(true, 0, prefixSegmentName, 0, prefixSegmentName.length())) { |
| proposals.add(new RKeywordCompletionProposal(context, keyword, offset)); |
| } |
| } |
| } |
| } |
| |
| protected void doComputeSubProposals(final RAssistInvocationContext context, |
| final RAstNode node, |
| final AssistProposalCollector proposals, |
| final IProgressMonitor monitor) { |
| final RElementName prefixName= context.getIdentifierElementName(); |
| |
| this.searchPath.init(context, node, getSearchMode(context), prefixName.getScope()); |
| |
| int count= 0; |
| final String namePrefix; |
| { RElementName prefixSegment= prefixName; |
| while (true) { |
| count++; |
| if (prefixSegment.getNextSegment() != null) { |
| prefixSegment= prefixSegment.getNextSegment(); |
| continue; |
| } |
| else { |
| break; |
| } |
| } |
| namePrefix= (prefixSegment.getSegmentName() != null) ? |
| prefixSegment.getSegmentName() : ""; //$NON-NLS-1$ |
| } |
| final RSymbolComparator.PrefixPattern pattern= new RSymbolComparator.PrefixPattern(namePrefix); |
| final int offset= context.getIdentifierLastSegmentOffset(); |
| |
| final Set<String> mainNames= new HashSet<>(); |
| final List<String> methodNames= new ArrayList<>(); |
| |
| for (final RFrameIterator iter= this.searchPath.iterator(); iter.hasNext();) { |
| final IRFrame envir= iter.next(); |
| |
| final List<? extends IRLangElement> elements= envir.getModelChildren(null); |
| ITER_ELEMENTS: for (final IRLangElement rootElement : elements) { |
| final RElementName elementName= rootElement.getElementName(); |
| final int c1type= (rootElement.getElementType() & IModelElement.MASK_C1); |
| final boolean isRich= (c1type == IModelElement.C1_METHOD); |
| if (isRich || c1type == IModelElement.C1_VARIABLE) { |
| IRLangElement element= rootElement; |
| RElementName prefixSegment= prefixName; |
| RElementName elementSegment= elementName; |
| ITER_SEGMENTS: for (int i= 0; i < count-1; i++) { |
| if (elementSegment == null) { |
| final List<? extends IRLangElement> children= getChildren(context, element); |
| for (final IRLangElement child : children) { |
| elementSegment= child.getElementName(); |
| if (isCompletable(elementSegment) |
| && elementSegment.getSegmentName().equals(prefixSegment.getSegmentName())) { |
| element= child; |
| prefixSegment= prefixSegment.getNextSegment(); |
| elementSegment= elementSegment.getNextSegment(); |
| continue ITER_SEGMENTS; |
| } |
| } |
| continue ITER_ELEMENTS; |
| } |
| else { |
| if (isCompletable(elementSegment) |
| && elementSegment.getSegmentName().equals(prefixSegment.getSegmentName())) { |
| prefixSegment= prefixSegment.getNextSegment(); |
| elementSegment= elementSegment.getNextSegment(); |
| continue ITER_SEGMENTS; |
| } |
| continue ITER_ELEMENTS; |
| } |
| } |
| |
| final boolean childMode; |
| final List<? extends IRLangElement> children; |
| if (elementSegment == null) { |
| childMode= true; |
| children= getChildren(context, element); |
| } |
| else { |
| childMode= false; |
| children= Collections.singletonList(element); |
| } |
| for (final IRLangElement child : children) { |
| if (childMode) { |
| elementSegment= child.getElementName(); |
| } |
| final String candidate= elementSegment.getSegmentName(); |
| final int relevance= iter.getRelevance(); |
| if (isCompletable(elementSegment) |
| && pattern.matches(candidate) ) { |
| if ((relevance > 0) && !isRich |
| && mainNames.contains(candidate) ) { |
| continue ITER_ELEMENTS; |
| } |
| final IAssistCompletionProposal proposal= createProposal(context, |
| offset, elementSegment, child, relevance ); |
| if (proposal != null) { |
| if (elementSegment.getNextSegment() == null) { |
| if (isRich) { |
| methodNames.add(candidate); |
| } |
| else { |
| mainNames.add(candidate); |
| } |
| } |
| proposals.add(proposal); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| mainNames.addAll(methodNames); |
| for (final RFrameIterator iter= this.searchPath.iterator(); iter.hasNext();) { |
| final IRFrame envir= iter.next(); |
| if (envir instanceof IRFrameInSource) { |
| final IRFrameInSource sframe= (IRFrameInSource) envir; |
| final List<? extends RElementAccess> allAccess= sframe.getAllAccessOf( |
| prefixName.getSegmentName(), true ); |
| if (allAccess != null) { |
| ITER_ELEMENTS: for (final RElementAccess elementAccess : allAccess) { |
| RElementAccess elementSegment= elementAccess; |
| RElementName prefixSegment= prefixName; |
| ITER_SEGMENTS: for (int i= 0; i < count - 1; i++) { |
| if (isCompletable(elementSegment) |
| && elementSegment.getSegmentName().equals(prefixSegment.getSegmentName())) { |
| prefixSegment= prefixSegment.getNextSegment(); |
| elementSegment= elementSegment.getNextSegment(); |
| continue ITER_SEGMENTS; |
| } |
| continue ITER_ELEMENTS; |
| } |
| |
| if (elementSegment == null || elementSegment.isSlave()) { |
| continue ITER_ELEMENTS; |
| } |
| final String candidate= elementSegment.getSegmentName(); |
| if (candidate != null && isCompletable(elementSegment) |
| && pattern.matches(candidate) |
| && !mainNames.contains(candidate) |
| && !candidate.equals(namePrefix) ) { |
| final IAssistCompletionProposal proposal= createProposal(context, |
| offset, candidate ); |
| if (proposal != null) { |
| mainNames.add(candidate); |
| proposals.add(proposal); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| protected IAssistCompletionProposal createProposal(final RAssistInvocationContext context, |
| final int offset, final String name) { |
| return new RSimpleCompletionProposal(context, name, offset); |
| } |
| |
| protected IAssistCompletionProposal createProposal(final RAssistInvocationContext context, |
| final int offset, final RElementName elementName, final IRElement element, |
| final int relevance) { |
| return new RElementCompletionProposal(context, elementName, offset, element, relevance, |
| this.labelProvider ); |
| } |
| |
| public void doComputeContextProposals(final RAssistInvocationContext context, |
| final AssistProposalCollector proposals, final IProgressMonitor monitor) { |
| if (context.getModelInfo() == null) { |
| return; |
| } |
| |
| final RAssistInvocationContext.FCallInfo fCallInfo= context.getFCallInfo(); |
| if (fCallInfo != null) { |
| final FCallNamePattern pattern= new FCallNamePattern(fCallInfo.getAccess()) { |
| |
| final int infoOffset= Math.max(fCallInfo.getNode().getArgsOpenOffset() + 1, 0); |
| |
| @Override |
| protected void handleMatch(final IRMethod element, final IRFrame frame, |
| final RFrameIterator iterator) { |
| proposals.add(new RElementCompletionProposal.ContextInformationProposal(context, |
| element.getElementName(), this.infoOffset, element, |
| iterator.getRelevance(), |
| RElementCompletionComputer.this.labelProvider )); |
| } |
| }; |
| pattern.searchFDef(fCallInfo.getSearchPath(getSearchMode(context))); |
| } |
| } |
| |
| |
| protected final boolean isPackageArg(final String name) { |
| return ("package".equals(name)); |
| } |
| |
| private void doComputeFCallArgumentProposals(final RAssistInvocationContext context, |
| final FCallInfo fCallInfo, final int argIdx, final boolean argName, final boolean argValue, |
| final AssistProposalCollector proposals) { |
| final RElementName prefixName= context.getIdentifierElementName(); |
| final String prefixSegmentName= nonNullAssert(prefixName.getSegmentName()); |
| |
| final int offset= context.getIdentifierOffset(); |
| |
| if (argValue && !argName) { |
| final FCall.Arg arg= fCallInfo.getArg(argIdx); |
| if (arg != null && arg.hasName()) { |
| if (this.inString && isPackageArg(arg.getNameChild().getText())) { |
| this.pkgNamePrio= ARG_TYPE_PRIO; |
| } |
| } |
| } |
| |
| class FCallHandler extends FCallNamePattern { |
| |
| private final RCoreFunctions coreFunction; |
| |
| private final HashSet<String> argNames= new HashSet<>(); |
| |
| private int matchCount; |
| |
| |
| FCallHandler() { |
| super(fCallInfo.getAccess()); |
| |
| if (fCallInfo.getAccess().getNextSegment() == null) { |
| this.coreFunction= RCoreFunctions.DEFAULT; |
| } |
| else { |
| this.coreFunction= null; |
| } |
| } |
| |
| |
| private boolean isValidNameContext(final ArgsDefinition.Arg argDef) { |
| if (RElementCompletionComputer.this.inString) { |
| return ((argDef.type & ArgsDefinition.NAME_AS_STRING) != 0); |
| } |
| else { |
| return ((argDef.type & ArgsDefinition.NAME_AS_SYMBOL) != 0); |
| } |
| } |
| |
| private boolean checkArgsDef(final FCallArgMatch args, |
| final boolean guess, final int relevance) { |
| final ArgsDefinition.Arg argDef= args.getArgDef(argIdx); |
| if (argDef != null) { |
| final boolean typedDef= (argDef.type != 0); |
| if (RElementCompletionComputer.this.pkgNamePrio == NA_PRIO) { |
| if (typedDef) { |
| if ((argDef.type & ArgsDefinition.PACKAGE_NAME) != 0 |
| && isValidNameContext(argDef) ) { |
| RElementCompletionComputer.this.pkgNamePrio= ARG_TYPE_PRIO + relevance; |
| } |
| } |
| else if (guess && RElementCompletionComputer.this.inString && isPackageArg(argDef.name)) { |
| RElementCompletionComputer.this.pkgNamePrio= ARG_TYPE_PRIO + relevance; |
| } |
| } |
| if (RElementCompletionComputer.this.helpTopicPrio == NA_PRIO) { |
| if (typedDef) { |
| if ((argDef.type & ArgsDefinition.HELP_TOPIC_NAME) != 0 |
| && isValidNameContext(argDef) ) { |
| RElementCompletionComputer.this.helpTopicPrio= ARG_TYPE_PRIO + relevance; |
| } |
| } |
| } |
| return typedDef; |
| } |
| return false; |
| } |
| |
| @Override |
| public void searchFDef(final RFrameSearchPath searchPath) { |
| super.searchFDef(searchPath); |
| |
| if (this.matchCount == 0 |
| && this.coreFunction != null) { |
| final FCall.Args callArgs= fCallInfo.getNode().getArgsChild(); |
| |
| final ArgsDefinition coreDef= this.coreFunction.getArgs( |
| getElementName().getSegmentName() ); |
| if (coreDef != null) { |
| checkArgsDef(RAst.matchArgs(callArgs, coreDef), false, 0); |
| } |
| } |
| } |
| |
| @Override |
| protected void handleMatch(final IRMethod element, final IRFrame frame, |
| final RFrameIterator iterator) { |
| final ArgsDefinition argsDef= element.getArgsDefinition(); |
| if (argsDef == null) { |
| return; |
| } |
| |
| this.matchCount++; |
| |
| final int relevance= iterator.getRelevance(); |
| if (argName) { |
| for (int i= 0; i < argsDef.size(); i++) { |
| final ArgsDefinition.Arg arg= argsDef.get(i); |
| if (arg.name != null && arg.name.length() > 0 && !arg.name.equals("...")) { |
| if ((prefixSegmentName == null || arg.name.startsWith(prefixSegmentName)) |
| && this.argNames.add(arg.name)) { |
| final RElementName name= RElementName.create(RElementName.MAIN_DEFAULT, arg.name); |
| proposals.add(new RElementCompletionProposal.ArgumentProposal( |
| context, name, offset, element, |
| ARG_NAME_PRIO + relevance, |
| RElementCompletionComputer.this.labelProvider )); |
| } |
| } |
| } |
| } |
| if (argValue) { |
| final FCall.Args callArgs= fCallInfo.getNode().getArgsChild(); |
| if (!checkArgsDef(RAst.matchArgs(callArgs, argsDef), true, relevance) |
| && frame.getFrameType() == IRFrame.PACKAGE |
| && this.coreFunction != null |
| && this.coreFunction.getPackageNames().contains(frame.getElementName().getSegmentName()) ) { |
| final ArgsDefinition coreDef= this.coreFunction.getArgs( |
| getElementName().getSegmentName() ); |
| if (coreDef != null) { |
| checkArgsDef(RAst.matchArgs(callArgs, coreDef), false, relevance); |
| } |
| } |
| } |
| } |
| |
| } |
| final FCallHandler search= new FCallHandler(); |
| search.searchFDef(fCallInfo.getSearchPath(getSearchMode(context))); |
| } |
| |
| |
| protected IRCoreAccess getRCoreAccess() { |
| return this.editor.getRCoreAccess(); |
| } |
| |
| protected final @Nullable RPkgManagerDataset getRPkgDataset() { |
| final IRPkgManager manager= RCore.getRPkgManager(getRCoreAccess().getREnv()); |
| if (manager != null) { |
| return manager.getDataset(); |
| } |
| return null; |
| } |
| |
| protected final boolean isPackageName(final RElementName elementName) { |
| return ((elementName.getType() == RElementName.MAIN_DEFAULT |
| || RElementName.isPackageFacetScopeType(elementName.getType()) ) |
| && elementName.getNextSegment() == null ); |
| } |
| |
| protected final void doComputePkgNameProposals(final RAssistInvocationContext context, |
| final int prio, final AssistProposalCollector proposals) { |
| final RElementName prefixName= context.getIdentifierElementName(); |
| final String prefixSegmentName= nonNullAssert(prefixName.getSegmentName()); |
| |
| if (prefixName.getScope() != null |
| || !isSymbolCandidate(prefixSegmentName)) { |
| return; |
| } |
| |
| final RPkgManagerDataset rPkgDataset= getRPkgDataset(); |
| |
| final PrefixPattern pattern= new RSymbolComparator.PrefixPattern(prefixSegmentName); |
| final int offset= context.getInvocationOffset() - prefixSegmentName.length(); |
| |
| final Collection<String> envNames; |
| if (rPkgDataset != null) { |
| final RPkgCompilation<? extends RPkgBuilt> pkgs= rPkgDataset.getInstalled(); |
| envNames= pkgs.getNames(); |
| for (final String pkgName : envNames) { |
| if (pattern.matches(pkgName)) { |
| proposals.add(new RElementCompletionProposal.RPkgProposal(context, |
| RElementName.create(RElementName.SCOPE_PACKAGE, pkgName), offset, |
| pkgs.getFirst(pkgName), prio )); |
| } |
| } |
| } |
| else { |
| envNames= ImCollections.emptySet(); |
| } |
| |
| final Set<String> workspaceNames= RModel.getRModelManager().getPkgNames(); |
| for (final String pkgName : workspaceNames) { |
| if (!envNames.contains(pkgName) && pattern.matches(pkgName)) { |
| proposals.add(new RElementCompletionProposal.RPkgProposal(context, |
| RElementName.create(RElementName.SCOPE_PACKAGE, pkgName), offset, |
| null, prio )); |
| } |
| } |
| } |
| |
| protected final void doComputeHelpTopicProposals(final RAssistInvocationContext context, |
| final String topicType, final RAstNode node, |
| final int prio, final AssistProposalCollector proposals) { |
| // (topic != null) => ? / (topic == null) => help() |
| final RElementName prefixName= context.getIdentifierElementName(); |
| final String prefixSegmentName= nonNullAssert(prefixName.getSegmentName()); |
| |
| if (topicType == null && (prefixName.getScope() != null || prefixSegmentName.isEmpty())) { |
| return; |
| } |
| |
| final REnv rEnv= getRCoreAccess().getREnv(); |
| if (rEnv == null) { |
| return; |
| } |
| |
| final Map<String, Object> matches= new HashMap<>(); |
| |
| final RHelpManager rHelpManager= RCore.getRHelpManager(); |
| |
| final REnvHelp help= rHelpManager.getHelp(rEnv); |
| if (help != null) { |
| try { |
| final PrefixPattern pattern= new RSymbolComparator.PrefixPattern(prefixSegmentName); |
| final BiFunction<Object, Object, Object> mergeFun= |
| (final Object oldValue, final Object newValue) -> { |
| List<Object> list; |
| if (oldValue instanceof List) { |
| list= (List<Object>) oldValue; |
| } |
| else { |
| list= new ArrayList<>(4); |
| list.add(oldValue); |
| } |
| list.add(newValue); |
| return list; |
| }; |
| this.searchPath.init(context, node, getSearchMode(context), prefixName.getScope()); |
| final Set<String> pkgNames= new HashSet<>(); |
| for (final IRFrame frame : this.searchPath) { |
| if (frame.getFrameType() == IRFrame.PACKAGE) { |
| final String pkgName= frame.getElementName().getSegmentName(); |
| if (pkgName != null && pkgNames.add(pkgName)) { |
| final RPkgHelp pkgHelp= help.getPkgHelp(pkgName); |
| if (pkgHelp != null) { |
| for (final RHelpTopicEntry topicEntry : pkgHelp.getTopics()) { |
| final String topic= topicEntry.getTopic(); |
| if (pattern.matches(topic)) { |
| matches.merge(topic, pkgName, mergeFun); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| finally { |
| help.unlock(); |
| } |
| } |
| |
| final int offset= (context.getInvocationContentType() != IRDocumentConstants.R_DEFAULT_CONTENT_TYPE) ? |
| context.getIdentifierLastSegmentOffset() + 1 : |
| context.getIdentifierLastSegmentOffset(); |
| for (final Map.Entry<String, Object> match : matches.entrySet()) { |
| proposals.add(new RSimpleCompletionProposal.RHelpTopicCompletionProposal( |
| context, match.getKey(), toString(match.getValue()), |
| offset, prio )); |
| } |
| } |
| |
| } |