blob: eecf2261601f079b0cab63b18a570b3546a0087d [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.ltk.ui.sourceediting.assist;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Iterator;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.BadPartitioningException;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.contentassist.ICompletionProposalExtension2;
import org.eclipse.jface.text.contentassist.IContextInformation;
import org.eclipse.jface.text.quickassist.IQuickAssistInvocationContext;
import org.eclipse.jface.text.quickassist.IQuickAssistProcessor;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.IAnnotationModel;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.ui.texteditor.spelling.SpellingAnnotation;
import org.eclipse.ui.texteditor.spelling.SpellingProblem;
import org.eclipse.statet.ecommons.text.core.util.TextUtils;
import org.eclipse.statet.ltk.model.core.IModelManager;
import org.eclipse.statet.ltk.ui.sourceediting.ISourceEditor;
/**
* LTK quick assistant processor.
*/
public class QuickAssistProcessor implements IQuickAssistProcessor {
private static class SpellingProposal implements IAssistCompletionProposal {
private final ICompletionProposal proposal;
public SpellingProposal(final ICompletionProposal proposal) {
this.proposal= proposal;
}
@Override
public int getRelevance() {
try {
final Method method= this.proposal.getClass().getMethod("getRelevance"); //$NON-NLS-1$
final Object value= method.invoke(this.proposal);
if (value instanceof Integer) {
return ((Integer) value).intValue();
}
}
catch (final Exception e) {
}
return 0;
}
@Override
public String getSortingString() {
return ""; //$NON-NLS-1$
}
@Override
public Image getImage() {
return this.proposal.getImage();
}
@Override
public String getDisplayString() {
return this.proposal.getDisplayString();
}
@Override
public void selected(final ITextViewer viewer, final boolean smartToggle) {
if (this.proposal instanceof ICompletionProposalExtension2) {
((ICompletionProposalExtension2) this.proposal).selected(viewer, smartToggle);
}
}
@Override
public void unselected(final ITextViewer viewer) {
if (this.proposal instanceof ICompletionProposalExtension2) {
((ICompletionProposalExtension2) this.proposal).unselected(viewer);
}
}
@Override
public boolean validate(final IDocument document, final int offset, final DocumentEvent event) {
if (this.proposal instanceof ICompletionProposalExtension2) {
return ((ICompletionProposalExtension2) this.proposal).validate(document, offset, event);
}
return false;
}
@Override
public String getAdditionalProposalInfo() {
return this.proposal.getAdditionalProposalInfo();
}
@Override
public void apply(final IDocument document) {
this.proposal.apply(document);
}
@Override
public void apply(final ITextViewer viewer, final char trigger, final int stateMask, final int offset) {
if (this.proposal instanceof ICompletionProposalExtension2) {
((ICompletionProposalExtension2) this.proposal).apply(viewer, trigger, stateMask, offset);
}
else {
this.proposal.apply(viewer.getDocument());
}
}
@Override
public Point getSelection(final IDocument document) {
return this.proposal.getSelection(document);
}
@Override
public IContextInformation getContextInformation() {
return this.proposal.getContextInformation();
}
}
private final ISourceEditor editor;
private String errorMessage;
public QuickAssistProcessor(final ISourceEditor editor) {
if (editor == null) {
throw new NullPointerException("editor"); //$NON-NLS-1$
}
this.editor= editor;
}
/**
* @return the editor
*/
public final ISourceEditor getEditor() {
return this.editor;
}
@Override
public boolean canAssist(final IQuickAssistInvocationContext invocationContext) {
return false;
}
@Override
public boolean canFix(final Annotation annotation) {
if (annotation.isMarkedDeleted()) {
return false;
}
final String type= annotation.getType();
if (type.equals(SpellingAnnotation.TYPE)) {
return true;
}
return false;
}
/**
* Creates the context that is passed to the completion proposal
* computers.
*
* @param invocationContext the original invocation context
*
* @return the context to be passed to the computers
*/
protected AssistInvocationContext createContext(final IQuickAssistInvocationContext invocationContext,
final String contentType,
final IProgressMonitor monitor) {
return new AssistInvocationContext(getEditor(),
invocationContext.getOffset(), contentType,
IModelManager.MODEL_FILE, monitor );
}
@Override
public ICompletionProposal[] computeQuickAssistProposals(final IQuickAssistInvocationContext invocationContext) {
this.errorMessage= null;
final SubMonitor m= SubMonitor.convert(null, 3 + 10 + 1);
try {
final String contentType= TextUtils.getContentType(this.editor.getViewer().getDocument(),
this.editor.getDocumentContentInfo(), invocationContext.getOffset(),
invocationContext.getLength() == 0 );
final AssistInvocationContext context= createContext(invocationContext, contentType,
m.newChild(3) );
if (context == null) {
return null;
}
final ISourceViewer viewer= context.getSourceViewer();
if (viewer == null) {
return null;
}
final AssistProposalCollector proposals= new AssistProposalCollector();
final IAnnotationModel model= viewer.getAnnotationModel();
if (model != null) {
addAnnotationProposals(context, proposals, model);
m.worked(5);
}
m.setWorkRemaining(5 + 1);
if (context.getModelInfo() != null) {
addModelAssistProposals(context, proposals, m.newChild(5));
}
if (proposals.getCount() == 0) {
return null;
}
m.setWorkRemaining(1);
return filterAndSortCompletionProposals(proposals, context, m.newChild(1));
}
catch (final BadPartitioningException | BadLocationException e) {
return null;
}
}
/**
* Filters and sorts the proposals. The passed list may be modified
* and returned, or a new list may be created and returned.
*
* @param proposals the list of collected proposals
* @param context
* @param monitor a progress monitor
* @return the list of filtered and sorted proposals, ready for display
*/
protected IAssistCompletionProposal[] filterAndSortCompletionProposals(
final AssistProposalCollector proposals,
final AssistInvocationContext context, final IProgressMonitor monitor) {
final IAssistCompletionProposal[] array= proposals.toArray();
if (array.length > 1) {
Arrays.sort(array, ContentAssistProcessor.PROPOSAL_COMPARATOR);
}
return array;
}
protected boolean isMatchingPosition(final Position pos, final int offset) {
return (pos != null)
&& (offset >= pos.getOffset())
&& (offset <= pos.getOffset()+pos.getLength());
}
private void addAnnotationProposals(final IQuickAssistInvocationContext invocationContext,
final AssistProposalCollector proposals, final IAnnotationModel model) {
final int offset= invocationContext.getOffset();
final Iterator<Annotation> iter= model.getAnnotationIterator();
while (iter.hasNext()) {
final Annotation annotation= iter.next();
if (annotation.isMarkedDeleted()) {
continue;
}
final String type= annotation.getType();
if (type.equals(SpellingAnnotation.TYPE)) {
if (!isMatchingPosition(model.getPosition(annotation), offset)) {
continue;
}
if (annotation instanceof SpellingAnnotation) {
final SpellingProblem problem= ((SpellingAnnotation) annotation).getSpellingProblem();
final ICompletionProposal[] annotationProposals= problem.getProposals(invocationContext);
if (annotationProposals != null && annotationProposals.length > 0) {
for (int i= 0; i < annotationProposals.length; i++) {
proposals.add(new SpellingProposal(annotationProposals[i]));
}
}
}
}
}
}
protected void addModelAssistProposals(final AssistInvocationContext context,
final AssistProposalCollector proposals, final IProgressMonitor monitor) {
}
@Override
public String getErrorMessage() {
return this.errorMessage;
}
}