blob: 791507d2052825280a352cbb25d9468f8dcb8b2d [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2005, 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.ltk.ui.sourceediting.assist;
import java.util.List;
import java.util.regex.Pattern;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
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.DocumentTemplateContext;
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.TextRegion;
import org.eclipse.statet.internal.ltk.ui.LTKUIPlugin;
import org.eclipse.statet.ltk.ui.LTKUI;
import org.eclipse.statet.ltk.ui.sourceediting.ISourceEditor;
import org.eclipse.statet.ltk.ui.templates.SourceEditorTemplateContext;
/**
* Content assist computer for editor templates.
*/
@NonNullByDefault
public abstract class TemplateCompletionComputer implements IContentAssistComputer {
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 final TemplateStore templateStore;
protected final org.eclipse.text.templates.ContextTypeRegistry typeRegistry;
public TemplateCompletionComputer(final TemplateStore templateStore, final ContextTypeRegistry contextTypes) {
if (templateStore == null) {
throw new NullPointerException("templateStore"); //$NON-NLS-1$
}
if (contextTypes == null) {
throw new NullPointerException("contextTypes"); //$NON-NLS-1$
}
this.templateStore= templateStore;
this.typeRegistry= contextTypes;
}
protected final TemplateStore getTemplateStore() {
return this.templateStore;
}
protected final ContextTypeRegistry getTypeRegistry() {
return this.typeRegistry;
}
@Override
public void sessionStarted(final ISourceEditor editor, final ContentAssist assist) {
}
@Override
public void sessionEnded() {
}
protected boolean handleRequest(final int mode, final String prefix) {
return (prefix != null
&& (prefix.length() > 0 || mode == SPECIFIC_MODE) );
}
@Override
public @Nullable IStatus 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 null;
}
TextRegion region;
if (context.getLength() == 0) {
region= new BasicTextRegion(context.getInvocationOffset() - prefix.length(), context.getInvocationOffset());
}
else {
region= context;
}
DocumentTemplateContext templateContext= createTemplateContext(context, region, flags);
if (templateContext == null) {
return null;
}
int count= 0;
if (context.getLength() > 0) {
if (prefix.length() == context.getLength()) {
count= doComputeProposals(templateContext, prefix, 0, region, proposals);
}
prefix= ""; // wenn erfolglos, dann ohne prefix //$NON-NLS-1$
if (count != 0) {
templateContext= createTemplateContext(context, region, flags);
if (templateContext == null) {
return null;
}
}
}
try {
final IDocument document= viewer.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);
doComputeProposals(templateContext, prefix, selectionType, region, proposals);
}
catch (final BadLocationException e) {
}
return null;
}
@Override
public @Nullable IStatus computeInformationProposals(final AssistInvocationContext context,
final AssistProposalCollector tenders, final IProgressMonitor monitor) {
return null;
}
private int doComputeProposals(final DocumentTemplateContext context, final String prefix,
final int selectionType, final TextRegion replacementRegion,
final AssistProposalCollector proposals) {
// Add Templates
final int count= 0;
final List<Template> templates= getTemplates(context.getContextType().getId());
for (final Template template : templates) {
if (include(template, prefix) || isSelectionTemplate(selectionType, template) ) {
try {
context.getContextType().validate(template.getPattern());
}
catch (final TemplateException e) {
continue;
}
proposals.add(createProposal(template, context, prefix, replacementRegion,
(template.getName().regionMatches(true, 0, prefix, 0, prefix.length())) ?
20 : -100 ));
}
}
return count;
}
protected boolean include(final Template template, final String prefix) {
return template.getName().regionMatches(true, 0, prefix, 0, prefix.length());
}
private boolean isSelectionTemplate(final int selectionType, final Template template) {
switch (selectionType) {
case SELECTION_INLINE:
return SELECTION_INLINE_PATTERN.matcher(template.getPattern()).matches();
case SELECTION_MULTILINE:
return SELECTION_ANY_PATTERN.matcher(template.getPattern()).matches();
default:
return false;
}
}
protected 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);
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.getStartOffset(), region.getLength(),
context.getEditor(), flags );
}
return null;
}
protected TemplateProposal createProposal(final Template template,
final DocumentTemplateContext context, final String prefix, final TextRegion region,
final int relevance) {
return new TemplateProposal(template, context, region, getImage(template), relevance);
}
protected @Nullable Image getImage(final Template template) {
return LTKUIPlugin.getInstance().getImageRegistry().get(LTKUI.OBJ_TEXT_TEMPLATE_IMAGE_ID);
}
}