| /******************************************************************************* |
| * Copyright (c) 2011 NumberFour AG |
| * |
| * 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: |
| * NumberFour AG - initial API and Implementation (Alex Panchenko) |
| *******************************************************************************/ |
| package org.eclipse.dltk.javascript.internal.ui.text.hyperlink; |
| |
| import static java.util.Collections.singletonList; |
| |
| import org.eclipse.dltk.core.DLTKCore; |
| import org.eclipse.dltk.core.IMember; |
| import org.eclipse.dltk.core.IModelElement; |
| import org.eclipse.dltk.core.IModelElementVisitor; |
| import org.eclipse.dltk.core.ISourceModule; |
| import org.eclipse.dltk.core.ModelException; |
| import org.eclipse.dltk.core.ScriptModelUtil; |
| import org.eclipse.dltk.internal.javascript.ti.JSDocSupport; |
| import org.eclipse.dltk.internal.javascript.ti.TypeInferencer2; |
| import org.eclipse.dltk.internal.ui.actions.SelectionConverter; |
| import org.eclipse.dltk.internal.ui.editor.EditorUtility; |
| import org.eclipse.dltk.internal.ui.editor.ModelElementHyperlink; |
| import org.eclipse.dltk.javascript.internal.core.codeassist.JavaScriptSelectionEngine2; |
| import org.eclipse.dltk.javascript.internal.ui.JavaScriptUI; |
| import org.eclipse.dltk.javascript.internal.ui.text.JSDocTextUtils; |
| import org.eclipse.dltk.javascript.internal.ui.text.TypeNameNode; |
| import org.eclipse.dltk.javascript.parser.jsdoc.JSDocTag; |
| import org.eclipse.dltk.javascript.typeinfo.JSDocTypeRegion; |
| import org.eclipse.dltk.javascript.typeinfo.JSDocTypeUtil; |
| import org.eclipse.dltk.javascript.typeinfo.TypeInfoManager; |
| import org.eclipse.dltk.javascript.typeinfo.TypeMode; |
| import org.eclipse.dltk.javascript.typeinfo.model.Type; |
| import org.eclipse.dltk.javascript.ui.text.IJavaScriptPartitions; |
| import org.eclipse.dltk.ui.actions.OpenAction; |
| import org.eclipse.dltk.ui.infoviews.ModelElementArray; |
| import org.eclipse.jface.action.IAction; |
| import org.eclipse.jface.text.BadLocationException; |
| import org.eclipse.jface.text.IDocument; |
| import org.eclipse.jface.text.IRegion; |
| import org.eclipse.jface.text.ITextViewer; |
| import org.eclipse.jface.text.Region; |
| import org.eclipse.jface.text.TextUtilities; |
| import org.eclipse.jface.text.hyperlink.AbstractHyperlinkDetector; |
| import org.eclipse.jface.text.hyperlink.IHyperlink; |
| import org.eclipse.ui.texteditor.ITextEditor; |
| |
| @SuppressWarnings("restriction") |
| public class JSDocTypeHyperlinkDetector extends AbstractHyperlinkDetector { |
| |
| public IHyperlink[] detectHyperlinks(ITextViewer textViewer, |
| IRegion inputRegion, boolean canShowMultipleHyperlinks) { |
| if (inputRegion == null || textViewer == null |
| || JavaScriptSelectionEngine2.isJSDocTypeSelectionEnabled()) { |
| return null; |
| } |
| try { |
| final IDocument doc = textViewer.getDocument(); |
| final int offset = inputRegion.getOffset(); |
| final String contentType = TextUtilities.getContentType(doc, |
| IJavaScriptPartitions.JS_PARTITIONING, offset, true); |
| if (!IJavaScriptPartitions.JS_DOC.equals(contentType) |
| && !IJavaScriptPartitions.JS_MULTI_LINE_COMMENT |
| .equals(contentType)) { |
| return null; |
| } |
| final IRegion lineRegion = JSDocTextUtils |
| .getLineRegion(doc, offset); |
| final String line = doc.get(lineRegion.getOffset(), |
| lineRegion.getLength()); |
| final int offsetInLine = offset - lineRegion.getOffset(); |
| int start = line.lastIndexOf('{', offsetInLine); |
| if (start < 0) { |
| return null; |
| } |
| ++start; |
| int end = line.indexOf('}', offsetInLine); |
| if (end < 0) { |
| return null; |
| } |
| final TypeNameNode tagName = JSDocTextUtils.getTag( |
| line.toCharArray(), 0, start); |
| if (tagName != null && JSDocTag.PARAM.equals(tagName.value())) { |
| if (line.regionMatches( |
| end - JSDocSupport.PARAM_OPTIONAL.length(), |
| JSDocSupport.PARAM_OPTIONAL, 0, |
| JSDocSupport.PARAM_OPTIONAL.length())) { |
| end -= JSDocSupport.PARAM_OPTIONAL.length(); |
| } |
| if (line.regionMatches(end - JSDocSupport.PARAM_DOTS.length(), |
| JSDocSupport.PARAM_DOTS, 0, |
| JSDocSupport.PARAM_DOTS.length())) { |
| end -= JSDocSupport.PARAM_DOTS.length(); |
| } |
| if (line.regionMatches(start, JSDocSupport.PARAM_DOTS, 0, |
| JSDocSupport.PARAM_DOTS.length())) { |
| start += JSDocSupport.PARAM_DOTS.length(); |
| } |
| } |
| final ITextEditor editor = (ITextEditor) getAdapter(ITextEditor.class); |
| if (editor == null) { |
| return null; |
| } |
| IAction action = editor.getAction("OpenEditor"); //$NON-NLS-1$ |
| if (action == null || !(action instanceof OpenAction)) |
| return null; |
| final OpenAction openAction = (OpenAction) action; |
| final ISourceModule input = EditorUtility |
| .getEditorInputModelElement(editor, false); |
| if (input == null) { |
| return null; |
| } |
| TypeInferencer2 inferencer2 = new TypeInferencer2(); |
| inferencer2.setModelElement(input); |
| final int typeExpressionOffset = lineRegion.getOffset() + start; |
| final JSDocTypeRegion selection = JSDocTypeUtil.findTypeAt( |
| inferencer2, line.substring(start, end), offset |
| - typeExpressionOffset); |
| if (selection == null) { |
| return null; |
| } |
| Type type = inferencer2.getKnownType(selection.name(), |
| TypeMode.JSDOC); |
| Object[] elements = null; |
| if (type == null) { |
| try { |
| ScriptModelUtil.reconcile(input); |
| input.accept(new Visitor(selection.name())); |
| } catch (ModelException e) { |
| if (DLTKCore.DEBUG) { |
| e.printStackTrace(); |
| } |
| } catch (ModelElementFound e) { |
| elements = new Object[] { e.element }; |
| } |
| } else { |
| final IModelElement me = TypeInfoManager.convertElement(input, |
| type); |
| elements = SelectionConverter |
| .filterElements(singletonList(me != null ? me : type)); |
| } |
| if (elements != null && elements.length > 0) { |
| final IRegion region = new Region(typeExpressionOffset |
| + selection.start(), selection.length()); |
| final IHyperlink link; |
| if (elements.length == 1) { |
| link = new ModelElementHyperlink(region, elements[0], |
| openAction); |
| } else { |
| link = new ModelElementHyperlink(region, |
| new ModelElementArray(elements), openAction); |
| } |
| return new IHyperlink[] { link }; |
| } |
| } catch (BadLocationException e) { |
| JavaScriptUI.log(e); |
| } |
| return null; |
| } |
| |
| private static class Visitor implements IModelElementVisitor { |
| |
| private final String name; |
| |
| public Visitor(String name) { |
| this.name = name; |
| } |
| |
| public boolean visit(IModelElement element) { |
| if (element instanceof IMember) { |
| IMember member = (IMember) element; |
| if (name.equals(member.getElementName())) { |
| throw new ModelElementFound(element); |
| } |
| } |
| return true; |
| } |
| } |
| |
| @SuppressWarnings("serial") |
| private static class ModelElementFound extends RuntimeException { |
| final IModelElement element; |
| |
| public ModelElementFound(IModelElement element) { |
| this.element = element; |
| } |
| |
| } |
| |
| } |