blob: 43c8c42d0b6bb65c0306c3c751c83722468ef207 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2005, 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.ltk.ui.sourceediting.assist;
import static org.eclipse.statet.jcommons.lang.ObjectUtils.nonNullAssert;
import java.util.List;
import java.util.regex.Pattern;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.text.templates.GlobalTemplateVariables;
import org.eclipse.jface.text.templates.GlobalTemplateVariables.LineSelection;
import org.eclipse.jface.text.templates.GlobalTemplateVariables.WordSelection;
import org.eclipse.jface.text.templates.Template;
import org.eclipse.jface.text.templates.TemplateContextType;
import org.eclipse.jface.text.templates.TemplateException;
import org.eclipse.jface.text.templates.persistence.TemplateStore;
import org.eclipse.swt.graphics.Image;
import org.eclipse.text.templates.ContextTypeRegistry;
import org.eclipse.statet.jcommons.collections.ImCollections;
import org.eclipse.statet.jcommons.lang.NonNullByDefault;
import org.eclipse.statet.jcommons.lang.Nullable;
import org.eclipse.statet.jcommons.text.core.BasicTextRegion;
import org.eclipse.statet.jcommons.text.core.SearchPattern;
import org.eclipse.statet.jcommons.text.core.TextRegion;
import org.eclipse.statet.internal.ltk.ui.LtkUIPlugin;
import org.eclipse.statet.ltk.ui.LtkUI;
import org.eclipse.statet.ltk.ui.sourceediting.SourceEditor;
import org.eclipse.statet.ltk.ui.sourceediting.assist.SourceProposal.ProposalParameters;
import org.eclipse.statet.ltk.ui.sourceediting.assist.TemplateProposal.TemplateProposalParameters;
import org.eclipse.statet.ltk.ui.templates.SourceEditorTemplateContext;
/**
* Content assist computer for editor templates.
*/
@NonNullByDefault
public abstract class TemplateCompletionComputer implements ContentAssistComputer {
private static final byte SELECTION_NONE= 0;
private static final byte SELECTION_INLINE= 1;
private static final byte SELECTION_MULTILINE= 2;
private static final Pattern SELECTION_INLINE_PATTERN= Pattern.compile(
"\\$\\{" + WordSelection.NAME + "\\}"); //$NON-NLS-1$ //$NON-NLS-2$
private static final Pattern SELECTION_ANY_PATTERN= Pattern.compile(
"\\$\\{(?:" + WordSelection.NAME + "|" + LineSelection.NAME + ")\\}"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
protected static final int SELECTION_MATCH= 1 << 21;
protected final TemplateStore templateStore;
protected final org.eclipse.text.templates.ContextTypeRegistry typeRegistry;
private int searchMatchRules;
public TemplateCompletionComputer(final TemplateStore templateStore, final ContextTypeRegistry contextTypes) {
this.templateStore= nonNullAssert(templateStore);
this.typeRegistry= nonNullAssert(contextTypes);
}
protected final TemplateStore getTemplateStore() {
return this.templateStore;
}
protected final ContextTypeRegistry getTypeRegistry() {
return this.typeRegistry;
}
@Override
public void onSessionStarted(final SourceEditor editor, final ContentAssist assist) {
int matchRules= SearchPattern.PREFIX_MATCH;
if (assist.getShowSubstringMatches()) {
matchRules |= SearchPattern.SUBSTRING_MATCH;
}
this.searchMatchRules= matchRules;
}
@Override
public void onSessionEnded() {
}
protected boolean handleRequest(final int mode, final String prefix) {
return (prefix != null
&& (prefix.length() > 0 || mode == SPECIFIC_MODE) );
}
protected int getSearchMatchRules() {
return this.searchMatchRules;
}
@Override
public void computeCompletionProposals(final AssistInvocationContext context,
final int mode, final AssistProposalCollector proposals, final IProgressMonitor monitor) {
final ISourceViewer viewer= context.getSourceViewer();
final int flags= 0;
String prefix= extractPrefix(context);
if (!handleRequest(mode, prefix)) {
return;
}
TextRegion region;
if (context.getLength() == 0) {
region= new BasicTextRegion(context.getInvocationOffset() - prefix.length(), context.getInvocationOffset());
}
else {
region= context;
}
SourceEditorTemplateContext templateContext= createTemplateContext(context, region, flags);
if (templateContext == null) {
return;
}
int count= 0;
if (context.getLength() > 0) {
if (prefix.length() == context.getLength()) {
final TemplateProposalParameters<?> parameters= new TemplateProposalParameters<>(
context, region,
new SearchPattern(getSearchMatchRules(), prefix),
templateContext );
count= computeProposals0(parameters, 0, proposals);
}
prefix= ""; // wenn erfolglos, dann ohne prefix //$NON-NLS-1$
if (count != 0) {
templateContext= createTemplateContext(context, region, flags);
if (templateContext == null) {
return;
}
}
}
try {
final int selectionType= configureTemplateContext(context, templateContext);
final TemplateProposalParameters<?> parameters= new TemplateProposalParameters<>(
context, region,
new SearchPattern(getSearchMatchRules(), prefix),
templateContext );
computeProposals0(parameters, selectionType, proposals);
}
catch (final BadLocationException e) {
}
}
@Override
public void computeInformationProposals(final AssistInvocationContext context,
final AssistProposalCollector tenders, final IProgressMonitor monitor) {
}
private int computeProposals0(final TemplateProposalParameters<?> parameters,
final int selectionType,
final AssistProposalCollector proposals) {
final int count= 0;
final List<Template> templates= getTemplates(
parameters.templateContext.getContextType().getId() );
for (final Template template : templates) {
parameters.matchRule= 0;
if (include(template, parameters) || isSelectionTemplate(selectionType, template, parameters) ) {
try {
parameters.templateContext.getContextType().validate(template.getPattern());
}
catch (final TemplateException e) {
continue;
}
parameters.template= template;
switch (parameters.matchRule) {
case SearchPattern.PREFIX_MATCH:
parameters.baseRelevance= 20;
break;
case SELECTION_MATCH:
parameters.baseRelevance= 16;
break;
case SearchPattern.OTHER_MATCH:
parameters.baseRelevance= 15;
break;
default:
parameters.baseRelevance= -100;
break;
}
proposals.add(createProposal(parameters));
}
}
return count;
}
protected boolean include(final Template template, final TemplateProposalParameters<?> parameters) {
return parameters.matchesNamePattern(template.getName());
}
private boolean isSelectionTemplate(final int selectionType,
final Template template, final ProposalParameters<?> parameters) {
switch (selectionType) {
case SELECTION_INLINE:
if (SELECTION_INLINE_PATTERN.matcher(template.getPattern()).matches()) {
parameters.matchRule= SELECTION_MATCH;
return true;
}
return false;
case SELECTION_MULTILINE:
if (SELECTION_ANY_PATTERN.matcher(template.getPattern()).matches()) {
parameters.matchRule= SELECTION_MATCH;
return true;
}
return false;
default:
return false;
}
}
protected @Nullable String extractPrefix(final AssistInvocationContext context) {
return context.getIdentifierPrefix();
}
protected List<Template> getTemplates(final String contextTypeId) {
return ImCollections.newList(this.templateStore.getTemplates(contextTypeId));
}
protected abstract @Nullable TemplateContextType getContextType(
final AssistInvocationContext context, final TextRegion region);
public @Nullable SourceEditorTemplateContext createTemplateContext(
final AssistInvocationContext context, final TextRegion region,
final int flags, final boolean configure) throws BadLocationException {
final SourceEditorTemplateContext templateContext= createTemplateContext(context, region, flags);
if (templateContext != null && configure) {
configureTemplateContext(context, templateContext);
}
return templateContext;
}
protected @Nullable SourceEditorTemplateContext createTemplateContext(
final AssistInvocationContext context, final TextRegion region,
final int flags) {
final TemplateContextType contextType= getContextType(context, region);
if (contextType != null) {
return new SourceEditorTemplateContext(contextType, context.getDocument(), region,
context.getEditor(), flags );
}
return null;
}
protected int configureTemplateContext(final AssistInvocationContext context,
final SourceEditorTemplateContext templateContext) throws BadLocationException {
final IDocument document= context.getDocument();
final String text= document.get(context.getOffset(), context.getLength());
final int selectionType;
if (text.isEmpty()) {
selectionType= SELECTION_NONE;
}
else {
selectionType= (text.indexOf('\n') >= 0) ? SELECTION_MULTILINE : SELECTION_INLINE;
templateContext.setVariable("text", text); //$NON-NLS-1$
}
templateContext.setVariable(GlobalTemplateVariables.SELECTION, text);
return selectionType;
}
protected TemplateProposal createProposal(final TemplateProposalParameters<?> parameters) {
return new TemplateProposal(parameters, getImage(parameters.template));
}
protected @Nullable Image getImage(final Template template) {
return LtkUIPlugin.getInstance().getImageRegistry().get(LtkUI.OBJ_TEXT_TEMPLATE_IMAGE_ID);
}
}