/******************************************************************************* * Copyright (c) 2009 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation * Zend Technologies *******************************************************************************/ package org.eclipse.php.internal.ui.editor.contentassist; import java.lang.reflect.Field; import java.lang.reflect.Method; import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener; import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent; import org.eclipse.core.runtime.preferences.InstanceScope; import org.eclipse.dltk.ui.text.completion.IScriptContentAssistExtension; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.TextUtilities; import org.eclipse.jface.text.contentassist.ContentAssistant; import org.eclipse.jface.text.contentassist.IContentAssistProcessor; import org.eclipse.php.internal.core.PHPCoreConstants; import org.eclipse.php.internal.core.PHPCorePlugin; import org.eclipse.php.internal.core.documentModel.partitioner.PHPPartitionTypes; import org.eclipse.php.internal.ui.PHPUiPlugin; import org.eclipse.php.internal.ui.editor.PHPStructuredTextViewer; import org.eclipse.php.internal.ui.editor.configuration.PHPStructuredTextViewerConfiguration; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.VerifyKeyListener; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.KeyListener; import org.eclipse.swt.graphics.Point; import org.eclipse.wst.sse.ui.internal.contentassist.StructuredContentAssistant; public class PHPContentAssistant extends StructuredContentAssistant implements IScriptContentAssistExtension { private static final int DEFAULT_AUTO_ACTIVATION_DELAY = 200; private int fAutoActivationDelay = DEFAULT_AUTO_ACTIVATION_DELAY; private IPreferenceChangeListener fPreferenceChangeListener; private class PreferenceListener implements IPreferenceChangeListener { @Override public void preferenceChange(PreferenceChangeEvent event) { if (PHPCoreConstants.CODEASSIST_AUTOINSERT_COMMON_PREFIX.equals(event.getKey())) { enablePrefixCompletion( event.getNode().getBoolean(PHPCoreConstants.CODEASSIST_AUTOINSERT_COMMON_PREFIX, true)); } } } private ITextViewer fViewer; public PHPContentAssistant() { enableColoredLabels(true); enablePrefixCompletion(InstanceScope.INSTANCE.getNode(PHPCorePlugin.ID) .getBoolean(PHPCoreConstants.CODEASSIST_AUTOINSERT_COMMON_PREFIX, true)); setSorter(new PHPCompletionProposalSorter()); } @Override protected AutoAssistListener createAutoAssistListener() { return new AutoAssistListener2(); } @Override public void install(ITextViewer textViewer) { super.install(textViewer); fViewer = textViewer; if (fPreferenceChangeListener == null) { fPreferenceChangeListener = new PreferenceListener(); InstanceScope.INSTANCE.getNode(PHPCorePlugin.ID).addPreferenceChangeListener(fPreferenceChangeListener); } } @Override public void uninstall() { super.uninstall(); if (fPreferenceChangeListener != null) { InstanceScope.INSTANCE.getNode(PHPCorePlugin.ID).removePreferenceChangeListener(fPreferenceChangeListener); fPreferenceChangeListener = null; } fViewer = null; } @Override public void setAutoActivationDelay(int delay) { fAutoActivationDelay = delay; super.setAutoActivationDelay(delay); } class AutoAssistListener2 extends AutoAssistListener implements KeyListener, Runnable, VerifyKeyListener { private Thread fThread; private boolean fIsReset = false; private Object fMutex = new Object(); private int fShowStyle; private final static int SHOW_PROPOSALS = 1; private final static int SHOW_CONTEXT_INFO = 2; @Override protected void start(int showStyle) { fShowStyle = showStyle; fThread = new Thread(this, "AutoAssist Delay"); //$NON-NLS-1$ fThread.start(); } @Override public void run() { try { while (true) { synchronized (fMutex) { if (fAutoActivationDelay != 0) { fMutex.wait(fAutoActivationDelay); } if (fIsReset) { fIsReset = false; continue; } } showAssist(fShowStyle); break; } } catch (InterruptedException e) { } fThread = null; } @Override protected void reset(int showStyle) { synchronized (fMutex) { fShowStyle = showStyle; fIsReset = true; fMutex.notifyAll(); } } @Override protected void stop() { Thread threadToStop = fThread; if (threadToStop != null && threadToStop.isAlive()) { threadToStop.interrupt(); } } @Override public void keyPressed(KeyEvent e) { // Only act on typed characters and ignore modifier-only events if (e.character == 0 && (e.keyCode & SWT.KEYCODE_BIT) == 0) { return; } // https://bugs.eclipse.org/bugs/show_bug.cgi?id=465917, // ignore if with CMD/CTRL if ((e.stateMask & SWT.MOD1) != 0) { return; } if (!(e.character >= 0x20 && e.character != 0x7F && e.character != 0xFF)) { return; } try { int pos = ((Point) evaluatePrivateMemberMethod("fContentAssistSubjectControlAdapter", //$NON-NLS-1$ "getSelectedRange", new Class[0], new Object[0])).x; //$NON-NLS-1$ IDocument document = fViewer.getDocument(); String type = TextUtilities.getContentType(document, getDocumentPartitioning(), pos, true); boolean activated = true; if (type != PHPPartitionTypes.PHP_DEFAULT) { if (fViewer instanceof PHPStructuredTextViewer) { PHPStructuredTextViewer phpViewer = (PHPStructuredTextViewer) fViewer; if (phpViewer.getViewerConfiguration() instanceof PHPStructuredTextViewerConfiguration) { PHPStructuredTextViewerConfiguration viewerConfiguration = (PHPStructuredTextViewerConfiguration) phpViewer .getViewerConfiguration(); IContentAssistProcessor[] processors = viewerConfiguration.getProcessorMap().get(type); if (processors != null) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < processors.length; i++) { sb.append(computeAllAutoActivationTriggers(processors[i])); } if (sb.toString().indexOf(e.character) < 0) { stop(); return; } } else { IContentAssistProcessor processor = getContentAssistProcessor(type); if (computeAllAutoActivationTriggers(processor).indexOf(e.character) < 0) { stop(); return; } } } } char[] activation; activation = (char[]) evaluatePrivateMemberMethod("fContentAssistSubjectControlAdapter", //$NON-NLS-1$ "getCompletionProposalAutoActivationCharacters", //$NON-NLS-1$ new Class[] { ContentAssistant.class, int.class }, new Object[] { PHPContentAssistant.class.getSuperclass().cast(PHPContentAssistant.this), pos }); activated = contains(activation, e.character); } else // just '>' or just '-' will not trigger proposal pop-up if ((pos > 0 && e.character == '>' && document.getChar(pos - 1) != '-') || (e.character == '-')) { stop(); return; } else // Bug 458285 - do not run auto assist with empty identifier if (Character.isWhitespace(e.character) || (pos > 0 && e.character == '\\' && Character.isWhitespace(document.getChar(pos - 1))) || e.character == ',' || e.character == '(' || e.character == ')' || e.character == '{' || e.character == '}' || e.character == '[' || e.character == ']' || e.character == '\'' || e.character == '"' || e.character == '*') { stop(); return; } int showStyle; if (activated && !isProposalPopupActive()) { showStyle = SHOW_PROPOSALS; } else { if (isContextInfoPopupActive()) { showStyle = SHOW_CONTEXT_INFO; } else { stop(); return; } } if (fThread != null && fThread.isAlive()) { reset(showStyle); } else { start(showStyle); } } catch (Exception e1) { PHPUiPlugin.log(e1); } } private String computeAllAutoActivationTriggers(IContentAssistProcessor processor) { if (processor == null) { return ""; //$NON-NLS-1$ } StringBuilder buf = new StringBuilder(5); char[] triggers = processor.getCompletionProposalAutoActivationCharacters(); if (triggers != null) { buf.append(triggers); } triggers = processor.getContextInformationAutoActivationCharacters(); if (triggers != null) { buf.append(triggers); } return buf.toString(); } private boolean contains(char[] characters, char character) { if (characters != null) { for (char character2 : characters) { if (character == character2) { return true; } } } return false; } private Object getPrivateMember(String member) throws Exception { Field declaredField = PHPContentAssistant.this.getClass().getSuperclass().getSuperclass() .getDeclaredField(member); declaredField.setAccessible(true); Object fProposalPopup = declaredField.get(PHPContentAssistant.this); return fProposalPopup; } private Object evaluatePrivateMemberMethod(String privateMember, String method, Class<?>[] params, Object[] args) throws Exception { Method declaredMethod = getPrivateMemberMethod(privateMember, method, params); if (declaredMethod == null) { return null; } Object member = getPrivateMember(privateMember); return declaredMethod.invoke(member, args); } private Method getPrivateMemberMethod(String privateMember, String method, Class<?>[] params) throws Exception { Object member = getPrivateMember(privateMember); if (member == null) { return null; } return getPrivateMetod(member, method, params); } private Method getPrivateMetod(Object obj, String method, Class<?>[] params) throws Exception { Method declaredMethod = obj.getClass().getDeclaredMethod(method, params); declaredMethod.setAccessible(true); return declaredMethod; } } @Override public boolean provide(IContentAssistProcessor processor) { return true; } }