/** * Copyright (c) 2005-2011 by Appcelerator, Inc. All Rights Reserved. * Licensed under the terms of the Eclipse Public License (EPL). * Please see the license.txt included with this distribution for details. * Any modifications to this file must keep this entire header intact. */ /* * Created on 24/09/2005 */ package org.python.pydev.editor.simpleassist; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.eclipse.core.runtime.Assert; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.TextPresentation; import org.eclipse.jface.text.contentassist.ContentAssistEvent; import org.eclipse.jface.text.contentassist.ICompletionListener; 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.IContextInformationPresenter; import org.eclipse.jface.text.contentassist.IContextInformationValidator; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; import org.python.pydev.core.ExtensionHelper; import org.python.pydev.core.docutils.PySelection; import org.python.pydev.core.log.Log; import org.python.pydev.editor.IPySyntaxHighlightingAndCodeCompletionEditor; import org.python.pydev.editor.codecompletion.CompletionError; import org.python.pydev.editor.codecompletion.IPyCodeCompletion; import org.python.pydev.editor.codecompletion.PyCodeCompletionPreferencesPage; import org.python.pydev.editor.codecompletion.PyContentAssistant; import org.python.pydev.editor.codecompletion.PythonCompletionProcessor; import org.python.pydev.plugin.PydevPlugin; /** * This processor controls the completion cycle (and also works as a 'delegator' to the processor that deals * with actual python completions -- which may be a bit slower that simple completions). * * @author Fabio */ public class SimpleAssistProcessor implements IContentAssistProcessor { private class ContextInformationDelegator implements IContextInformationValidator, IContextInformationPresenter { private final IContextInformationValidator defaultContextInformationValidator; private ContextInformationDelegator(IContextInformationValidator defaultContextInformationValidator) { Assert.isTrue(defaultContextInformationValidator instanceof IContextInformationPresenter); this.defaultContextInformationValidator = defaultContextInformationValidator; } public void install(IContextInformation info, ITextViewer viewer, int offset) { defaultContextInformationValidator.install(info, viewer, offset); } public boolean isContextInformationValid(int offset) { if (showDefault()) { return defaultContextInformationValidator.isContextInformationValid(offset); } return true; } public boolean updatePresentation(int offset, TextPresentation presentation) { return ((IContextInformationPresenter) defaultContextInformationValidator).updatePresentation(offset, presentation); } } public static final char[] ALL_ASCII_CHARS = new char[] { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '_' }; //-------- cycling through simple completions and default processor private static final int SHOW_SIMPLE = 1; private static final int SHOW_DEFAULT = 2; private int whatToShow = SHOW_SIMPLE; public void startCycle() { whatToShow = SHOW_SIMPLE; } public void doCycle() { if (whatToShow == SHOW_SIMPLE) { whatToShow = SHOW_DEFAULT; } //cycles only once here } public void updateStatus() { if (whatToShow == SHOW_SIMPLE) { assistant.setIterationStatusMessage("Press %s for default completions."); } } //-------- end cycling through regular completions and templates /** * The editor that contains this processor */ private IPySyntaxHighlightingAndCodeCompletionEditor edit; /** * The 'default' processor (gets python completions) */ private PythonCompletionProcessor defaultPythonProcessor; /** * The content assistant that contains this processor */ private PyContentAssistant assistant; /** * Participants for a simple completion */ private List<ISimpleAssistParticipant> participants; /** * Whether the last completion was auto-activated or not */ private boolean lastCompletionAutoActivated; /** * Whether we should use the default auto-completion on all ascii chars * Cleared when the property cache is updated (based on autoActivationCharsCache) */ private volatile static boolean useAutocompleteOnAllAsciiCharsCache; /** * Cache with the chars that should be used for auto-activation * Cleared when the property cache is updated */ private volatile static char[] autoActivationCharsCache; /** * The last error that occurred while requesting a completion. */ private String lastError = null; @SuppressWarnings("unchecked") public SimpleAssistProcessor(IPySyntaxHighlightingAndCodeCompletionEditor edit, PythonCompletionProcessor defaultPythonProcessor, final PyContentAssistant assistant) { this.edit = edit; this.defaultPythonProcessor = defaultPythonProcessor; this.assistant = assistant; this.participants = ExtensionHelper.getParticipants(ExtensionHelper.PYDEV_SIMPLE_ASSIST); assistant.addCompletionListener(new ICompletionListener() { public void assistSessionEnded(ContentAssistEvent event) { } public void assistSessionStarted(ContentAssistEvent event) { startCycle(); lastCompletionAutoActivated = assistant.getLastCompletionAutoActivated(); if (!lastCompletionAutoActivated) { //user request... cycle to the default completions at once doCycle(); } } public void selectionChanged(ICompletionProposal proposal, boolean smartToggle) { //ignore } }); } /** * Computes the proposals (may forward for simple or 'complete' proposals) * * @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#computeCompletionProposals(org.eclipse.jface.text.ITextViewer, int) */ public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset) { try { if (showDefault()) { return defaultPythonProcessor.computeCompletionProposals(viewer, offset); } else { updateStatus(); IDocument doc = viewer.getDocument(); String[] strs = PySelection.getActivationTokenAndQual(doc, offset, false); String activationToken = strs[0]; String qualifier = strs[1]; PySelection ps = edit.createPySelection(); if (ps == null) { return new ICompletionProposal[0]; } List<ICompletionProposal> results = new ArrayList<ICompletionProposal>(); for (ISimpleAssistParticipant participant : participants) { results.addAll(participant.computeCompletionProposals(activationToken, qualifier, ps, edit, offset)); } //don't matter the result... next time we won't ask for simple stuff doCycle(); if (results.size() == 0) { if (!lastCompletionAutoActivated || defaultAutoActivated(viewer, offset) || useAutocompleteOnAllAsciiCharsCache) { return defaultPythonProcessor.computeCompletionProposals(viewer, offset); } return new ICompletionProposal[0]; } else { Collections.sort(results, IPyCodeCompletion.PROPOSAL_COMPARATOR); return (ICompletionProposal[]) results.toArray(new ICompletionProposal[0]); } } } catch (Exception e) { Log.log(e); CompletionError completionError = new CompletionError(e); this.lastError = completionError.getErrorMessage(); //Make the error visible to the user! return new ICompletionProposal[] { completionError }; } } /** * Determines whether it was auto-activated on the default completion or in the simple one. * @param viewer the viewer for which this completion was requested * @param offset the offset at which it was requested * @return true if it was auto-activated for the default completion (and false if it was for the simple) */ private boolean defaultAutoActivated(ITextViewer viewer, int offset) { try { char docChar = viewer.getDocument().getChar(offset - 1); for (char c : this.defaultPythonProcessor.getCompletionProposalAutoActivationCharacters()) { if (c == docChar) { return true; } } } catch (BadLocationException e) { } return false; } /** * @return true if we should show the default completions (and false if we shouldn't) */ private boolean showDefault() { return whatToShow == SHOW_DEFAULT || this.participants.size() == 0; } /** * Compute context information */ public IContextInformation[] computeContextInformation(ITextViewer viewer, int offset) { if (showDefault()) { return defaultPythonProcessor.computeContextInformation(viewer, offset); } return null; } /** * only very simple proposals should be here, as it is auto-activated for any character * * @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#getCompletionProposalAutoActivationCharacters() */ public char[] getCompletionProposalAutoActivationCharacters() { return getStaticAutoActivationCharacters( defaultPythonProcessor.getCompletionProposalAutoActivationCharacters(), this.participants.size()); } /** * Attribute that determines if the listener that'll clear the auto activation chars is already in place. */ private volatile static boolean listenerToClearAutoActivationAlreadySetup = false; /** * @return the auto-activation chars that should be used. */ public synchronized static char[] getStaticAutoActivationCharacters(char[] defaultChars, int participantsLen) { if (!listenerToClearAutoActivationAlreadySetup) { PydevPlugin.getDefault().getPreferenceStore().addPropertyChangeListener(new IPropertyChangeListener() { public void propertyChange(PropertyChangeEvent event) { autoActivationCharsCache = null; } }); listenerToClearAutoActivationAlreadySetup = true; } if (autoActivationCharsCache == null) { char[] defaultAutoActivationCharacters = defaultChars; useAutocompleteOnAllAsciiCharsCache = PyCodeCompletionPreferencesPage.useAutocompleteOnAllAsciiChars() && PyCodeCompletionPreferencesPage.useAutocomplete(); char[] c2; if (participantsLen == 0 && !useAutocompleteOnAllAsciiCharsCache) { c2 = defaultAutoActivationCharacters; } else { //just use the extension for the simple if we do have it c2 = new char[ALL_ASCII_CHARS.length + defaultAutoActivationCharacters.length]; System.arraycopy(ALL_ASCII_CHARS, 0, c2, 0, ALL_ASCII_CHARS.length); System.arraycopy(defaultAutoActivationCharacters, 0, c2, ALL_ASCII_CHARS.length, defaultAutoActivationCharacters.length); } autoActivationCharsCache = c2; } return autoActivationCharsCache; } /** * @return chars that are used for context information auto-activation */ public char[] getContextInformationAutoActivationCharacters() { return null; } /** * @return some error that might have happened in the completion */ public String getErrorMessage() { String ret = this.lastError; if (ret == null && showDefault()) { ret = defaultPythonProcessor.getErrorMessage(); } this.lastError = null; return ret; } /** * @return the validator we should use */ public IContextInformationValidator getContextInformationValidator() { final IContextInformationValidator defaultContextInformationValidator = defaultPythonProcessor .getContextInformationValidator(); return new ContextInformationDelegator(defaultContextInformationValidator); } }