blob: 4ad19660e0bbfe28e668349aa5e5cb26c109b6b3 [file] [log] [blame]
/*=============================================================================#
# 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.internal.r.ui.editors;
import java.util.List;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.statet.jcommons.collections.ImCollections;
import org.eclipse.statet.jcommons.lang.NonNull;
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.ltk.ui.LTKUI;
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.ContentAssistComputer;
import org.eclipse.statet.ltk.ui.sourceediting.assist.SimpleCompletionProposal;
import org.eclipse.statet.ltk.ui.sourceediting.assist.SourceProposal.ProposalParameters;
@NonNullByDefault
public class RoxygenCompletionComputer implements ContentAssistComputer {
private static final List<String> TAG_COMMANDS;
static {
@SuppressWarnings("nls")
final String[] tags= new String[] {
"docType",
"export",
"exportClass",
"exportMethod",
"exportPattern",
"import",
"importFrom",
"importClassesFrom",
"importMethodsFrom",
"name",
"aliases",
"title",
"usage",
"references",
"note",
"include",
"slot",
"param",
"return",
"returnType",
"seealso",
"example",
"examples",
"author",
"concept",
"keywords",
"method",
"prototype",
"S3method", // deprecated 4.0
"S3class",
"listObject",
"attributeObject",
"environmentObject",
"noRd",
"useDynLib",
"rdname", // 2.0
"template", // 2.0
"section", // 2.0
"description", // 2.0
"details", // 2.0
"family", // 2.0
"inheritParams", // 2.0
"format", // 2.0
"source", // 2.1
"encoding", // 2.2
"describeIn", // since 4.0
"field", // since 4.0 (for fields of reference classes)
};
final String[] commands= new @NonNull String[tags.length];
for (int i= 0; i < commands.length; i++) {
commands[i]= '@' + tags[i];
}
TAG_COMMANDS= ImCollections.newList(commands);
}
private static class TagProposal extends SimpleCompletionProposal {
public TagProposal(final ProposalParameters<?> parameters, final String keyword) {
super(parameters, keyword);
}
@Override
protected int computeReplacementLength(final int replacementOffset, final Point selection, final int caretOffset, final boolean overwrite) throws BadLocationException {
int end= Math.max(caretOffset, selection.x + selection.y);
if (overwrite) {
final IDocument document= getInvocationContext().getSourceViewer().getDocument();
while (end < document.getLength()) {
if (Character.isLetterOrDigit(document.getChar(end))) {
end++;
continue;
}
break;
}
}
return (end - replacementOffset);
}
@Override
public Image getImage() {
return LTKUI.getImages().get(LTKUI.OBJ_TEXT_AT_TAG_IMAGE_ID);
}
@Override
public boolean isAutoInsertable() {
return true;
}
@Override
protected void doApply(final char trigger, final int stateMask, final int caretOffset,
final int replacementOffset, final int replacementLength) throws BadLocationException {
final AssistInvocationContext context= getInvocationContext();
final IDocument document= context.getDocument();
final ApplyData applyData= getApplyData();
String replacement= getName();
final int cursor= replacement.length() + 1;
if (replacementOffset + replacementLength == document.getLength()
|| document.getChar(replacementOffset + replacementLength) != ' ') {
replacement= replacement + ' ';
}
document.replace(replacementOffset, replacementLength, replacement);
applyData.setSelection(replacementOffset + cursor);
}
}
private int searchMatchRules;
public RoxygenCompletionComputer() {
}
@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() {
}
protected int getSearchMatchRules() {
return this.searchMatchRules;
}
@Override
public void computeCompletionProposals(final AssistInvocationContext context, final int mode,
final AssistProposalCollector proposals, final IProgressMonitor monitor) {
final String tagPrefix= getTagPrefix(context);
if (tagPrefix != null) {
doComputeTagProposals(context, tagPrefix, proposals, monitor);
}
}
@Override
public void computeInformationProposals(final AssistInvocationContext context,
final AssistProposalCollector proposals, final IProgressMonitor monitor) {
}
private @Nullable String getTagPrefix(final AssistInvocationContext context) {
try {
final IDocument document= context.getSourceViewer().getDocument();
final int start= Math.max(context.getInvocationOffset() - 20, 0); // max keyword length incl
final String s= document.get(start, context.getInvocationOffset()-start);
final int last= s.length()-1;
int i= last;
while (i >= 0) {
final char c= s.charAt(i);
if (c == '@') {
return s.substring(i);
}
if (!isRoxygenTagChar(c)) {
return (i == last) ? "" : null; //$NON-NLS-1$
}
i--;
}
return null;
}
catch (final BadLocationException e) {
return null;
}
}
private boolean isRoxygenTagChar(final int c) {
if ((c >= 0x41 && c <= 0x5A) || (c >= 0x61 && c <= 0x7A)) {
return true;
}
final int type= Character.getType(c);
return (type > 0) && (type < 12 || type > 19);
}
private void doComputeTagProposals(final AssistInvocationContext context, final String prefix,
final AssistProposalCollector proposals, final IProgressMonitor monitor) {
final ProposalParameters<?> parameters= new ProposalParameters<>(
context, context.getInvocationOffset() - prefix.length(),
new SearchPattern(getSearchMatchRules(), prefix) );
final List<String> keywords= TAG_COMMANDS;
for (final String keyword : keywords) {
if (parameters.matchesNamePattern(keyword)) {
proposals.add(new TagProposal(parameters, keyword));
}
}
}
}