blob: 8034a7454ca11c9334a3024db854e24218f4eb57 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2009, 2021 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.TreePartitionNode;
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.RAsts;
import org.eclipse.statet.r.core.rsource.ast.RAsts.FCallArgMatch;
import org.eclipse.statet.r.core.rsource.ast.RParser;
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 TreePartitionNode 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 RParser rParser= new RParser(AstInfo.LEVEL_MODEL_DEFAULT);
args= rParser.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= RAsts.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;
}
}