| /*=============================================================================# |
| # 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 org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.jface.text.BadLocationException; |
| import org.eclipse.jface.text.BadPartitioningException; |
| import org.eclipse.jface.text.IDocument; |
| |
| import org.eclipse.statet.jcommons.collections.ImCollections; |
| import org.eclipse.statet.jcommons.collections.ImList; |
| import org.eclipse.statet.jcommons.lang.NonNullByDefault; |
| import org.eclipse.statet.jcommons.lang.Nullable; |
| import org.eclipse.statet.jcommons.text.core.SearchPattern; |
| |
| import org.eclipse.statet.ecommons.text.core.FragmentDocument; |
| import org.eclipse.statet.ecommons.text.core.PartitionConstraint; |
| |
| 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.RElementCompletionProposal.RElementProposalParameters; |
| import org.eclipse.statet.internal.r.ui.editors.RHelpTopicCompletionProposal; |
| import org.eclipse.statet.internal.r.ui.editors.RPkgCompletionProposal; |
| 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.ElementLabelProvider; |
| 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.AssistProposal; |
| 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.ContentAssistComputer; |
| import org.eclipse.statet.ltk.ui.sourceediting.assist.SourceProposal.ProposalParameters; |
| import org.eclipse.statet.r.core.RCore; |
| import org.eclipse.statet.r.core.RSearchPattern; |
| import org.eclipse.statet.r.core.RSymbolComparator; |
| 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.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.runtime.RPkgManagerDataset; |
| |
| |
| @NonNullByDefault |
| public class RElementCompletionComputer implements ContentAssistComputer { |
| |
| |
| private static final PartitionConstraint NO_R_COMMENT_CONSTRAINT= new PartitionConstraint() { |
| @Override |
| public boolean matches(final String partitionType) { |
| return (partitionType != IRDocumentConstants.R_COMMENT_CONTENT_TYPE); |
| }; |
| }; |
| |
| |
| private static final ImList<String> fgKeywords; |
| static { |
| final ArrayList<String> list= new ArrayList<>(); |
| Collections.addAll(list, RTokens.CONSTANT_WORDS); |
| Collections.addAll(list, RTokens.FLOWCONTROL_WORDS); |
| fgKeywords= ImCollections.toList(list, RSymbolComparator.R_NAMES_COLLATOR); |
| } |
| |
| 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 ElementLabelProvider labelProvider= new RLabelProvider(RLabelProvider.NAMESPACE); |
| |
| private final int mode; |
| |
| private int searchMatchRules; |
| |
| private final RFrameSearchPath searchPath= new RFrameSearchPath(); |
| |
| private boolean inDefault; |
| private boolean inString; |
| |
| private int pkgNamePrio; |
| private int helpTopicPrio; |
| |
| |
| protected RElementCompletionComputer(final int mode) { |
| this.mode= mode; |
| } |
| |
| public RElementCompletionComputer() { |
| this(0); |
| } |
| |
| |
| @Override |
| public void onSessionStarted(final ISourceEditor editor, final ContentAssist assist) { |
| int matchRules= SearchPattern.PREFIX_MATCH; |
| if (assist.getShowSubstringMatches()) { |
| matchRules |= SearchPattern.SUBSTRING_MATCH; |
| } |
| this.searchMatchRules= matchRules; |
| } |
| |
| @Override |
| public void onSessionEnded() { |
| this.searchPath.clear(); |
| } |
| |
| |
| protected final int getSearchMode(final RAssistInvocationContext context) { |
| if (this.mode != 0) { |
| return this.mode; |
| } |
| return context.getDefaultRFrameSearchMode(); |
| } |
| |
| protected int getSearchMatchRules() { |
| return this.searchMatchRules; |
| } |
| |
| |
| 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(@Nullable 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 @Nullable 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 void computeCompletionProposals(final AssistInvocationContext context, |
| final int mode, final AssistProposalCollector proposals, |
| final IProgressMonitor monitor) { |
| if (context instanceof RAssistInvocationContext) { |
| computeCompletionProposals((RAssistInvocationContext) context, mode, proposals, |
| monitor ); |
| } |
| } |
| |
| @Override |
| public void computeInformationProposals(final AssistInvocationContext context, |
| final AssistProposalCollector proposals, final IProgressMonitor monitor) { |
| if (context instanceof RAssistInvocationContext) { |
| doComputeContextProposals((RAssistInvocationContext) context, proposals, monitor); |
| } |
| } |
| |
| 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 @Nullable 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 FragmentDocument) { |
| final FragmentDocument inputDoc= (FragmentDocument) 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 RElementProposalParameters parameters= new RElementProposalParameters( |
| context, context.getIdentifierLastSegmentOffset(), |
| new RSearchPattern(getSearchMatchRules(), prefixSegmentName), |
| this.labelProvider ); |
| |
| 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(); |
| parameters.baseRelevance= iter.getRelevance(); |
| |
| 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) |
| && parameters.matchesNamePattern(elementName.getSegmentName()) ) { |
| if ((parameters.baseRelevance < 0) && !isRich |
| && mainNames.contains(elementName.getSegmentName()) ) { |
| continue; |
| } |
| parameters.replacementName= elementName; |
| parameters.element= element; |
| |
| final AssistProposal proposal= new RElementCompletionProposal(parameters); |
| 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(); |
| parameters.baseRelevance= 0; |
| |
| if (envir instanceof IRFrameInSource) { |
| final IRFrameInSource sframe= (IRFrameInSource) envir; |
| final Set<String> elementNames= sframe.getAllAccessNames(); |
| for (final String candidate : elementNames) { |
| if (candidate != null |
| && !(candidate.equals(prefixSegmentName) |
| && (sframe.getAllAccessOf(candidate, false).size() <= 1) ) |
| && !mainNames.contains(candidate) |
| && parameters.matchesNamePattern(candidate) ) { |
| |
| final AssistProposal proposal= new RSimpleCompletionProposal(parameters, |
| candidate ); |
| 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 ProposalParameters<RAssistInvocationContext> parameters= new ProposalParameters<>( |
| context, context.getIdentifierOffset(), |
| new RSearchPattern(SearchPattern.PREFIX_MATCH, prefixSegmentName) ); |
| |
| final List<String> keywords= fgKeywords; |
| for (final String keyword : keywords) { |
| if (parameters.matchesNamePattern(keyword)) { |
| proposals.add(new RSimpleCompletionProposal(parameters, keyword)); |
| } |
| } |
| } |
| } |
| |
| 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 RElementProposalParameters parameters= new RElementProposalParameters( |
| context, context.getIdentifierLastSegmentOffset(), |
| new RSearchPattern(getSearchMatchRules(), namePrefix), |
| this.labelProvider ); |
| |
| 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(); |
| parameters.baseRelevance= iter.getRelevance(); |
| |
| 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(); |
| if (isCompletable(elementSegment) |
| && parameters.matchesNamePattern(candidate) ) { |
| if ((parameters.baseRelevance > 0) && !isRich |
| && mainNames.contains(candidate) ) { |
| continue ITER_ELEMENTS; |
| } |
| parameters.replacementName= elementSegment; |
| parameters.element= child; |
| |
| final AssistProposal proposal= new RElementCompletionProposal(parameters); |
| 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(); |
| parameters.baseRelevance= 0; |
| |
| 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) |
| && !candidate.equals(namePrefix) |
| && !mainNames.contains(candidate) |
| && parameters.matchesNamePattern(candidate) ) { |
| |
| final AssistProposal proposal= new RSimpleCompletionProposal(parameters, |
| candidate ); |
| mainNames.add(candidate); |
| proposals.add(proposal); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| 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 RElementProposalParameters parameters= new RElementProposalParameters( |
| context, Math.max(fCallInfo.getNode().getArgsOpenOffset() + 1, 0), |
| this.labelProvider ); |
| |
| final FCallNamePattern pattern= new FCallNamePattern(fCallInfo.getAccess()) { |
| @Override |
| protected void handleMatch(final IRMethod element, final IRFrame frame, |
| final RFrameIterator iterator) { |
| parameters.baseRelevance= iterator.getRelevance(); |
| parameters.replacementName= element.getElementName(); |
| parameters.element= element; |
| |
| proposals.add(new RElementCompletionProposal.ContextInformationProposal(parameters)); |
| } |
| }; |
| 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()); |
| |
| 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; |
| } |
| } |
| } |
| |
| final RElementProposalParameters parameters= new RElementProposalParameters( |
| context, context.getIdentifierOffset(), |
| new RSearchPattern(getSearchMatchRules(), prefixSegmentName), |
| this.labelProvider ); |
| |
| class FCallHandler extends FCallNamePattern { |
| |
| private final @Nullable 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 iter) { |
| final ArgsDefinition argsDef= element.getArgsDefinition(); |
| if (argsDef == null) { |
| return; |
| } |
| |
| final int contextRelevance= iter.getRelevance(); |
| this.matchCount++; |
| |
| 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 (parameters.matchesNamePattern(arg.name) |
| && this.argNames.add(arg.name)) { |
| final RElementName name= RElementName.create(RElementName.MAIN_DEFAULT, arg.name); |
| parameters.baseRelevance= contextRelevance + ARG_NAME_PRIO; |
| parameters.replacementName= name; |
| parameters.element= element; |
| |
| proposals.add(new RElementCompletionProposal.ArgumentProposal(parameters)); |
| } |
| } |
| } |
| } |
| if (argValue) { |
| final FCall.Args callArgs= fCallInfo.getNode().getArgsChild(); |
| if (!checkArgsDef(RAst.matchArgs(callArgs, argsDef), true, contextRelevance) |
| && 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, contextRelevance); |
| } |
| } |
| } |
| } |
| |
| } |
| final FCallHandler search= new FCallHandler(); |
| search.searchFDef(fCallInfo.getSearchPath(getSearchMode(context))); |
| } |
| |
| |
| protected final @Nullable RPkgManagerDataset getRPkgDataset(final RAssistInvocationContext context) { |
| final IRPkgManager manager= RCore.getRPkgManager(context.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 RElementProposalParameters parameters= new RElementProposalParameters( |
| context, context.getInvocationOffset() - prefixSegmentName.length(), |
| new RSearchPattern(getSearchMatchRules(), prefixSegmentName), prio, |
| this.labelProvider ); |
| |
| final RPkgManagerDataset rPkgDataset= getRPkgDataset(context); |
| |
| final Collection<String> envNames; |
| if (rPkgDataset != null) { |
| final RPkgCompilation<? extends RPkgBuilt> pkgs= rPkgDataset.getInstalled(); |
| envNames= pkgs.getNames(); |
| for (final String pkgName : envNames) { |
| if (parameters.matchesNamePattern(pkgName)) { |
| parameters.replacementName= RElementName.create(RElementName.SCOPE_PACKAGE, pkgName); |
| |
| final AssistProposal proposal= new RPkgCompletionProposal(parameters, |
| pkgs.getFirst(pkgName) ); |
| proposals.add(proposal); |
| } |
| } |
| } |
| else { |
| envNames= ImCollections.emptySet(); |
| } |
| |
| final Set<String> workspaceNames= RModel.getRModelManager().getPkgNames(); |
| for (final String pkgName : workspaceNames) { |
| if (!envNames.contains(pkgName) |
| && parameters.matchesNamePattern(pkgName) ) { |
| parameters.replacementName= RElementName.create(RElementName.SCOPE_PACKAGE, pkgName); |
| |
| proposals.add(new RPkgCompletionProposal(parameters, null)); |
| } |
| } |
| } |
| |
| protected final void doComputeHelpTopicProposals(final RAssistInvocationContext context, |
| final @Nullable 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= context.getRCoreAccess().getREnv(); |
| if (rEnv == null) { |
| return; |
| } |
| |
| final RHelpManager rHelpManager= RCore.getRHelpManager(); |
| |
| final REnvHelp help= rHelpManager.getHelp(rEnv); |
| if (help != null) { |
| try { |
| final ProposalParameters<RAssistInvocationContext> parameters= new ProposalParameters<>( |
| context, (context.getInvocationContentType() != IRDocumentConstants.R_DEFAULT_CONTENT_TYPE) ? |
| context.getIdentifierLastSegmentOffset() + 1 : |
| context.getIdentifierLastSegmentOffset(), |
| new RSearchPattern(getSearchMatchRules(), prefixSegmentName), |
| prio ); |
| final Map<String, RHelpTopicCompletionProposal> map= new HashMap<>(); |
| 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 (parameters.matchesNamePattern(topic)) { |
| |
| RHelpTopicCompletionProposal proposal= map.get(topic); |
| if (proposal == null) { |
| proposal= new RHelpTopicCompletionProposal( |
| parameters, topic, pkgName ); |
| map.put(topic, proposal); |
| proposals.add(proposal); |
| } |
| else { |
| proposal.addPackage(pkgName); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| finally { |
| help.unlock(); |
| } |
| } |
| } |
| |
| } |