/** * Aptana Studio * Copyright (c) 2005-2012 by Appcelerator, Inc. All Rights Reserved. * Licensed under the terms of the GNU Public License (GPL) v3 (with exceptions). * Please see the license.html included with this distribution for details. * Any modifications to this file must keep this entire header intact. */ package com.aptana.editor.php.internal.ui.hover; import java.util.List; import java.util.Map; import org.eclipse.core.resources.IFile; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.ToolBarManager; import org.eclipse.jface.internal.text.html.BrowserInformationControlInput; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IInputChangedListener; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.hyperlink.IHyperlink; import org.eclipse.jface.text.source.ISourceViewer; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.part.FileEditorInput; import org2.eclipse.php.internal.core.compiler.ast.nodes.PHPDocBlock; import com.aptana.core.logging.IdeLog; import com.aptana.core.util.ArrayUtil; import com.aptana.core.util.CollectionsUtil; import com.aptana.core.util.StringUtil; import com.aptana.editor.common.contentassist.ILexemeProvider; import com.aptana.editor.common.hover.CustomBrowserInformationControl; import com.aptana.editor.common.hover.DocumentationBrowserInformationControlInput; import com.aptana.editor.php.PHPEditorPlugin; import com.aptana.editor.php.indexer.IElementEntry; import com.aptana.editor.php.internal.builder.LocalModule; import com.aptana.editor.php.internal.contentAssist.ContentAssistUtils; import com.aptana.editor.php.internal.contentAssist.PHPTokenType; import com.aptana.editor.php.internal.contentAssist.ParsingUtils; import com.aptana.editor.php.internal.contentAssist.mapping.PHPOffsetMapper; import com.aptana.editor.php.internal.core.builder.IModule; import com.aptana.editor.php.internal.indexer.AbstractPHPEntryValue; import com.aptana.editor.php.internal.indexer.PHPDocUtils; import com.aptana.editor.php.internal.indexer.VariablePHPEntryValue; import com.aptana.editor.php.internal.parser.nodes.PHPBaseParseNode; import com.aptana.editor.php.internal.parser.phpdoc.FunctionDocumentation; import com.aptana.editor.php.internal.ui.editor.PHPSourceEditor; import com.aptana.editor.php.internal.ui.editor.hyperlink.PHPHyperlinkDetector; import com.aptana.ide.ui.io.internal.UniformFileStoreEditorInput; import com.aptana.parsing.lexer.Lexeme; import com.aptana.ui.epl.UIEplPlugin; import com.aptana.ui.util.UIUtils; /** * Provides PHPDoc as hover info for PHP elements. */ @SuppressWarnings("restriction") public class PHPTextHover extends AbstractPHPTextHover { /* * (non-Javadoc) * @see com.aptana.editor.common.hover.AbstractDocumentationHover#getHeader(java.lang.Object, * org.eclipse.ui.IEditorPart, org.eclipse.jface.text.IRegion) */ @Override public String getHeader(Object element, IEditorPart editorPart, IRegion hoverRegion) { // Set the header to display the file location if (element instanceof IElementEntry) { IElementEntry entry = (IElementEntry) element; if (entry.getModule() != null) { String moduleName = entry.getModule().getShortName(); // In case the module is from a remote location, we would like to display the file name as is, and not // the temp file name that was created. if (editorPart != null && editorPart.getEditorInput() instanceof UniformFileStoreEditorInput) { UniformFileStoreEditorInput uniformInput = (UniformFileStoreEditorInput) editorPart .getEditorInput(); if (uniformInput.isRemote()) { moduleName = editorPart.getTitle(); } } return moduleName; } } else if (element instanceof PHPBaseParseNode) { return Messages.PHPTextHover_phpAPIHeader; } return null; } /* * (non-Javadoc) * @see com.aptana.editor.common.hover.AbstractDocumentationHover#getDocumentation(java.lang.Object, * org.eclipse.ui.IEditorPart, org.eclipse.jface.text.IRegion) */ @Override public String getDocumentation(Object element, IEditorPart editorPart, IRegion hoverRegion) { String computedDocumentation = null; if (element instanceof IElementEntry) { IElementEntry entry = (IElementEntry) element; AbstractPHPEntryValue phpValue = (AbstractPHPEntryValue) entry.getValue(); int startOffset = phpValue.getStartOffset(); // Locate the IDocument and pass it along to the PHPDocUtils IDocument document = null; if (editorPart != null) { ISourceViewer sourceViewer = (ISourceViewer) editorPart.getAdapter(ISourceViewer.class); if (sourceViewer != null) { document = sourceViewer.getDocument(); } } PHPDocBlock comment = PHPDocUtils.findFunctionPHPDocComment(entry, document, startOffset); FunctionDocumentation documentation; if (phpValue instanceof VariablePHPEntryValue && ((VariablePHPEntryValue) phpValue).isParameter()) { try { documentation = PHPDocUtils.getParameterDocumentation(comment, document.get(hoverRegion.getOffset(), hoverRegion.getLength())); } catch (BadLocationException e) { IdeLog.logError(PHPEditorPlugin.getDefault(), e); documentation = null; } } else { documentation = PHPDocUtils.getFunctionDocumentation(comment); } computedDocumentation = PHPDocUtils.computeDocumentation(documentation, document, entry.getEntryPath()); } else if (element instanceof PHPBaseParseNode) { PHPBaseParseNode node = (PHPBaseParseNode) element; computedDocumentation = ContentAssistUtils.getDocumentation(node, node.getNodeName()); } return computedDocumentation; } /* * (non-Javadoc) * @see com.aptana.editor.common.hover.AbstractDocumentationHover#populateToolbarActions(org.eclipse.jface.action. * ToolBarManager, com.aptana.editor.common.hover.CustomBrowserInformationControl) */ @Override public void populateToolbarActions(ToolBarManager tbm, CustomBrowserInformationControl iControl) { final OpenDeclarationAction openDeclarationAction = new OpenDeclarationAction(iControl); tbm.add(openDeclarationAction); IInputChangedListener inputChangeListener = new IInputChangedListener() { public void inputChanged(Object newInput) { if (newInput instanceof BrowserInformationControlInput) { openDeclarationAction.update(); } } }; iControl.addInputChangeListener(inputChangeListener); } /* * (non-Javadoc) * @see org.eclipse.jface.text.ITextHoverExtension2#getHoverInfo2(org.eclipse.jface.text.ITextViewer, * org.eclipse.jface.text.IRegion) */ public Object getHoverInfo2(ITextViewer textViewer, IRegion hoverRegion) { if (isHoverEnabled()) { Object[] elements = getPHPElementsAt(textViewer, hoverRegion); if (ArrayUtil.isEmpty(elements) || elements[0] == null) { return null; } IEditorPart editor = getEditor(); if (editor != null) { // We need to check if the current editor is matching the resource that the PHP module refers to. // In case it's not, we try to locate the editor that does match. If it's open, we'll pass it along. // If not, the hover will use the cached value for the documentation. IEditorInput editorInput = editor.getEditorInput(); IFile resource = (IFile) editorInput.getAdapter(IFile.class); if (resource != null) { final IFile moduleFile = getModuleFile(elements[0]); if (moduleFile != null && !moduleFile.equals(resource)) { final IEditorPart[] moduleEditorPart = new IEditorPart[1]; // The module points to a different file. Try to grab the matching editor. UIUtils.getDisplay().syncExec(new Runnable() { public void run() { IWorkbenchPage activePage = UIUtils.getActivePage(); if (activePage != null) { // locate an open editor. moduleEditorPart[0] = activePage.findEditor(new FileEditorInput(moduleFile)); } } }); // Assign the editor to the one we found. It can still be null in case the editor is not opened. editor = moduleEditorPart[0]; } } } return getHoverInfo(elements[0], isBrowserControlAvailable(textViewer), null, editor, hoverRegion); } return null; } /** * Returns an IFile associated with an IModule. * * @param element * An instance of IModule * @return An {@link IFile}; <code>null</code> if the given element is not a {@link LocalModule}. */ private IFile getModuleFile(Object element) { if (element instanceof IElementEntry) { IModule module = ((IElementEntry) element).getModule(); if (module instanceof LocalModule) { return ((LocalModule) module).getFile(); } } return null; } /* * (non-Javadoc) * @see * com.aptana.editor.php.internal.ui.hover.AbstractPHPTextHover#getPHPElementsAt(org.eclipse.jface.text.ITextViewer, * org.eclipse.jface.text.IRegion) */ protected Object[] getPHPElementsAt(ITextViewer textViewer, IRegion hoverRegion) { PHPSourceEditor editor = (PHPSourceEditor) getEditor(); if (editor == null) { return null; } ILexemeProvider<PHPTokenType> lexemeProvider = ParsingUtils.createLexemeProvider(editor.getDocumentProvider() .getDocument(editor.getEditorInput()), hoverRegion.getOffset()); Lexeme<PHPTokenType> lexeme = lexemeProvider.getLexemeFromOffset(hoverRegion.getOffset()); if (lexeme == null) { return null; } PHPOffsetMapper offsetMapper = editor.getOffsetMapper(); Object entry = offsetMapper.findEntry(lexeme, lexemeProvider); if (entry == null) { try { String name = textViewer.getDocument().get(hoverRegion.getOffset(), hoverRegion.getLength()); if (!StringUtil.isEmpty(name)) { List<Object> elements = ContentAssistUtils.selectModelElements(name, true); if (!CollectionsUtil.isEmpty(elements)) { // return the first element only entry = elements.get(0); } } } catch (BadLocationException e) { IdeLog.logError(PHPEditorPlugin.getDefault(), "PHP documentation hover - error getting an element at offset - " + hoverRegion.getOffset(), e); //$NON-NLS-1$ } } if (entry != null) { return new Object[] { entry }; } return null; } /** * Open declaration action. */ public class OpenDeclarationAction extends Action { private static final String IMG_OPEN_DECLARATION = "icons/full/elcl16/goto_input.gif"; //$NON-NLS-1$ private static final String IMG_OPEN_DECLARATION_DISABLED = "icons/full/dlcl16/goto_input.gif"; //$NON-NLS-1$ private CustomBrowserInformationControl iControl; private IHyperlink[] hyperlinks; /** * @param iControl */ public OpenDeclarationAction(CustomBrowserInformationControl iControl) { setText(Messages.PHPTextHover_openDeclarationTooltip); setImageDescriptor(UIEplPlugin.imageDescriptorFromPlugin(UIEplPlugin.PLUGIN_ID, IMG_OPEN_DECLARATION)); setDisabledImageDescriptor(UIEplPlugin.imageDescriptorFromPlugin(UIEplPlugin.PLUGIN_ID, IMG_OPEN_DECLARATION_DISABLED)); this.iControl = iControl; } /** * Update the action */ void update() { BrowserInformationControlInput input = iControl.getInput(); if (input instanceof DocumentationBrowserInformationControlInput) { PHPHyperlinkDetector detector = new PHPHyperlinkDetector(); IRegion hoverRegion = ((DocumentationBrowserInformationControlInput) input).getHoverRegion(); if (hoverRegion != null) { hyperlinks = detector.detectHyperlinks((PHPSourceEditor) getEditor(), hoverRegion, false); setEnabled(!ArrayUtil.isEmpty(hyperlinks) && hyperlinks[0] != null); return; } } setEnabled(false); } /* * (non-Javadoc) * @see org.eclipse.jface.action.Action#run() */ @Override public void run() { // We already know that this hyperlink is valid. A check was made at the update call. iControl.dispose(); hyperlinks[0].open(); } } @Override public String getFileLocation(Object element, IEditorPart editorPart, IRegion hoverRegion) { return null; } @Override public Map<String, String> getUserAgent(Object element, IEditorPart editorPart, IRegion hoverRegion) { return null; } @Override public String getShowImage(Object element, IEditorPart editorPart, IRegion hoverRegion) { return null; } @Override public String getBook(Object element, IEditorPart editorPart, IRegion hoverRegion) { return null; } }