blob: da294e459b0fba6e2481eb9a72284ded6fb445bf [file] [log] [blame]
/*=============================================================================#
# 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 org.eclipse.jface.text.AbstractDocument;
import org.eclipse.jface.text.IDocument;
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.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.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;
@NonNullByDefault
public class RContextInformationValidator implements IContextInformationValidator, IContextInformationPresenter {
private ITextViewer viewer;
private int startOffset;
private @Nullable RArgumentListContextInformation info;
private int currentParameter;
private long scannedArgsStamp;
private @Nullable Args scannedArgs;
private int lastPresentation= -2;
public RContextInformationValidator() {
}
@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) {
this.info= (RArgumentListContextInformation) info;
this.currentParameter= -1;
}
else {
this.info= null;
return;
}
this.viewer= viewer;
this.startOffset= offset;
}
@Override
public boolean isContextInformationValid(final int offset) {
final RArgumentListContextInformation info= this.info;
if (info == null) {
return false;
}
final IDocument document= this.viewer.getDocument();
if (offset < this.startOffset || offset > document.getLength()) {
return false;
}
final Args args= getScannedArgs();
if (args != null) {
return (offset <= args.getEndOffset());
}
return (offset == this.startOffset);
}
@Override
public boolean updatePresentation(final int offset, final TextPresentation presentation) {
final RArgumentListContextInformation info= this.info;
if (info == null) {
return false;
}
final ArgsDefinition args= info.getArguments();
if (args != null && args.size() > 0) {
final int argIndex= getCurrentArgInFDef(offset);
final int[] idxs= info.getInformationDisplayStringArgumentIdxs();
if (argIndex >= 0 && argIndex < idxs.length) {
if (argIndex == this.lastPresentation) {
return false;
}
final int start= idxs[argIndex];
final int stop= (argIndex+1 < idxs.length) ? idxs[argIndex+1] : info.getInformationDisplayString().length();
presentation.clear();
presentation.addStyleRange(new StyleRange(start, stop-start, null, null, SWT.BOLD));
this.lastPresentation= argIndex;
return true;
}
}
if (this.lastPresentation >= 0) {
presentation.clear();
this.lastPresentation= -1;
return true;
}
return false;
}
private @Nullable Args getScannedArgs() {
AbstractDocument document= (AbstractDocument)this.viewer.getDocument();
final int startOffset= this.info.getCallArgsOffset();
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 {
final String text= document.get(docStartOffset, Math.min(0x800, document.getLength() - docStartOffset));
final RScanner scanner= new RScanner(AstInfo.LEVEL_MODEL_DEFAULT);
this.scannedArgs= scanner.scanFCallArgs(
new OffsetStringParserInput(text, startOffset)
.init(startOffset, startOffset + text.length()),
true );
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.getArguments());
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;
}
}