diff options
author | Tor Norbye <tnorbye@google.com> | 2010-12-17 16:41:25 -0800 |
---|---|---|
committer | Android Code Review <code-review@android.com> | 2010-12-17 16:41:25 -0800 |
commit | f6c48faceef8b8f707cb93d0c7452ab4e66b6fac (patch) | |
tree | 4368dea8697060110a3bfefa29643227cdf10e44 | |
parent | 9ba3ed6f9787fee383f5b6f6bf4029be5095653b (diff) | |
parent | f16b3d794c9a79f5c91ecb4b6311b7bd139ecc6c (diff) | |
download | sdk-f6c48faceef8b8f707cb93d0c7452ab4e66b6fac.zip sdk-f6c48faceef8b8f707cb93d0c7452ab4e66b6fac.tar.gz sdk-f6c48faceef8b8f707cb93d0c7452ab4e66b6fac.tar.bz2 |
Merge "Hyperlink improvements"
5 files changed, 274 insertions, 29 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml b/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml index c66a604..f2da163 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml +++ b/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml @@ -871,7 +871,7 @@ point="org.eclipse.ui.workbench.texteditor.hyperlinkDetectorTargets"> <target id="com.android.ide.eclipse.xmlCode" - name="XML Hyperlink Target"> + name="XML Editor"> <context type="org.eclipse.ui.texteditor.ITextEditor"/> </target> </extension> @@ -898,7 +898,7 @@ <hyperlinkDetector class="com.android.ide.eclipse.adt.internal.editors.xml.Hyperlinks$JavaResolver" id="com.android.ide.eclipse.javaCodeResolver2" - modifierKeys="M1+M2" + modifierKeys="M1+M2+M3" name="Android Java Hyperlink Detector (Extra Modifier Key)" targetId="org.eclipse.jdt.ui.javaCode"> </hyperlinkDetector> diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java index f3da152..21d91e3 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java @@ -96,6 +96,8 @@ import org.osgi.framework.Version; import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -435,6 +437,22 @@ public class AdtPlugin extends AbstractUIPlugin implements ILogger { } /** + * Reads the contents of an {@link File} and return it as a String + * + * @param file the file to be read + * @return the String read from the file, or null if there was an error + */ + public static String readFile(File file) { + try { + return readFile(new FileReader(file)); + } catch (FileNotFoundException e) { + AdtPlugin.log(e, "Can't read file %1$s", file); //$NON-NLS-1$ + } + + return null; + } + + /** * Returns true iff the given file contains the given String. * * @param file the file to look for the string in @@ -455,6 +473,23 @@ public class AdtPlugin extends AbstractUIPlugin implements ILogger { } /** + * Returns true iff the given file contains the given String. + * + * @param file the file to look for the string in + * @param string the string to be searched for + * @return true if the file is found and contains the given string anywhere within it + */ + public static boolean fileContains(File file, String string) { + try { + return streamContains(new FileReader(file), string); + } catch (Exception e) { + AdtPlugin.log(e, "Can't read file %1$s", file); //$NON-NLS-1$ + } + + return false; + } + + /** * Returns true iff the given input stream contains the given String. * * @param r the stream to look for the string in diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AndroidConstants.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AndroidConstants.java index 3a6f3ff..6e4b278 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AndroidConstants.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AndroidConstants.java @@ -167,6 +167,9 @@ public class AndroidConstants { // another CL. public final static String NS_CUSTOM_RESOURCES = "http://schemas.android.com/apk/res/%1$s"; //$NON-NLS-1$ + /** The package "android" as used in resource urls etc */ + public static final String ANDROID_PKG = "android"; //$NON-NLS-1$ + /** The old common plug-in ID. Please do not use for new features. */ private static final String LEGACY_PLUGIN_ID = "com.android.ide.eclipse.common"; //$NON-NLS-1$ diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java index a7e7a3d..914b33e 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java @@ -16,6 +16,7 @@ package com.android.ide.eclipse.adt.internal.editors.layout.gle2; +import static com.android.ide.eclipse.adt.AndroidConstants.ANDROID_PKG; import static com.android.sdklib.resources.Density.DEFAULT_DENSITY; import com.android.ide.common.layoutlib.BasicLayoutScene; @@ -1812,7 +1813,7 @@ public class GraphicalEditorPart extends EditorPart // the platform apparently only supports @android for now (or if it does, // there are no usages in the current code base so this is not common). String packageName = url.substring(typeBegin, colon); - if ("android".equals(packageName)) { //$NON-NLS-1$ + if (ANDROID_PKG.equals(packageName)) { isFrameworkResource = true; } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/xml/Hyperlinks.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/xml/Hyperlinks.java index 4c6945f..cdc7d8a 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/xml/Hyperlinks.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/xml/Hyperlinks.java @@ -19,7 +19,10 @@ package com.android.ide.eclipse.adt.internal.editors.xml; import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI; import static com.android.ide.common.layout.LayoutConstants.ATTR_CLASS; import static com.android.ide.common.layout.LayoutConstants.VIEW; +import static com.android.ide.eclipse.adt.AndroidConstants.ANDROID_PKG; import static com.android.ide.eclipse.adt.AndroidConstants.EXT_XML; +import static com.android.ide.eclipse.adt.AndroidConstants.FN_RESOURCE_BASE; +import static com.android.ide.eclipse.adt.AndroidConstants.FN_RESOURCE_CLASS; import static com.android.ide.eclipse.adt.AndroidConstants.WS_RESOURCES; import static com.android.ide.eclipse.adt.AndroidConstants.WS_SEP; import static com.android.ide.eclipse.adt.internal.editors.resources.descriptors.ResourcesDescriptors.NAME_ATTR; @@ -48,6 +51,13 @@ import com.android.sdklib.IAndroidTarget; import com.android.sdklib.SdkConstants; import com.android.sdklib.annotations.VisibleForTesting; +import org.apache.xerces.parsers.DOMParser; +import org.apache.xerces.xni.Augmentations; +import org.apache.xerces.xni.NamespaceContext; +import org.apache.xerces.xni.QName; +import org.apache.xerces.xni.XMLAttributes; +import org.apache.xerces.xni.XMLLocator; +import org.apache.xerces.xni.XNIException; import org.eclipse.core.filesystem.EFS; import org.eclipse.core.filesystem.IFileStore; import org.eclipse.core.resources.IFile; @@ -87,6 +97,7 @@ import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.PartInitException; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.ide.IDE; +import org.eclipse.ui.part.MultiPageEditorPart; import org.eclipse.ui.texteditor.ITextEditor; import org.eclipse.wst.sse.core.StructuredModelManager; import org.eclipse.wst.sse.core.internal.encoding.util.Logger; @@ -95,6 +106,7 @@ import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion; import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument; import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion; import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion; +import org.eclipse.wst.sse.ui.StructuredTextEditor; import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel; import org.eclipse.wst.xml.core.internal.regions.DOMRegionContext; import org.w3c.dom.Attr; @@ -103,8 +115,11 @@ import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; import java.io.File; +import java.io.FileInputStream; import java.io.IOException; import java.util.HashSet; import java.util.List; @@ -409,7 +424,7 @@ public class Hyperlinks { } /** Opens a path (which may not be in the workspace) */ - private static void openPath(IPath filePath, IRegion region) { + private static void openPath(IPath filePath, IRegion region, int offset) { IEditorPart sourceEditor = getEditor(); IWorkbenchPage page = sourceEditor.getEditorSite().getPage(); IWorkspaceRoot workspace = ResourcesPlugin.getWorkspace().getRoot(); @@ -433,7 +448,22 @@ public class Hyperlinks { IFileStore fileStore = EFS.getLocalFileSystem().getStore(filePath); if (!fileStore.fetchInfo().isDirectory() && fileStore.fetchInfo().exists()) { try { - IDE.openEditorOnFileStore(page, fileStore); + IEditorPart target = IDE.openEditorOnFileStore(page, fileStore); + if (target instanceof MultiPageEditorPart) { + MultiPageEditorPart part = (MultiPageEditorPart) target; + IEditorPart[] editors = part.findEditors(target.getEditorInput()); + if (editors != null) { + for (IEditorPart editor : editors) { + if (editor instanceof StructuredTextEditor) { + StructuredTextEditor ste = (StructuredTextEditor) editor; + part.setActiveEditor(editor); + ste.selectAndReveal(offset, 0); + break; + } + } + } + } + return; } catch (PartInitException ex) { AdtPlugin.log(ex, "Can't open %$1s", filePath); //$NON-NLS-1$ @@ -449,26 +479,33 @@ public class Hyperlinks { } - /** Opens a framework resource */ + /** + * Opens a framework resource's declaration + * + * @param project project to look up the framework for + * @param url the resource url, such as @android:string/ok, to open the declaration + * for + * @return true if the url was successfully opened. + */ private static boolean openAndroidResource(IProject project, String url) { Pair<ResourceType,String> parsedUrl = parseResource(url); if (parsedUrl == null) { return false; } + Sdk currentSdk = Sdk.getCurrent(); + if (currentSdk == null) { + return false; + } + IAndroidTarget target = currentSdk.getTarget(project); + if (target == null) { + return false; + } ResourceType type = parsedUrl.getFirst(); String name = parsedUrl.getSecond(); // Attempt to open files, such as layouts and drawables in @android? if (isFileResource(type)) { - Sdk currentSdk = Sdk.getCurrent(); - if (currentSdk == null) { - return false; - } - IAndroidTarget target = currentSdk.getTarget(project); - if (target == null) { - return false; - } AndroidTargetData data = currentSdk.getTargetData(target); if (data == null) { return false; @@ -510,15 +547,27 @@ public class Hyperlinks { // string value of @android:string/cancel => "Cancel"). if (new File(valueStr).exists()) { Path path = new Path(valueStr); - openPath(path, null); + openPath(path, null, -1); + return true; } - return true; + break; } } else { - return false; + break; } } else { - return false; + break; + } + } + } else if (isValueResource(type)) { + File values = new File(target.getPath(IAndroidTarget.RESOURCES), + SdkConstants.FD_VALUES); + if (values.exists()) { + Pair<File, Integer> match = findValueDefinition(values, type, name); + if (match != null) { + Path path = new Path(match.getFirst().getPath()); + openPath(path, null, match.getSecond()); + return true; } } } @@ -868,6 +917,91 @@ public class Hyperlinks { return Pair.of(type, name); } + /** + * Searches for a resource of a "multi-file" type (like @string) where the value can + * be found in any file within the folder containing resource of that type (in the + * case of @string, "values", and in the case of @color, "colors", etc). + * <p> + * This method operates on plain {@link File} objects and is intended for searches in + * the Android platform files; for project-relative searches use + * {@link #findValueDefinition(File, ResourceType, String)}. + */ + private static Pair<File, Integer> findValueDefinition(File resourceDir, ResourceType type, + String name) { + // Search within the files in the values folder and find the value which defines + // the given resource. To be efficient, we will only parse XML files that contain + // a string match of the given token name. + + // Check XML files in values/ + File[] children = resourceDir.listFiles(); + if (children == null) { + return null; + } + for (File resource : children) { + // Must have an XML extension + if (resource.getName().endsWith(EXT_XML)) { + Pair<File, Integer> target = findValueInXml(type, name, resource); + if (target != null) { + return target; + } + } + } + + return null; + } + + /** Parses the given file and locates a definition of the given resource */ + private static Pair<File, Integer> findValueInXml(ResourceType type, String name, File file) { + // We can't use the StructureModelManager on files outside projects + // There is no open or cached model for the file; see if the file looks + // like it's interesting (content contains the String name we are looking for) + if (AdtPlugin.fileContains(file, name)) { + try { + InputSource is = new InputSource(new FileInputStream(file)); + OffsetTrackingParser parser = new OffsetTrackingParser(); + parser.parse(is); + Document document = parser.getDocument(); + + return findValueInDocument(type, name, file, parser, document); + } catch (SAXException e) { + // pass -- ignore files we can't parse + } catch (IOException e) { + // pass -- ignore files we can't parse + } + } + + return null; + } + + /** Looks within an XML DOM document for the given resource name and returns it */ + private static Pair<File, Integer> findValueInDocument(ResourceType type, String name, + File file, OffsetTrackingParser parser, Document document) { + String targetTag = type.getName(); + if (type == ResourceType.ID) { + // Ids are recorded in <item> tags instead of <id> tags + targetTag = "item"; //$NON-NLS-1$ + } + Element root = document.getDocumentElement(); + if (root.getTagName().equals(ROOT_ELEMENT)) { + NodeList children = root.getChildNodes(); + for (int i = 0, n = children.getLength(); i < n; i++) { + Node child = children.item(i); + if (child.getNodeType() == Node.ELEMENT_NODE) { + Element element = (Element) child; + if (element.getTagName().equals(targetTag)) { + String elementName = element.getAttribute(NAME_ATTR); + if (elementName.equals(name)) { + + return Pair.of(file, parser.getOffset(element)); + } + } + } + } + } + + return null; + } + /** Detector for finding Android references in XML files */ public static class XmlResolver extends AbstractHyperlinkDetector { @@ -955,19 +1089,26 @@ public class Hyperlinks { IJavaElement element = elements[0]; if (element.getElementType() == IJavaElement.FIELD) { IJavaElement unit = element.getAncestor(IJavaElement.COMPILATION_UNIT); - if ("R.java".equals(unit.getElementName())) { //$NON-NLS-1$ - // Yes, we're in an R class. Offer hyperlink navigation to XML - // resource - // files for the various definitions + if (unit == null) { + // Probably in a binary; see if this is an android.R resource + IJavaElement type = element.getAncestor(IJavaElement.TYPE); + if (type != null && type.getParent() != null) { + IJavaElement parentType = type.getParent(); + if (parentType.getElementType() == IJavaElement.CLASS_FILE) { + String pn = parentType.getElementName(); + String prefix = FN_RESOURCE_BASE + "$"; //$NON-NLS-1$ + if (pn.startsWith(prefix)) { + return createTypeLink(element, type, wordRegion, true); + } + } + } + } else if (FN_RESOURCE_CLASS.equals(unit.getElementName())) { + // Yes, we're referencing the project R class. + // Offer hyperlink navigation to XML resource files for + // the various definitions IJavaElement type = element.getAncestor(IJavaElement.TYPE); if (type != null) { - String typeName = type.getElementName(); - // typeName will be "id", "layout", "string", etc - String elementName = element.getElementName(); - String url = '@' + typeName + '/' + elementName; - return new IHyperlink[] { - new DeferredResolutionLink(url, null, wordRegion) - }; + return createTypeLink(element, type, wordRegion, false); } } } @@ -978,6 +1119,20 @@ public class Hyperlinks { return null; } } + + private IHyperlink[] createTypeLink(IJavaElement element, IJavaElement type, + IRegion wordRegion, boolean isFrameworkResource) { + String typeName = type.getElementName(); + // typeName will be "id", "layout", "string", etc + if (isFrameworkResource) { + typeName = ANDROID_PKG + ':' + typeName; + } + String elementName = element.getElementName(); + String url = '@' + typeName + '/' + elementName; + return new IHyperlink[] { + new DeferredResolutionLink(url, null, wordRegion) + }; + } } /** Returns the editor applicable to this hyperlink detection */ @@ -1199,4 +1354,55 @@ public class Hyperlinks { return null; } } + + /** + * DOM parser which records offsets in the element nodes such that it can return + * offsets for elements later + */ + private static final class OffsetTrackingParser extends DOMParser { + + private static final String KEY_OFFSET = "offset"; //$NON-NLS-1$ + + private static final String KEY_NODE = + "http://apache.org/xml/properties/dom/current-element-node"; //$NON-NLS-1$ + + private XMLLocator mLocator; + + public OffsetTrackingParser() throws SAXException { + this.setFeature("http://apache.org/xml/features/dom/defer-node-expansion",//$NON-NLS-1$ + false); + } + + public int getOffset(Node node) { + Integer offset = (Integer) node.getUserData(KEY_OFFSET); + if (offset != null) { + return offset; + } + + return -1; + } + + @Override + public void startElement(QName elementQName, XMLAttributes attrList, Augmentations augs) + throws XNIException { + int offset = mLocator.getCharacterOffset(); + super.startElement(elementQName, attrList, augs); + + try { + Node node = (Node) this.getProperty(KEY_NODE); + if (node != null) { + node.setUserData(KEY_OFFSET, offset, null); + } + } catch (org.xml.sax.SAXException ex) { + AdtPlugin.log(ex, ""); //$NON-NLS-1$ + } + } + + @Override + public void startDocument(XMLLocator locator, String encoding, + NamespaceContext namespaceContext, Augmentations augs) throws XNIException { + super.startDocument(locator, encoding, namespaceContext, augs); + mLocator = locator; + } + } } |