| /*=============================================================================# |
| # Copyright (c) 2009, 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.internal.r.ui.editors; |
| |
| import static org.eclipse.statet.jcommons.lang.ObjectUtils.nonNullAssert; |
| |
| import org.eclipse.jface.text.AbstractDocument; |
| import org.eclipse.jface.text.ITextViewer; |
| import org.eclipse.jface.text.TextPresentation; |
| import org.eclipse.jface.text.contentassist.IContextInformation; |
| import org.eclipse.jface.text.contentassist.IContextInformationPresenter; |
| import org.eclipse.jface.text.contentassist.IContextInformationValidator; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.custom.StyleRange; |
| |
| import org.eclipse.statet.jcommons.lang.NonNullByDefault; |
| import org.eclipse.statet.jcommons.lang.Nullable; |
| import org.eclipse.statet.jcommons.text.core.input.OffsetStringParserInput; |
| |
| import org.eclipse.statet.ecommons.text.core.FragmentDocument; |
| import org.eclipse.statet.ecommons.text.core.treepartitioner.ITreePartitionNode; |
| import org.eclipse.statet.ecommons.text.core.treepartitioner.TreePartitionUtils; |
| |
| import org.eclipse.statet.ltk.ast.core.AstInfo; |
| import org.eclipse.statet.ltk.ui.sourceediting.assist.AssistCompletionInformationProposalWrapper; |
| import org.eclipse.statet.r.core.model.ArgsDefinition; |
| import org.eclipse.statet.r.core.rsource.ast.FCall; |
| import org.eclipse.statet.r.core.rsource.ast.FCall.Args; |
| 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.RScanner; |
| import org.eclipse.statet.r.core.source.RPartitionNodeScanner; |
| import org.eclipse.statet.r.ui.editors.IRSourceEditor; |
| |
| |
| @NonNullByDefault |
| public class RContextInformationValidator implements IContextInformationValidator, IContextInformationPresenter { |
| |
| |
| private final IRSourceEditor sourceEditor; |
| |
| private @Nullable RArgumentListContextInformation info; |
| |
| private long scannedArgsStamp; |
| private @Nullable Args scannedArgs; |
| |
| private int lastPresentation= -2; |
| |
| |
| public RContextInformationValidator(final IRSourceEditor sourceEditor) { |
| this.sourceEditor= sourceEditor; |
| } |
| |
| |
| @Override |
| public void install(IContextInformation info, final ITextViewer viewer, final int offset) { |
| if (info instanceof AssistCompletionInformationProposalWrapper) { |
| info= ((AssistCompletionInformationProposalWrapper)info).getContextInformation(); |
| } |
| |
| this.scannedArgs= null; |
| this.lastPresentation= -2; |
| |
| if (info instanceof RArgumentListContextInformation |
| && viewer == this.sourceEditor.getViewer()) { |
| this.info= (RArgumentListContextInformation)info; |
| } |
| else { |
| this.info= null; |
| return; |
| } |
| } |
| |
| @Override |
| public boolean isContextInformationValid(final int offset) { |
| final RArgumentListContextInformation info= this.info; |
| if (info == null) { |
| return false; |
| } |
| if (offset < info.getContextInformationPosition() |
| || offset > this.sourceEditor.getViewer().getDocument().getLength()) { |
| return false; |
| } |
| final Args args= getScannedArgs(); |
| if (args != null) { |
| return (offset <= args.getEndOffset()); |
| } |
| return (offset == info.getContextInformationPosition()); |
| } |
| |
| @Override |
| public boolean updatePresentation(final int offset, final TextPresentation presentation) { |
| final RArgumentListContextInformation info= this.info; |
| if (info == null) { |
| return false; |
| } |
| final ArgsDefinition args= info.getArgsDefinition(); |
| if (args != null && args.size() > 0) { |
| final int argIdx= getCurrentArgInFDef(offset); |
| final int[] idxs= info.getInformationDisplayStringArgumentIdxs(); |
| if (argIdx >= 0 && argIdx < idxs.length) { |
| if (argIdx == this.lastPresentation) { |
| return false; |
| } |
| final int start= idxs[argIdx]; |
| final int stop= (argIdx + 1 < idxs.length) ? idxs[argIdx + 1] : info.getInformationDisplayString().length(); |
| presentation.clear(); |
| presentation.addStyleRange(new StyleRange(start, stop - start, null, null, SWT.BOLD)); |
| this.lastPresentation= argIdx; |
| return true; |
| } |
| } |
| if (this.lastPresentation >= 0) { |
| presentation.clear(); |
| this.lastPresentation= -1; |
| return true; |
| } |
| return false; |
| } |
| |
| |
| private @Nullable Args getScannedArgs() { |
| final RArgumentListContextInformation info= nonNullAssert(this.info); |
| final int startOffset= info.getCallArgsOffset(); |
| AbstractDocument document= (AbstractDocument)this.sourceEditor.getViewer().getDocument(); |
| int docStartOffset= startOffset; |
| if (document instanceof FragmentDocument) { |
| final FragmentDocument fragmentDoc= (FragmentDocument)document; |
| document= fragmentDoc.getMasterDocument(); |
| docStartOffset+= fragmentDoc.getOffsetInMasterDocument(); |
| } |
| if (docStartOffset < 0) { |
| docStartOffset= 0; |
| } |
| final long stamp= document.getModificationStamp(); |
| if (this.scannedArgs == null || this.scannedArgsStamp != stamp) { |
| try { |
| FCall.Args args= null; |
| final ITreePartitionNode rRootNode= RPartitionNodeScanner.findRRootNode( |
| TreePartitionUtils.getNode(document, this.sourceEditor.getDocumentContentInfo().getPartitioning(), |
| docStartOffset, true)); |
| if (rRootNode != null) { |
| final int docEndOffset= Math.min(0x800, rRootNode.getEndOffset() - docStartOffset); |
| final String text= document.get(docStartOffset, docEndOffset); |
| final RScanner scanner= new RScanner(AstInfo.LEVEL_MODEL_DEFAULT); |
| args= scanner.scanFCallArgs(new OffsetStringParserInput(text, startOffset) |
| .init(startOffset, startOffset + text.length()), |
| true ); |
| } |
| this.scannedArgs= args; |
| this.scannedArgsStamp= stamp; |
| } |
| catch (final Exception e) { |
| this.scannedArgs= null; |
| } |
| } |
| |
| return this.scannedArgs; |
| } |
| |
| |
| private int getCurrentArgInFDef(final int offset) { |
| final Args args= getScannedArgs(); |
| if (args != null) { |
| final int callArgIdx= getCurrentArgInFCall(args, offset); |
| if (callArgIdx >= 0) { |
| final FCallArgMatch match= RAst.matchArgs(args, this.info.getArgsDefinition()); |
| final ArgsDefinition.Arg argDef= match.getArgDef(callArgIdx); |
| if (argDef != null) { |
| return argDef.index; |
| } |
| } |
| } |
| return -1; |
| } |
| |
| private static int getCurrentArgInFCall(final Args args, final int offset) { |
| final int last= args.getChildCount() - 1; |
| if (last == -1) { |
| return 0; |
| } |
| for (int i= 0; i < last; i++) { |
| if (offset <= args.getSeparatorOffset(i)) { |
| return i; |
| } |
| } |
| return last; |
| } |
| |
| } |