* @param region an optional region which if set will be selected and shown to the
* user
* @param showEditorTab if true, front the editor tab after opening the file
+ * @return the editor that was opened, or null if no editor was opened
* @throws PartInitException if something goes wrong
*/
- public static void openFile(IFile file, IRegion region, boolean showEditorTab)
+ public static IEditorPart openFile(IFile file, IRegion region, boolean showEditorTab)
throws PartInitException {
IWorkbench workbench = PlatformUI.getWorkbench();
if (workbench == null) {
- return;
+ return null;
}
IWorkbenchWindow activeWorkbenchWindow = workbench.getActiveWorkbenchWindow();
if (activeWorkbenchWindow == null) {
- return;
+ return null;
}
IWorkbenchPage page = activeWorkbenchWindow.getActivePage();
if (page == null) {
- return;
+ return null;
}
IEditorPart targetEditor = IDE.openEditor(page, file, true);
if (targetEditor instanceof AndroidXmlEditor) {
AndroidXmlEditor editor = (AndroidXmlEditor) targetEditor;
if (region != null) {
- editor.show(region.getOffset(), region.getLength());
+ editor.show(region.getOffset(), region.getLength(), showEditorTab);
} else if (showEditorTab) {
editor.setActivePage(AndroidXmlEditor.TEXT_EDITOR_ID);
}
}
+
+ return targetEditor;
}
}
*
* @param start the beginning offset
* @param length the length of the region to show
+ * @param frontTab if true, front the tab, otherwise just make the selection but don't
+ * change the active tab
*/
- public void show(int start, int length) {
+ public void show(int start, int length, boolean frontTab) {
IEditorPart textPage = getEditor(mTextPageIndex);
if (textPage instanceof StructuredTextEditor) {
StructuredTextEditor editor = (StructuredTextEditor) textPage;
- setActivePage(AndroidXmlEditor.TEXT_EDITOR_ID);
+ if (frontTab) {
+ setActivePage(AndroidXmlEditor.TEXT_EDITOR_ID);
+ }
editor.selectAndReveal(start, length);
+ if (frontTab) {
+ editor.setFocus();
+ }
}
}
/**
+ * Returns true if this editor has more than one page (usually a graphical view and an
+ * editor)
+ *
+ * @return true if this editor has multiple pages
+ */
+ public boolean hasMultiplePages() {
+ return getPageCount() > 1;
+ }
+
+ /**
* Get the XML text directly from the editor.
*
* @param xmlNode The node whose XML text we want to obtain.
*/
package com.android.ide.eclipse.adt.internal.editors.formatting;
+import com.android.resources.ResourceFolderType;
+import com.android.resources.ResourceType;
+import com.android.sdklib.SdkConstants;
+
+import org.eclipse.core.runtime.IPath;
+
/**
* Style to use when printing the XML. Different types of Android XML files use slightly
* different preferred formats. For example, in layout files there is typically always a
* included on the same line whereas for other layout styles the children are typically
* placed on a line of their own.
*/
-enum XmlFormatStyle {
+public enum XmlFormatStyle {
/** Layout formatting style: blank lines between elements, attributes on separate lines */
LAYOUT,
* single block with no whitespace within it)
*/
MANIFEST;
+
+ /**
+ * Returns the {@link XmlFormatStyle} to use for a resource of the given type
+ *
+ * @param resourceType the type of resource to be formatted
+ * @return the suitable format style to use
+ */
+ public static XmlFormatStyle get(ResourceType resourceType) {
+ switch (resourceType) {
+ case ARRAY:
+ case ATTR:
+ case BOOL:
+ case DECLARE_STYLEABLE:
+ case DIMEN:
+ case FRACTION:
+ case ID:
+ case INTEGER:
+ case STRING:
+ case STYLE:
+ case STYLEABLE:
+ return RESOURCE;
+
+ default:
+ return LAYOUT;
+ }
+ }
+
+ /**
+ * Returns the {@link XmlFormatStyle} to use for resource files in the given resource
+ * folder
+ *
+ * @param folderType the type of folder containing the resource file
+ * @return the suitable format style to use
+ */
+ public static XmlFormatStyle getForFolderType(ResourceFolderType folderType) {
+ if (folderType == ResourceFolderType.VALUES) {
+ return RESOURCE;
+ } else {
+ return LAYOUT;
+ }
+ }
+
+ /**
+ * Returns the {@link XmlFormatStyle} to use for resource files of the given path.
+ *
+ * @param path the path to the resource file
+ * @return the suitable format style to use
+ */
+ public static XmlFormatStyle getForFile(IPath path) {
+ if (SdkConstants.FN_ANDROID_MANIFEST_XML.equals(path.lastSegment())) {
+ return MANIFEST;
+ }
+
+ String parentName = path.segment(path.segmentCount() - 1);
+ ResourceFolderType folderType = ResourceFolderType.getFolderType(parentName);
+ return getForFolderType(folderType);
+ }
}
\ No newline at end of file
import static com.android.ide.eclipse.adt.internal.editors.resources.descriptors.ResourcesDescriptors.STYLE_ELEMENT;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
+import com.android.resources.ResourceFolderType;
import org.eclipse.wst.xml.core.internal.document.DocumentTypeImpl;
import org.eclipse.wst.xml.core.internal.document.ElementImpl;
import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
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 java.io.StringReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
/**
* Visitor which walks over the subtree of the DOM to be formatted and pretty prints
* the DOM into the given {@link StringBuilder}
*/
@SuppressWarnings("restriction")
-class XmlPrettyPrinter {
+public class XmlPrettyPrinter {
/** The style to print the XML in */
private final XmlFormatStyle mStyle;
/** Formatting preferences to use when formatting the XML */
* @param lineSeparator the line separator to use, such as "\n" (can be null, in which
* case the system default is looked up via the line.separator property)
*/
- XmlPrettyPrinter(XmlFormatPreferences prefs, XmlFormatStyle style, String lineSeparator) {
+ public XmlPrettyPrinter(XmlFormatPreferences prefs, XmlFormatStyle style,
+ String lineSeparator) {
mPrefs = prefs;
mStyle = style;
if (lineSeparator == null) {
}
/**
+ * Creates a new {@link XmlPrettyPrinter}
+ *
+ * @param prefs the preferences to format with
+ * @param folderType the type of resource folder containing the XML content to be formatted
+ * @param lineSeparator the line separator to use, such as "\n"
+ */
+ public XmlPrettyPrinter(XmlFormatPreferences prefs, ResourceFolderType folderType,
+ String lineSeparator) {
+ this(prefs, XmlFormatStyle.getForFolderType(folderType), lineSeparator);
+ }
+
+ /**
+ * Pretty-prints the given XML document, which must be well-formed. If it is not,
+ * the original unformatted XML document is returned
+ *
+ * @param xml the XML content to format
+ * @param prefs the preferences to format with
+ * @param style the style to format with
+ * @param lineSeparator the line separator to use, such as "\n" (can be null, in which
+ * case the system default is looked up via the line.separator property)
+ * @return the formatted document
+ */
+ public static String prettyPrint(String xml, XmlFormatPreferences prefs, XmlFormatStyle style,
+ String lineSeparator) {
+ try {
+ // TODO: Use the proper XML model from Eclipse such that I can handle empty
+ // tags properly. Unfortunately, this is tricky; the Eclipse XML model wants to
+ // be tied to an IFile. A possible solution is described in
+ // http://www.eclipse.org/forums/index.php/t/73640/ .
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ InputSource is = new InputSource(new StringReader(xml));
+ factory.setIgnoringComments(false);
+ factory.setIgnoringElementContentWhitespace(false);
+ factory.setCoalescing(false);
+ factory.setNamespaceAware(true);
+ factory.setValidating(false);
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ Document document = builder.parse(is);
+
+ XmlPrettyPrinter printer = new XmlPrettyPrinter(prefs, style, lineSeparator);
+ StringBuilder sb = new StringBuilder(1000);
+ printer.prettyPrint(-1, document, null, null, sb);
+ return sb.toString();
+ } catch (Exception e) {
+ return xml;
+ }
+ }
+
+ /**
* Start pretty-printing at the given node, which must either be the
* startNode or contain it as a descendant.
*
import com.android.ide.common.layout.LayoutConstants;
import com.android.ide.eclipse.adt.AdtConstants;
import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.editors.formatting.XmlFormatPreferences;
+import com.android.ide.eclipse.adt.internal.editors.formatting.XmlFormatStyle;
+import com.android.ide.eclipse.adt.internal.editors.formatting.XmlPrettyPrinter;
+import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
import com.android.ide.eclipse.adt.internal.project.AndroidNature;
import com.android.ide.eclipse.adt.internal.project.ProjectHelper;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
manifestTemplate = manifestTemplate.replaceAll(PH_USES_SDK, "");
}
+ // Reformat the file according to the user's formatting settings
+ // This is disabled for now since the non-file-based pretty printing uses
+ // the JDK XML parser instead of the Eclipse parser, and the JDK parser does
+ // not track "empty tags", so <uses-sdk/> turns into <uses-sdk></uses-sdk>
+ //manifestTemplate = reformat(XmlFormatStyle.MANIFEST, manifestTemplate);
+
// Save in the project as UTF-8
InputStream stream = new ByteArrayInputStream(
manifestTemplate.getBytes("UTF-8")); //$NON-NLS-1$
stringDefinitionTemplate = stringDefinitionTemplate.replace(PH_STRINGS,
stringNodes.toString());
+ // reformat the file according to the user's formatting settings
+ stringDefinitionTemplate = reformat(XmlFormatStyle.RESOURCE, stringDefinitionTemplate);
+
// write the file as UTF-8
InputStream stream = new ByteArrayInputStream(
stringDefinitionTemplate.getBytes("UTF-8")); //$NON-NLS-1$
}
}
+ /** Reformats the given contents with the current formatting settings */
+ private String reformat(XmlFormatStyle style, String contents) {
+ if (AdtPrefs.getPrefs().getUseCustomXmlFormatter()) {
+ XmlFormatPreferences formatPrefs = XmlFormatPreferences.create();
+ return XmlPrettyPrinter.prettyPrint(contents, formatPrefs, style,
+ null /*lineSeparator*/);
+ } else {
+ return contents;
+ }
+ }
/**
* Adds default application icon to the project.
String activityJava = activityName + AdtConstants.DOT_JAVA;
IFile file = pkgFolder.getFile(activityJava);
if (!file.exists()) {
- copyFile(JAVA_ACTIVITY_TEMPLATE, file, java_activity_parameters, monitor);
+ copyFile(JAVA_ACTIVITY_TEMPLATE, file, java_activity_parameters, monitor, false);
}
}
IFolder layoutfolder = project.getFolder(RES_DIRECTORY).getFolder(LAYOUT_DIRECTORY);
IFile file = layoutfolder.getFile(MAIN_LAYOUT_XML);
if (!file.exists()) {
- copyFile(LAYOUT_TEMPLATE, file, parameters, monitor);
+ copyFile(LAYOUT_TEMPLATE, file, parameters, monitor, true);
if (activityName != null) {
dictionary.put(STRING_HELLO_WORLD, "Hello World, " + activityName + "!");
} else {
* length.
*/
private void copyFile(String resourceFilename, IFile destFile,
- Map<String, Object> parameters, IProgressMonitor monitor)
+ Map<String, Object> parameters, IProgressMonitor monitor, boolean reformat)
throws CoreException, IOException {
// Read existing file.
// Replace all keyword parameters
template = replaceParameters(template, parameters);
+ if (reformat) {
+ // Guess the formatting style based on the file location
+ XmlFormatStyle style = XmlFormatStyle.getForFile(destFile.getProjectRelativePath());
+ if (style != null) {
+ template = reformat(style, template);
+ }
+ }
+
// Save in the project as UTF-8
InputStream stream = new ByteArrayInputStream(template.getBytes("UTF-8")); //$NON-NLS-1$
destFile.create(stream, false /* force */, new SubProgressMonitor(monitor, 10));
import com.android.ide.eclipse.adt.internal.editors.menu.descriptors.MenuDescriptors;
import com.android.ide.eclipse.adt.internal.editors.resources.descriptors.ResourcesDescriptors;
import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
-import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper.IProjectFilter;
import com.android.ide.eclipse.adt.internal.project.ProjectChooserHelper;
+import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper.IProjectFilter;
import com.android.ide.eclipse.adt.internal.resources.ResourceNameValidator;
import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
"An XML file that describes a color state list.", // tooltip
ResourceFolderType.COLOR, // folder type
AndroidTargetData.DESCRIPTOR_COLOR, // root seed
- null, // default root
+ "selector", //$NON-NLS-1$ // default root
SdkConstants.NS_RESOURCES, // xmlns
null, // default attributes
1 // target API level
"An XML file that describes an animator.", // tooltip
ResourceFolderType.ANIMATOR, // folder type
AndroidTargetData.DESCRIPTOR_ANIMATOR, // root seed
- "set", // default root
+ "set", //$NON-NLS-1$ // default root
SdkConstants.NS_RESOURCES, // xmlns
null, // default attributes
11 // target API level
"An XML file that describes an animation.", // tooltip
ResourceFolderType.ANIM, // folder type
AndroidTargetData.DESCRIPTOR_ANIM, // root seed
- "set", // default root
+ "set", //$NON-NLS-1$ // default root
null, // xmlns
null, // default attributes
1 // target API level
package com.android.ide.eclipse.adt.internal.wizards.newxmlfile;
import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
import com.android.ide.eclipse.adt.internal.editors.IconFactory;
+import com.android.ide.eclipse.adt.internal.editors.formatting.XmlFormatPreferences;
+import com.android.ide.eclipse.adt.internal.editors.formatting.XmlFormatStyle;
+import com.android.ide.eclipse.adt.internal.editors.formatting.XmlPrettyPrinter;
+import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
import com.android.ide.eclipse.adt.internal.wizards.newxmlfile.NewXmlFileCreationPage.TypeInfo;
import com.android.resources.ResourceFolderType;
import com.android.util.Pair;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.wizard.Wizard;
+import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.INewWizard;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.PartInitException;
*/
@Override
public boolean performFinish() {
- Pair<IFile, IRegion> created = createXmlFile();
+ final Pair<IFile, IRegion> created = createXmlFile();
if (created == null) {
return false;
} else {
- IFile file = created.getFirst();
-
// Open the file
- try {
- AdtPlugin.openFile(file, null, false /* showEditorTab */);
- } catch (PartInitException e) {
- AdtPlugin.log(e, "Failed to create %1$s: missing type", //$NON-NLS-1$
- file.getFullPath().toString());
- }
+ // This has to be delayed in order for focus handling to work correctly
+ AdtPlugin.getDisplay().asyncExec(new Runnable() {
+ public void run() {
+ IFile file = created.getFirst();
+ IRegion region = created.getSecond();
+ try {
+ IEditorPart editor = AdtPlugin.openFile(file, null,
+ false /*showEditorTab*/);
+ if (editor instanceof AndroidXmlEditor) {
+ final AndroidXmlEditor xmlEditor = (AndroidXmlEditor)editor;
+ if (!xmlEditor.hasMultiplePages()) {
+ xmlEditor.show(region.getOffset(), region.getLength(),
+ true /* showEditorTab */);
+ }
+ }
+ } catch (PartInitException e) {
+ AdtPlugin.log(e, "Failed to create %1$s: missing type", //$NON-NLS-1$
+ file.getFullPath().toString());
+ }
+ }});
+
return true;
}
}
String attrs = type.getDefaultAttrs(mMainPage.getProject(), root);
String child = type.getChild(mMainPage.getProject(), root);
- return createXmlFile(file, xmlns, root, attrs, child);
+ return createXmlFile(file, xmlns, root, attrs, child, type.getResFolderType());
}
/** Creates a new file using the given root element, namespace and root attributes */
private static Pair<IFile, IRegion> createXmlFile(IFile file, String xmlns,
- String root, String rootAttributes, String child) {
+ String root, String rootAttributes, String child, ResourceFolderType folderType) {
String name = file.getFullPath().toString();
boolean need_delete = false;
sb.append(child);
}
- // The insertion line
- sb.append(" "); //$NON-NLS-1$
- int caretOffset = sb.length();
- sb.append("\n"); //$NON-NLS-1$
+ // Insert an indented caret. Since the markup here will be reformatted, we need to
+ // insert text tokens that the formatter will preserve, which we can then turn back
+ // into indentation and a caret offset:
+ final String indentToken = "${indent}"; //$NON-NLS-1$
+ final String caretToken = "${caret}"; //$NON-NLS-1$
+ sb.append(indentToken);
+ sb.append(caretToken);
sb.append("</").append(root).append(">\n"); //$NON-NLS-1$ //$NON-NLS-2$
- String result = sb.toString();
+ XmlFormatPreferences formatPrefs = XmlFormatPreferences.create();
+ String fileContents;
+ if (AdtPrefs.getPrefs().getUseCustomXmlFormatter()) {
+ fileContents = sb.toString();
+ } else {
+ XmlFormatStyle style = XmlFormatStyle.getForFolderType(folderType);
+ fileContents = XmlPrettyPrinter.prettyPrint(sb.toString(), formatPrefs,
+ style, null /*lineSeparator*/);
+ }
+
+ // Remove marker tokens and replace them with whitespace
+ fileContents = fileContents.replace(indentToken, formatPrefs.getOneIndentUnit());
+ int caretOffset = fileContents.indexOf(caretToken);
+ if (caretOffset != -1) {
+ fileContents = fileContents.replace(caretToken, ""); //$NON-NLS-1$
+ }
+
String error = null;
try {
- byte[] buf = result.getBytes("UTF8"); //$NON-NLS-1$
+ byte[] buf = fileContents.getBytes("UTF8"); //$NON-NLS-1$
InputStream stream = new ByteArrayInputStream(buf);
if (need_delete) {
file.delete(IResource.KEEP_HISTORY | IResource.FORCE, null /*monitor*/);
}
file.create(stream, true /*force*/, null /*progress*/);
- IRegion region = new Region(caretOffset, 0);
+ IRegion region = caretOffset != -1 ? new Region(caretOffset, 0) : null;
return Pair.of(file, region);
} catch (UnsupportedEncodingException e) {
error = e.getMessage();
root = type.getRootSeed().toString();
}
String attrs = type.getDefaultAttrs(project, root);
- return createXmlFile(file, xmlns, root, attrs, null);
+ return createXmlFile(file, xmlns, root, attrs, null, folderType);
}
public static boolean createWsParentDirectory(IContainer wsPath) {