| /*=============================================================================# |
| # 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); |
| } |
| |
| } |