Package org.erlide.ui.editors.erl.completion

Source Code of org.erlide.ui.editors.erl.completion.AbstractErlContentAssistProcessor$CompletionNameComparer

package org.erlide.ui.editors.erl.completion;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.List;

import org.eclipse.core.runtime.CoreException;
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.eclipse.swt.graphics.Point;
import org.erlide.backend.BackendCore;
import org.erlide.engine.ErlangEngine;
import org.erlide.engine.model.ErlModelException;
import org.erlide.engine.model.erlang.ErlangFunction;
import org.erlide.engine.model.erlang.IErlComment;
import org.erlide.engine.model.erlang.IErlFunction;
import org.erlide.engine.model.erlang.IErlFunctionClause;
import org.erlide.engine.model.erlang.IErlImport;
import org.erlide.engine.model.erlang.IErlModule;
import org.erlide.engine.model.erlang.IErlPreprocessorDef;
import org.erlide.engine.model.erlang.IErlRecordDef;
import org.erlide.engine.model.erlang.IErlRecordField;
import org.erlide.engine.model.erlang.ISourceRange;
import org.erlide.engine.model.erlang.ISourceReference;
import org.erlide.engine.model.root.ErlElementKind;
import org.erlide.engine.model.root.IErlElement;
import org.erlide.engine.model.root.IErlElementLocator;
import org.erlide.engine.model.root.IErlProject;
import org.erlide.engine.services.codeassist.RecordCompletion;
import org.erlide.engine.services.search.ModelFindService;
import org.erlide.engine.services.search.OtpDocService;
import org.erlide.runtime.api.IOtpRpc;
import org.erlide.ui.internal.ErlideUIPlugin;
import org.erlide.ui.internal.information.HoverUtil;
import org.erlide.ui.prefs.plugin.NavigationPreferencePage;
import org.erlide.ui.templates.ErlTemplateCompletionProcessor;
import org.erlide.ui.util.eclipse.text.HTMLPrinter;
import org.erlide.util.ErlLogger;
import org.erlide.util.StringUtils;
import org.erlide.util.Util;
import org.erlide.util.event_tracer.ErlideEventTracer;

import com.ericsson.otp.erlang.OtpErlangList;
import com.ericsson.otp.erlang.OtpErlangLong;
import com.ericsson.otp.erlang.OtpErlangObject;
import com.ericsson.otp.erlang.OtpErlangRangeException;
import com.ericsson.otp.erlang.OtpErlangString;
import com.ericsson.otp.erlang.OtpErlangTuple;
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;
        }
    }

    public static class CompletionNameComparer implements Comparator<ICompletionProposal> {

        private final String prefix;

        public CompletionNameComparer(final String prefix) {
            this.prefix = prefix;
        }

        @Override
        public int compare(final ICompletionProposal o1, final ICompletionProposal o2) {
            final String s1 = o1.getDisplayString();
            final String s2 = o2.getDisplayString();
            // exact prefix matches get higher priority
            if (s1.startsWith(prefix)) {
                return -1;
            }
            if (s2.startsWith(prefix)) {
                return 1;
            }
            return s1.compareTo(s2);
        }

    }

    static boolean isErlangIdentifierChar(final char char1) {
        return Character.isJavaIdentifierPart(char1);
    }

    static boolean filterImported(final IErlImport erlImport, final String funWithArity) {
        if (erlImport == null) {
            return true;
        }
        for (final ErlangFunction ef : erlImport.getFunctions()) {
            if (ef.getNameWithArity().equals(funWithArity)) {
                return true;
            }
        }
        return false;
    }

    protected final ISourceViewer sourceViewer;
    protected final IErlModule module;
    protected final IErlProject project;

    protected enum Kinds {
        //@formatter:off
        DECLARED_FUNCTIONS,
        EXTERNAL_FUNCTIONS,
        VARIABLES,
        RECORD_FIELDS,
        RECORD_DEFS,
        MODULES,
        MACRO_DEFS,
        IMPORTED_FUNCTIONS,
        AUTO_IMPORTED_FUNCTIONS,
        ARITY_ONLY,
        UNEXPORTED_ONLY,
        INCLUDES,
        INCLUDE_LIBS,
        TYPES
        //@formatter:on
    }

    protected static final List<ICompletionProposal> EMPTY_COMPLETIONS = new ArrayList<ICompletionProposal>();
    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());
        }
    }

    protected List<ICompletionProposal> getModules(final IOtpRpc backend,
            final int offset, final String prefix, final Kinds kind)
            throws ErlModelException {
        final List<ICompletionProposal> result = Lists.newArrayList();
        final boolean includes = kind == Kinds.INCLUDES || kind == Kinds.INCLUDE_LIBS;
        final List<String> names = ErlangEngine.getInstance().getModelUtilService()
                .findUnitsWithPrefix(prefix, project, kind != Kinds.INCLUDES, includes);
        final OtpErlangObject res = ErlangEngine.getInstance()
                .getService(OtpDocService.class)
                .getModules(backend, prefix, names, includes);
        if (res instanceof OtpErlangList) {
            final OtpErlangList resList = (OtpErlangList) res;
            for (final OtpErlangObject o : resList) {
                if (o instanceof OtpErlangString) {
                    final OtpErlangString s = (OtpErlangString) o;
                    final String suffix = includes ? "" : ":";
                    final String cpl = quoted(s.stringValue() + suffix, kind);
                    final int prefixLength = prefix.length();
                    result.add(new CompletionProposal(cpl, offset - prefixLength,
                            prefixLength, cpl.length()));
                }
            }
        }
        return result;
    }

    protected abstract String quoted(String string, Kinds kind);

    private static class CompletionOptions {
        EnumSet<Kinds> flags = EnumSet.noneOf(Kinds.class);
        int pos;
        String moduleOrRecord = null;
        List<String> fieldsSoFar = null;
        String text;
        int offset;
        public String prefix;
    }

    @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);
                // 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 CompletionOptions options = computeOptions(before, offset);
                final List<ICompletionProposal> result = addCompletions(options);
                final ErlTemplateCompletionProcessor t = new ErlTemplateCompletionProcessor(
                        doc, offset - options.text.length(), options.text.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 getNoCompletion(offset);
            }
        } finally {
            ErlideEventTracer.getInstance().traceOperationEnd("completion", id);
        }
    }

    private CompletionOptions computeOptions(final String text, final int offset) {
        final CompletionOptions options = new CompletionOptions();

        options.text = text;
        options.offset = offset;
        options.prefix = getPrefix(text);

        final int commaPos = text.lastIndexOf(',');
        final int colonPos = text.lastIndexOf(':');
        final boolean doubleColon = colonPos >= 0 && text.charAt(colonPos - 1) == ':';
        final int hashMarkPos = text.lastIndexOf('#');
        final int dotPos = text.lastIndexOf('.');
        final int parenPos = text.lastIndexOf('(');
        final int leftBracketPos = text.lastIndexOf('{');
        final int interrogationMarkPos = text.lastIndexOf('?');
        final int arrowPos = text.lastIndexOf("->");

        RecordCompletion rc = null;
        if (hashMarkPos >= 0) {
            final IErlProject aproject = project;
            if (aproject != null) {
                rc = ErlangEngine
                        .getInstance()
                        .getContextAssistService()
                        .checkRecordCompletion(BackendCore.getBuildBackend(aproject),
                                text);
            }
        }
        if (rc != null && rc.isNameWanted()) {
            options.flags = EnumSet.of(Kinds.RECORD_DEFS);
            options.pos = hashMarkPos;
            options.text = rc.getPrefix();
        } else if (rc != null && rc.isFieldWanted()) {
            options.flags = EnumSet.of(Kinds.RECORD_FIELDS);
            options.pos = hashMarkPos;
            if (dotPos > hashMarkPos) {
                options.pos = dotPos;
            } else if (leftBracketPos > hashMarkPos) {
                options.pos = leftBracketPos;
            } else {
                assert false;
            }
            options.text = rc.getPrefix();
            options.moduleOrRecord = rc.getName();
            options.fieldsSoFar = rc.getFields();
        } else if (colonPos > commaPos && colonPos > parenPos) {
            if (doubleColon) {
                options.flags = EnumSet.of(Kinds.TYPES);
                options.pos = colonPos;
                options.text = text.substring(colonPos + 1);
            } else {
                options.moduleOrRecord = StringUtils.unquote(getPrefix(text.substring(0,
                        colonPos)));
                options.flags = EnumSet.of(Kinds.EXTERNAL_FUNCTIONS);
                options.pos = colonPos;
                options.text = text.substring(colonPos + 1);
            }
        } else if (interrogationMarkPos > hashMarkPos && interrogationMarkPos > commaPos
                && interrogationMarkPos > colonPos && interrogationMarkPos > arrowPos) {
            options.flags = EnumSet.of(Kinds.MACRO_DEFS);
            options.pos = interrogationMarkPos;
            options.text = text.substring(interrogationMarkPos + 1);
        } else {
            options.pos = colonPos;
            options.text = options.prefix;
            IErlElement element = getElementAt(offset);
            for (int i = 1; element == null && i <= 15; ++i) {
                // TODO this is wrong.... element is not the latest expression
                element = getElementAt(offset - i);
                // options.offset = offset - i;
            }
            if (element != null) {
                switch (element.getKind()) {
                case EXPORT:
                    options.flags = EnumSet.of(Kinds.DECLARED_FUNCTIONS,
                            Kinds.ARITY_ONLY, Kinds.UNEXPORTED_ONLY);
                    break;
                case IMPORT:
                    final IErlImport i = (IErlImport) element;
                    options.moduleOrRecord = i.getImportModule();
                    options.flags = EnumSet
                            .of(Kinds.EXTERNAL_FUNCTIONS, Kinds.ARITY_ONLY);
                    break;
                case FUNCTION:
                case CLAUSE:
                    options.flags = EnumSet.of(Kinds.MODULES);
                    if (module != null) {
                        options.flags.addAll(EnumSet.of(Kinds.VARIABLES,
                                Kinds.DECLARED_FUNCTIONS, Kinds.IMPORTED_FUNCTIONS,
                                Kinds.AUTO_IMPORTED_FUNCTIONS));

                    }
                    break;
                case ATTRIBUTE:
                    if (element.getName().equals("include")) {
                        options.flags = EnumSet.of(Kinds.INCLUDES);
                    } else if (element.getName().equals("include_lib")) {
                        options.flags = EnumSet.of(Kinds.INCLUDE_LIBS);
                    }
                    break;
                default:
                    break;
                }
            } else {
                if (doubleColon) {
                    options.flags = EnumSet.of(Kinds.TYPES);
                } else {
                    options.flags = EnumSet.of(Kinds.MODULES);
                }
            }
        }
        options.flags = filterFlags(options.flags);
        options.prefix = getPrefix(options.text);

        return options;
    }

    protected abstract EnumSet<Kinds> filterFlags(EnumSet<Kinds> flags);

    private ICompletionProposal[] getNoCompletion(final int offset) {
        return new ICompletionProposal[] { new DummyCompletionProposal(offset) };
    }

    private List<ICompletionProposal> addCompletions(final CompletionOptions options)
            throws CoreException, BadLocationException {
        final List<ICompletionProposal> result = new ArrayList<ICompletionProposal>();
        final IErlProject aProject = project;
        if (aProject == null) {
            return result;
        }
        final IOtpRpc backend = BackendCore.getBuildBackend(aProject);
        if (options.flags.contains(Kinds.DECLARED_FUNCTIONS)) {
            addSorted(
                    options.prefix,
                    result,
                    getDeclaredFunctions(options.offset, options.prefix,
                            options.flags.contains(Kinds.UNEXPORTED_ONLY),
                            options.flags.contains(Kinds.ARITY_ONLY)));
        }
        if (options.flags.contains(Kinds.VARIABLES)) {
            addSorted(options.prefix, result,
                    getVariables(backend, options.offset, options.prefix));
        }
        if (options.flags.contains(Kinds.IMPORTED_FUNCTIONS)) {
            addSorted(options.prefix, result,
                    getImportedFunctions(backend, options.offset, options.prefix));
        }
        if (options.flags.contains(Kinds.AUTO_IMPORTED_FUNCTIONS)) {
            addSorted(options.prefix, result,
                    getAutoImportedFunctions(backend, options.offset, options.prefix));
        }
        if (options.flags.contains(Kinds.MODULES)) {
            addSorted(options.prefix, result,
                    getModules(backend, options.offset, options.prefix, Kinds.MODULES));
        }
        if (options.flags.contains(Kinds.INCLUDES)) {
            addSorted(options.prefix, result,
                    getModules(backend, options.offset, options.prefix, Kinds.INCLUDES));
        }
        if (options.flags.contains(Kinds.INCLUDE_LIBS)) {
            addSorted(
                    options.prefix,
                    result,
                    getModules(backend, options.offset, options.prefix,
                            Kinds.INCLUDE_LIBS));
        }
        if (options.flags.contains(Kinds.RECORD_DEFS)) {
            addSorted(
                    options.prefix,
                    result,
                    getMacroOrRecordCompletions(options.offset, options.prefix,
                            ErlElementKind.RECORD_DEF));
        }
        if (options.flags.contains(Kinds.RECORD_FIELDS)) {
            addSorted(
                    options.prefix,
                    result,
                    getRecordFieldCompletions(options.moduleOrRecord, options.offset,
                            options.prefix, options.pos, options.fieldsSoFar));
        }
        if (options.flags.contains(Kinds.MACRO_DEFS)) {
            addSorted(
                    options.prefix,
                    result,
                    getMacroOrRecordCompletions(options.offset, options.prefix,
                            ErlElementKind.MACRO_DEF));
        }
        if (options.flags.contains(Kinds.EXTERNAL_FUNCTIONS)) {
            addSorted(
                    options.prefix,
                    result,
                    getExternalCallCompletions(backend, options.moduleOrRecord,
                            options.offset, options.prefix,
                            options.flags.contains(Kinds.ARITY_ONLY)));
        }
        if (options.flags.contains(Kinds.TYPES)) {
            addSorted(
                    options.prefix,
                    result,
                    getTypeCompletions(backend, options.moduleOrRecord, options.offset,
                            options.prefix));
        }

        return result;
    }

    private void addSorted(final String prefix, final List<ICompletionProposal> result,
            final List<ICompletionProposal> completions) {
        final CompletionNameComparer completionNameComparer = new CompletionNameComparer(
                prefix);
        Collections.sort(completions, completionNameComparer);
        result.addAll(completions);
    }

    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) {
        }
        return "";
    }

    protected IErlElement getElementAt(final int offset) {
        if (module == null) {
            return null;
        }
        try {
            return module.getElementAt(offset);
        } catch (final ErlModelException e) {
            ErlLogger.error(e);
        }
        return null;
    }

    String getPrefix(final String before) {
        for (int n = before.length() - 1; n >= 0; --n) {
            final char c = before.charAt(n);
            if (!isErlangIdentifierChar(c) && c != '?' && c != '\'') {
                return before.substring(n + 1);
            }
        }
        return before;
    }

    List<ICompletionProposal> getDeclaredFunctions(final int offset, final String prefix,
            final boolean unexportedOnly, final boolean arityOnly)
            throws ErlModelException {
        final List<ICompletionProposal> result = new ArrayList<ICompletionProposal>();
        for (final IErlElement e : module.getChildren()) {
            if (e instanceof IErlFunction) {
                final IErlFunction f = (IErlFunction) e;
                if (unexportedOnly && f.isExported()) {
                    continue;
                }
                addFunctionCompletion(offset, prefix, result, f, arityOnly);
            }
        }
        return result;
    }

    List<ICompletionProposal> getVariables(final IOtpRpc b, final int offset,
            final String prefix) throws BadLocationException {
        final List<ICompletionProposal> result = new ArrayList<ICompletionProposal>();
        // get variables
        final IErlElement el = getElementAt(offset);
        if (el instanceof ISourceReference) {
            final ISourceRange r = ((ISourceReference) el).getSourceRange();
            final int o = r.getOffset();
            final IDocument doc = sourceViewer.getDocument();
            final int prefixLength = prefix.length();
            final String src = doc.get(o, offset - o - prefixLength);
            final Collection<String> vars = ErlangEngine.getInstance()
                    .getContextAssistService().getVariables(src, prefix);
            for (final String var : vars) {
                result.add(new CompletionProposal(var, offset - prefixLength,
                        prefixLength, var.length()));
            }
        }
        return result;
    }

    List<ICompletionProposal> getMacroOrRecordCompletions(final int offset,
            final String prefix, final ErlElementKind kind) {
        if (module == null) {
            return EMPTY_COMPLETIONS;
        }
        final List<ICompletionProposal> result = new ArrayList<ICompletionProposal>();
        try {
            final List<IErlPreprocessorDef> defs = ErlangEngine.getInstance()
                    .getModelUtilService().getAllPreprocessorDefs(module, kind);
            for (final IErlPreprocessorDef pd : defs) {
                final String name = pd.getDefinedName();
                addIfMatches(name, prefix, offset, result);
            }
        } catch (final CoreException e) {
            ErlLogger.error(e);
        }
        if (kind == ErlElementKind.MACRO_DEF) {
            final String[] names = ErlangEngine.getInstance().getModelUtilService()
                    .getPredefinedMacroNames();
            for (final String name : names) {
                addIfMatches(name, prefix, offset, result);
            }
        }
        return result;
    }

    List<ICompletionProposal> getExternalCallCompletions(final IOtpRpc b,
            final String moduleName0, final int offset, final String prefix,
            final boolean arityOnly) throws CoreException {
        final ModelFindService modelFindService = ErlangEngine.getInstance()
                .getModelFindService();
        final String moduleName = modelFindService.resolveMacroValue(moduleName0, module);
        // we have an external call
        final List<ICompletionProposal> result = new ArrayList<ICompletionProposal>();
        final boolean checkAllProjects = NavigationPreferencePage.getCheckAllProjects();
        final IErlElementLocator model = ErlangEngine.getInstance().getModel();
        final IErlModule theModule = modelFindService.findModule(model, project,
                moduleName, null,
                checkAllProjects ? IErlElementLocator.Scope.ALL_PROJECTS
                        : IErlElementLocator.Scope.REFERENCED_PROJECTS);
        if (theModule != null) {
            if (ErlangEngine.getInstance().getModelUtilService().isOtpModule(theModule)) {
                final String stateDir = ErlideUIPlugin.getDefault().getStateLocation()
                        .toString();
                final OtpErlangObject res = ErlangEngine.getInstance()
                        .getService(OtpDocService.class)
                        .getProposalsWithDoc(b, moduleName, prefix, stateDir);
                addFunctionProposalsWithDoc(offset, prefix, result, res, null, arityOnly);
            } else {
                addFunctionsFromModule(offset, prefix, arityOnly, result, theModule);
            }
        }
        return result;
    }

    List<ICompletionProposal> getRecordFieldCompletions(final String recordName,
            final int offset, final String prefix, final int hashMarkPos,
            final List<String> fieldsSoFar) {
        if (module == null) {
            return EMPTY_COMPLETIONS;
        }
        IErlPreprocessorDef pd;
        try {
            pd = ErlangEngine.getInstance().getModelFindService()
                    .findPreprocessorDef(module, recordName, ErlElementKind.RECORD_DEF);
        } catch (final CoreException e) {
            return EMPTY_COMPLETIONS;
        }
        if (pd instanceof IErlRecordDef) {
            final List<ICompletionProposal> result = new ArrayList<ICompletionProposal>();
            try {
                for (final IErlElement i : pd.getChildren()) {
                    final IErlRecordField field = (IErlRecordField) i;
                    final String fieldName = field.getFieldName();
                    if (!fieldsSoFar.contains(fieldName)) {
                        addIfMatches(fieldName, prefix, offset, result);
                    }
                }
            } catch (final ErlModelException e) {
            }
            return result;
        }
        return EMPTY_COMPLETIONS;
    }

    private void addIfMatches(final String name, final String prefix, final int offset,
            final List<ICompletionProposal> result) {
        final int length = prefix.length();
        if (name.regionMatches(true, 0, prefix, 0, length)) {
            result.add(new CompletionProposal(name, offset - length, length, name
                    .length()));
        }
    }

    List<ICompletionProposal> getAutoImportedFunctions(final IOtpRpc backend,
            final int offset, final String prefix) {
        final String stateDir = ErlangEngine.getInstance().getStateDir();
        final OtpErlangObject res = ErlangEngine.getInstance()
                .getService(OtpDocService.class)
                .getProposalsWithDoc(backend, "<auto_imported>", prefix, stateDir);
        final List<ICompletionProposal> result = new ArrayList<ICompletionProposal>();
        addFunctionProposalsWithDoc(offset, prefix, result, res, null, false);
        return result;
    }

    List<ICompletionProposal> getImportedFunctions(final IOtpRpc backend,
            final int offset, final String prefix) {
        final String stateDir = ErlangEngine.getInstance().getStateDir();
        final List<ICompletionProposal> result = new ArrayList<ICompletionProposal>();
        for (final IErlImport imp : module.getImports()) {
            final OtpErlangObject res = ErlangEngine
                    .getInstance()
                    .getService(OtpDocService.class)
                    .getProposalsWithDoc(backend, imp.getImportModule(), prefix, stateDir);
            addFunctionProposalsWithDoc(offset, prefix, result, res, imp, false);
        }
        return result;
    }

    List<ICompletionProposal> getTypeCompletions(final IOtpRpc backend,
            final String moduleOrRecord, final int offset, final String prefix) {
        final List<ICompletionProposal> result = new ArrayList<ICompletionProposal>();
        for (final String builtin : getBuiltinTypeCompletions()) {
            if (builtin.startsWith(prefix.trim())) {
                result.add(new CompletionProposal(builtin, offset - prefix.length(),
                        prefix.length(), builtin.length()));
            }
        }
        // TODO provide types completions from workspace
        return result;
    }

    List<String> getBuiltinTypeCompletions() {
        final List<String> result = Lists.newArrayList("any()", "binary()",
                "bitstring()", "boolean()", "byte()", "char()", "float()", "fun()",
                "integer()", "iolist()", "list()", "maybe_improper_list()", "mfa()",
                "module()", "neg_integer()", "no_return()", "node()",
                "non_neg_integer()", "none()", "nonempty_list()", "number()", "pid()",
                "port()", "pos_integer()", "reference()", "term()", "timeout()",
                "tuple()");
        return result;
    }

    boolean addFunctionsFromModule(final int offset, final String prefix,
            final boolean arityOnly, final List<ICompletionProposal> proposals,
            final IErlModule m) {
        boolean result = false;
        try {
            m.open(null);
            for (final IErlElement e : m.getChildren()) {
                if (e instanceof IErlFunction) {
                    final IErlFunction f = (IErlFunction) e;
                    if (f.isExported()) {
                        addFunctionCompletion(offset, prefix, proposals, f, arityOnly);
                        result = true;
                    }
                }
            }
        } catch (final ErlModelException e) {
            ErlLogger.error(e);
        }
        return result;
    }

    void addFunctionProposalsWithDoc(final int offset, final String aprefix,
            final List<ICompletionProposal> result, final OtpErlangObject res,
            final IErlImport erlImport, final boolean arityOnly) {
        if (res instanceof OtpErlangList) {
            final OtpErlangList resl = (OtpErlangList) res;
            for (final OtpErlangObject i : resl) {
                // {FunWithArity, FunWithParameters, [{Offset, Length}], Doc}
                final OtpErlangTuple f = (OtpErlangTuple) i;
                final String funWithArity = ((OtpErlangString) f.elementAt(0))
                        .stringValue();
                if (!filterImported(erlImport, funWithArity)) {
                    continue;
                }
                String funWithParameters = arityOnly ? funWithArity
                        : ((OtpErlangString) f.elementAt(1)).stringValue();
                final OtpErlangList parOffsets = (OtpErlangList) f.elementAt(2);
                String docStr = null;
                if (f.arity() > 3) {
                    final OtpErlangObject elt = f.elementAt(3);
                    if (elt instanceof OtpErlangString) {
                        docStr = HTMLPrinter.asHtml(Util.stringValue(elt));
                    }
                }

                funWithParameters = funWithParameters.substring(aprefix.length());
                final List<Point> offsetsAndLengths = new ArrayList<Point>();
                if (!arityOnly) {
                    addOffsetsAndLengths(parOffsets, offset, offsetsAndLengths);
                }
                addFunctionCompletion(offset, result, funWithArity, docStr,
                        funWithParameters, offsetsAndLengths);
            }
        }
    }

    /**
     * @param offset
     * @param result
     * @param funWithArity
     * @param docStr
     * @param funWithParameters
     * @param offsetsAndLengths
     * @param cursorPosition
     */
    protected void addFunctionCompletion(final int offset,
            final List<ICompletionProposal> result, final String funWithArity,
            final String docStr, final String funWithParameters,
            final List<Point> offsetsAndLengths) {
        int cursorPosition = funWithParameters.length();
        if (!offsetsAndLengths.isEmpty()) {
            cursorPosition = offsetsAndLengths.get(0).x;
        }

        // first check if it's already there...
        for (final ICompletionProposal c : result) {
            if (c.getDisplayString().equals(funWithArity)) {
                return;
            }
        }
        final ICompletionProposal c = new ErlCompletionProposal(offsetsAndLengths,
                funWithArity, funWithParameters, offset, 0, cursorPosition, null, null,
                docStr, sourceViewer);

        result.add(c);
    }

    void addFunctionCompletion(final int offset, final String aprefix,
            final List<ICompletionProposal> result, final IErlFunction function,
            final boolean arityOnly) {
        addFunctionCompletion(offset, aprefix, function.getFunction(),
                function.getComments(), arityOnly, arityOnly ? null
                        : getParameterNames(function), result);
    }

    List<String> getParameterNames(final IErlFunction function) {
        final List<String> parameters = function.getParameters();
        final int arity = function.getArity();
        final List<String> result = new ArrayList<String>(arity);
        addEmptyParameterNames(arity, result);
        addParametersFromFunctionParameters(parameters, result);
        for (final IErlFunctionClause clause : function.getClauses()) {
            addParametersFromFunctionParameters(clause.getParameters(), result);
        }
        return result;
    }

    private void addParametersFromFunctionParameters(final List<String> parameters,
            final List<String> result) {
        final int n = Math.min(parameters.size(), result.size());
        for (int i = 0; i < n; ++i) {
            if (result.get(i).equals("_")) {
                final String var = parameters.get(i).trim();
                if (looksLikeParameter(var)) {
                    result.set(i, fixVarName(var));
                }
            }
        }
    }

    private void addEmptyParameterNames(final int arity, final List<String> result) {
        for (int i = result.size(); i < arity; ++i) {
            result.add("_");
        }
    }

    private String fixVarName(final String var) {
        final String v = var.charAt(0) == '_' ? var.substring(1) : var;
        final char c = v.charAt(0);
        return Character.isLowerCase(c) ? Character.toUpperCase(c) + v.substring(1) : v;
    }

    /**
     * Check if the string looks like an erlang parameter
     *
     * @param parameter
     *            String the parameter to check
     * @return true iff parameter is like Par, _Par or _par
     */
    private boolean looksLikeParameter(final String parameter) {
        if (parameter == null || parameter.length() == 0) {
            return false;
        }
        final char c = parameter.charAt(0);
        final char c2 = parameter.length() > 1 ? parameter.charAt(1) : c;
        return c >= 'A' && c <= 'Z' || c == '_'
                && (c2 >= 'A' && c <= 'Z' || c2 >= 'a' && c2 <= 'z');
    }

    void addFunctionCompletion(final int offset, final String prefix,
            final ErlangFunction function, final Collection<IErlComment> comments,
            final boolean arityOnly, final List<String> parameterNames,
            final List<ICompletionProposal> result) {
        if (function.name.regionMatches(true, 0, prefix, 0, prefix.length())) {
            final int offs = function.name.length() - prefix.length();

            final List<Point> offsetsAndLengths = new ArrayList<Point>();
            if (!arityOnly) {
                addOffsetsAndLengths(parameterNames, offset + offs + 1, offsetsAndLengths);
            }
            final String funWithArity = function.getNameWithArity();
            String funWithParameters = arityOnly ? funWithArity : getNameWithParameters(
                    function.name, parameterNames);
            funWithParameters = funWithParameters.substring(prefix.length());
            final String htmlComment = comments == null ? "" : HTMLPrinter
                    .asHtml(HoverUtil.getDocumentationString(comments, null));
            addFunctionCompletion(offset, result, funWithArity, htmlComment,
                    funWithParameters, offsetsAndLengths);
        }
    }

    private String getNameWithParameters(final String name,
            final List<String> parameterNames) {
        final StringBuilder b = new StringBuilder();
        b.append(name).append('(');
        for (int i = 0, n = parameterNames.size(); i < n; i++) {
            b.append(parameterNames.get(i));
            if (i < n - 1) {
                b.append(", ");
            }
        }
        b.append(')');
        return b.toString();
    }

    private void addOffsetsAndLengths(final List<String> parameterNames,
            final int replacementOffset0, final List<Point> result) {
        int replacementOffset = replacementOffset0;
        for (final String par : parameterNames) {
            result.add(new Point(replacementOffset, par.length()));
            replacementOffset += par.length() + 2;
        }
    }

    void addOffsetsAndLengths(final OtpErlangList parOffsets,
            final int replacementOffset, final List<Point> result) {
        for (final OtpErlangObject i : parOffsets) {
            final OtpErlangTuple t = (OtpErlangTuple) i;
            final OtpErlangLong offset = (OtpErlangLong) t.elementAt(0);
            final OtpErlangLong length = (OtpErlangLong) t.elementAt(1);
            try {
                result.add(new Point(offset.intValue() + replacementOffset, length
                        .intValue()));
            } catch (final OtpErlangRangeException e) {
            }
        }
    }

    @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;
    }

}
TOP

Related Classes of org.erlide.ui.editors.erl.completion.AbstractErlContentAssistProcessor$CompletionNameComparer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.