package org.erlide.ui.editors.erl.completion; import java.util.Arrays; import java.util.List; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.contentassist.CompletionProposal; import org.eclipse.jface.text.contentassist.ContentAssistEvent; import org.eclipse.jface.text.contentassist.ContentAssistant; import org.eclipse.jface.text.contentassist.ICompletionListener; import org.eclipse.jface.text.contentassist.ICompletionListenerExtension; import org.eclipse.jface.text.contentassist.ICompletionProposal; import org.eclipse.jface.text.contentassist.IContentAssistProcessor; import org.eclipse.jface.text.contentassist.IContextInformation; import org.eclipse.jface.text.contentassist.IContextInformationValidator; import org.eclipse.jface.text.source.ISourceViewer; import org.erlide.backend.BackendCore; import org.erlide.engine.ErlangEngine; import org.erlide.engine.model.ErlModelException; import org.erlide.engine.model.IErlElement; import org.erlide.engine.model.erlang.ISourceRange; import org.erlide.engine.model.erlang.ISourceReference; import org.erlide.engine.model.root.IErlModule; import org.erlide.engine.model.root.IErlProject; import org.erlide.engine.services.codeassist.CompletionData; import org.erlide.engine.services.codeassist.CompletionService; import org.erlide.engine.services.codeassist.FunctionCompletionData; import org.erlide.runtime.rpc.IOtpRpc; import org.erlide.ui.templates.ErlTemplateCompletionProcessor; import org.erlide.ui.util.eclipse.text.HTMLPrinter; import org.erlide.util.ErlLogger; import org.erlide.util.event_tracer.ErlideEventTracer; import com.google.common.base.Function; import com.google.common.base.Objects; import com.google.common.collect.Lists; public abstract class AbstractErlContentAssistProcessor implements IContentAssistProcessor { public boolean restarted = false; private final class CompletionListener implements ICompletionListener, ICompletionListenerExtension { @Override public void assistSessionStarted(final ContentAssistEvent event) { if (event.processor != AbstractErlContentAssistProcessor.this) { return; } restarted = false; } @Override public void assistSessionEnded(final ContentAssistEvent event) { if (event.processor != AbstractErlContentAssistProcessor.this) { return; } restarted = false; } @Override public void selectionChanged(final ICompletionProposal proposal, final boolean smartToggle) { } @Override public void assistSessionRestarted(final ContentAssistEvent event) { if (event.processor != AbstractErlContentAssistProcessor.this) { return; } restarted = true; } } protected final ISourceViewer sourceViewer; protected final IErlModule module; protected final IErlProject project; protected final ContentAssistant contentAssistant; private IDocument oldDoc; private String oldBefore; private int oldSuggestions = -1; public AbstractErlContentAssistProcessor(final ISourceViewer sourceViewer, final IErlModule module, final IErlProject project, final ContentAssistant contentAssistant) { this.sourceViewer = sourceViewer; this.module = module; this.project = project; this.contentAssistant = contentAssistant; if (contentAssistant != null) { contentAssistant.addCompletionListener(new CompletionListener()); } } @Override public ICompletionProposal[] computeCompletionProposals(final ITextViewer viewer, final int offset) { final String id = Integer.toHexString(viewer.hashCode()) + "@" + offset; try { ErlideEventTracer.getInstance().traceOperationStart("completion", id); try { final IDocument doc = viewer.getDocument(); final String before = getBefore(viewer, doc, offset); String elementBefore; final IErlElement el = getElementAt(offset); if (el instanceof ISourceReference) { final ISourceRange r = ((ISourceReference) el).getSourceRange(); final int o = r.getOffset(); elementBefore = doc.get(o, offset - o); } else { elementBefore = null; } // ErlLogger.debug("computeCompletionProposals before = %s %d %s", // before, oldSuggestions, oldDoc); if (restarted && offset > 0) { final char last = doc.get(offset - 1, 1).charAt(0); if (last == ',' || last == '.' || last == ';' || last == ')' || last == '(') { return null; } } if (Objects.equal(oldDoc, doc) && oldBefore != null && before.startsWith(oldBefore) && oldSuggestions == 0) { return getNoCompletion(offset); } oldDoc = doc; oldBefore = before; final CompletionService completionService = ErlangEngine.getInstance() .getCompletionService(project, module, elementBefore); List<ICompletionProposal> result = Lists.newArrayList(); if (project != null) { final IOtpRpc backend = BackendCore.getBuildBackend(project); final List<CompletionData> resultData = completionService .computeCompletions(backend, offset, before, isInString()); result = Lists.transform(resultData, new Function<CompletionData, ICompletionProposal>() { @Override public ICompletionProposal apply( final CompletionData data) { return toProposal(data); } }); } final ErlTemplateCompletionProcessor t = new ErlTemplateCompletionProcessor( doc, offset - before.length(), before.length()); result.addAll( Arrays.asList(t.computeCompletionProposals(viewer, offset))); oldSuggestions = result.size(); if (result.isEmpty()) { ErlLogger.debug("no results"); return getNoCompletion(offset); } // ErlLogger.debug("%d results", result.size()); return result.toArray(new ICompletionProposal[result.size()]); } catch (final Exception e) { ErlLogger.warn(e); return null; } } finally { ErlideEventTracer.getInstance().traceOperationEnd("completion", id); } } protected boolean isInString() { return false; } private IErlElement getElementAt(final int offset) { if (module == null) { return null; } try { return module.getElementAt(offset); } catch (final ErlModelException e) { ErlLogger.error(e); } return null; } protected ICompletionProposal toProposal(final CompletionData data) { if (data instanceof FunctionCompletionData) { final FunctionCompletionData fdata = (FunctionCompletionData) data; return new ErlCompletionProposal(fdata.getOffsetsAndLengths(), fdata.getDisplayString(), fdata.getReplacementString(), fdata.getReplacementOffset(), fdata.getReplacementLength(), fdata.getCursorPosition(), null, null, HTMLPrinter.asHtml(fdata.getAdditionalProposalInfo()), sourceViewer); } return new CompletionProposal(data.getReplacementString(), data.getReplacementOffset(), data.getReplacementLength(), data.getCursorPosition()); } private ICompletionProposal[] getNoCompletion(final int offset) { return new ICompletionProposal[] { new DummyCompletionProposal(offset) }; } String getBefore(final ITextViewer viewer, final IDocument doc, final int offset) { try { if (module != null) { try { final IErlElement element = module.getElementAt(offset); if (element instanceof ISourceReference) { final ISourceReference sr = (ISourceReference) element; final int start = sr.getSourceRange().getOffset(); if (start <= offset) { return doc.get(start, offset - start); } } } catch (final ErlModelException e) { } } for (int n = offset - 1; n >= 0; --n) { final char c = doc.getChar(n); final int type = Character.getType(c); if (type == Character.LINE_SEPARATOR || type == Character.PARAGRAPH_SEPARATOR || type == Character.CONTROL) { return doc.get(n + 1, offset - n - 1); } } return doc.get(0, offset); } catch (final BadLocationException e) { ErlLogger.warn(e); } return ""; } @Override public IContextInformation[] computeContextInformation(final ITextViewer viewer, final int offset) { return null; } @Override public char[] getContextInformationAutoActivationCharacters() { return null; } @Override public String getErrorMessage() { return null; } @Override public IContextInformationValidator getContextInformationValidator() { return null; } }