aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlEditor.java
diff options
context:
space:
mode:
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlEditor.java')
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlEditor.java1709
1 files changed, 0 insertions, 1709 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlEditor.java
deleted file mode 100644
index 1d4e133b6..000000000
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlEditor.java
+++ /dev/null
@@ -1,1709 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.eclipse.org/org/documents/epl-v10.php
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.eclipse.adt.internal.editors;
-
-import static org.eclipse.wst.sse.ui.internal.actions.StructuredTextEditorActionConstants.ACTION_NAME_FORMAT_DOCUMENT;
-
-import com.android.annotations.Nullable;
-import com.android.ide.eclipse.adt.AdtConstants;
-import com.android.ide.eclipse.adt.AdtPlugin;
-import com.android.ide.eclipse.adt.AdtUtils;
-import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
-import com.android.ide.eclipse.adt.internal.lint.EclipseLintRunner;
-import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
-import com.android.ide.eclipse.adt.internal.refactorings.core.RenameResourceXmlTextAction;
-import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
-import com.android.ide.eclipse.adt.internal.sdk.Sdk;
-import com.android.ide.eclipse.adt.internal.sdk.Sdk.ITargetChangeListener;
-import com.android.ide.eclipse.adt.internal.sdk.Sdk.TargetChangeListener;
-import com.android.sdklib.IAndroidTarget;
-
-import org.eclipse.core.resources.IFile;
-import org.eclipse.core.resources.IMarker;
-import org.eclipse.core.resources.IProject;
-import org.eclipse.core.resources.IResource;
-import org.eclipse.core.runtime.CoreException;
-import org.eclipse.core.runtime.IProgressMonitor;
-import org.eclipse.core.runtime.IStatus;
-import org.eclipse.core.runtime.QualifiedName;
-import org.eclipse.core.runtime.Status;
-import org.eclipse.core.runtime.jobs.Job;
-import org.eclipse.jdt.ui.actions.IJavaEditorActionDefinitionIds;
-import org.eclipse.jface.action.Action;
-import org.eclipse.jface.action.IAction;
-import org.eclipse.jface.dialogs.ErrorDialog;
-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.source.ISourceViewer;
-import org.eclipse.swt.custom.StyledText;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.ui.IActionBars;
-import org.eclipse.ui.IEditorInput;
-import org.eclipse.ui.IEditorPart;
-import org.eclipse.ui.IEditorReference;
-import org.eclipse.ui.IEditorSite;
-import org.eclipse.ui.IFileEditorInput;
-import org.eclipse.ui.IURIEditorInput;
-import org.eclipse.ui.IWorkbenchPage;
-import org.eclipse.ui.IWorkbenchWindow;
-import org.eclipse.ui.PartInitException;
-import org.eclipse.ui.PlatformUI;
-import org.eclipse.ui.actions.ActionFactory;
-import org.eclipse.ui.browser.IWorkbenchBrowserSupport;
-import org.eclipse.ui.forms.IManagedForm;
-import org.eclipse.ui.forms.editor.FormEditor;
-import org.eclipse.ui.forms.editor.IFormPage;
-import org.eclipse.ui.forms.events.HyperlinkAdapter;
-import org.eclipse.ui.forms.events.HyperlinkEvent;
-import org.eclipse.ui.forms.events.IHyperlinkListener;
-import org.eclipse.ui.forms.widgets.FormText;
-import org.eclipse.ui.ide.IDEActionFactory;
-import org.eclipse.ui.ide.IGotoMarker;
-import org.eclipse.ui.internal.browser.WorkbenchBrowserSupport;
-import org.eclipse.ui.part.MultiPageEditorPart;
-import org.eclipse.ui.part.WorkbenchPart;
-import org.eclipse.ui.views.contentoutline.IContentOutlinePage;
-import org.eclipse.wst.sse.core.StructuredModelManager;
-import org.eclipse.wst.sse.core.internal.provisional.IModelManager;
-import org.eclipse.wst.sse.core.internal.provisional.IModelStateListener;
-import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
-import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
-import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
-import org.eclipse.wst.sse.ui.StructuredTextEditor;
-import org.eclipse.wst.sse.ui.internal.StructuredTextViewer;
-import org.eclipse.wst.xml.core.internal.document.NodeContainer;
-import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel;
-import org.w3c.dom.Document;
-import org.w3c.dom.Node;
-
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.Collections;
-
-/**
- * Multi-page form editor for Android XML files.
- * <p/>
- * It is designed to work with a {@link StructuredTextEditor} that will display an XML file.
- * <br/>
- * Derived classes must implement createFormPages to create the forms before the
- * source editor. This can be a no-op if desired.
- */
-@SuppressWarnings("restriction") // Uses XML model, which has no non-restricted replacement yet
-public abstract class AndroidXmlEditor extends FormEditor {
-
- /** Icon used for the XML source page. */
- public static final String ICON_XML_PAGE = "editor_page_source"; //$NON-NLS-1$
-
- /** Preference name for the current page of this file */
- private static final String PREF_CURRENT_PAGE = "_current_page"; //$NON-NLS-1$
-
- /** Id string used to create the Android SDK browser */
- private static String BROWSER_ID = "android"; //$NON-NLS-1$
-
- /** Page id of the XML source editor, used for switching tabs programmatically */
- public final static String TEXT_EDITOR_ID = "editor_part"; //$NON-NLS-1$
-
- /** Width hint for text fields. Helps the grid layout resize properly on smaller screens */
- public static final int TEXT_WIDTH_HINT = 50;
-
- /** Page index of the text editor (always the last page) */
- protected int mTextPageIndex;
- /** The text editor */
- private StructuredTextEditor mTextEditor;
- /** Listener for the XML model from the StructuredEditor */
- private XmlModelStateListener mXmlModelStateListener;
- /** Listener to update the root node if the target of the file is changed because of a
- * SDK location change or a project target change */
- private TargetChangeListener mTargetListener = null;
-
- /** flag set during page creation */
- private boolean mIsCreatingPage = false;
-
- /**
- * Flag used to ignore XML model updates. For example, the flag is set during
- * formatting. A format operation should completely preserve the semantics of the XML
- * so the document listeners can use this flag to skip updating the model when edits
- * are observed during a formatting operation
- */
- private boolean mIgnoreXmlUpdate;
-
- /**
- * Flag indicating we're inside {@link #wrapEditXmlModel(Runnable)}.
- * This is a counter, which allows us to nest the edit XML calls.
- * There is no pending operation when the counter is at zero.
- */
- private int mIsEditXmlModelPending;
-
- /**
- * Usually null, but during an editing operation, represents the highest
- * node which should be formatted when the editing operation is complete.
- */
- private UiElementNode mFormatNode;
-
- /**
- * Whether {@link #mFormatNode} should be formatted recursively, or just
- * the node itself (its arguments)
- */
- private boolean mFormatChildren;
-
- /**
- * Creates a form editor.
- * <p/>
- * Some derived classes will want to use {@link #addDefaultTargetListener()}
- * to setup the default listener to monitor SDK target changes. This
- * is no longer the default.
- */
- public AndroidXmlEditor() {
- super();
- }
-
- @Override
- public void init(IEditorSite site, IEditorInput input) throws PartInitException {
- super.init(site, input);
- // Trigger a check to see if the SDK needs to be reloaded (which will
- // invoke onSdkLoaded or ITargetChangeListener asynchronously as needed).
- AdtPlugin.getDefault().refreshSdk();
- }
-
- /**
- * Setups a default {@link ITargetChangeListener} that will call
- * {@link #initUiRootNode(boolean)} when the SDK or the target changes.
- */
- public void addDefaultTargetListener() {
- if (mTargetListener == null) {
- mTargetListener = new TargetChangeListener() {
- @Override
- public IProject getProject() {
- return AndroidXmlEditor.this.getProject();
- }
-
- @Override
- public void reload() {
- commitPages(false /* onSave */);
-
- // recreate the ui root node always
- initUiRootNode(true /*force*/);
- }
- };
- AdtPlugin.getDefault().addTargetListener(mTargetListener);
- }
- }
-
- // ---- Abstract Methods ----
-
- /**
- * Returns the root node of the UI element hierarchy manipulated by the current
- * UI node editor.
- */
- abstract public UiElementNode getUiRootNode();
-
- /**
- * Creates the various form pages.
- * <p/>
- * Derived classes must implement this to add their own specific tabs.
- */
- abstract protected void createFormPages();
-
- /**
- * Called by the base class {@link AndroidXmlEditor} once all pages (custom form pages
- * as well as text editor page) have been created. This give a chance to deriving
- * classes to adjust behavior once the text page has been created.
- */
- protected void postCreatePages() {
- // Nothing in the base class.
- }
-
- /**
- * Creates the initial UI Root Node, including the known mandatory elements.
- * @param force if true, a new UiManifestNode is recreated even if it already exists.
- */
- abstract protected void initUiRootNode(boolean force);
-
- /**
- * Subclasses should override this method to process the new XML Model, which XML
- * root node is given.
- *
- * The base implementation is empty.
- *
- * @param xml_doc The XML document, if available, or null if none exists.
- */
- abstract protected void xmlModelChanged(Document xml_doc);
-
- /**
- * Controls whether XML models are ignored or not.
- *
- * @param ignore when true, ignore all subsequent XML model updates, when false start
- * processing XML model updates again
- */
- public void setIgnoreXmlUpdate(boolean ignore) {
- mIgnoreXmlUpdate = ignore;
- }
-
- /**
- * Returns whether XML model events are ignored or not. This is the case
- * when we are deliberately modifying the document in a way which does not
- * change the semantics (such as formatting), or when we have already
- * directly updated the model ourselves.
- *
- * @return true if XML events should be ignored
- */
- public boolean getIgnoreXmlUpdate() {
- return mIgnoreXmlUpdate;
- }
-
- // ---- Base Class Overrides, Interfaces Implemented ----
-
- @Override
- public Object getAdapter(@SuppressWarnings("rawtypes") Class adapter) {
- Object result = super.getAdapter(adapter);
-
- if (result != null && adapter.equals(IGotoMarker.class) ) {
- final IGotoMarker gotoMarker = (IGotoMarker) result;
- return new IGotoMarker() {
- @Override
- public void gotoMarker(IMarker marker) {
- gotoMarker.gotoMarker(marker);
- try {
- // Lint markers should always jump to XML text
- if (marker.getType().equals(AdtConstants.MARKER_LINT)) {
- IEditorPart editor = AdtUtils.getActiveEditor();
- if (editor instanceof AndroidXmlEditor) {
- AndroidXmlEditor xmlEditor = (AndroidXmlEditor) editor;
- xmlEditor.setActivePage(AndroidXmlEditor.TEXT_EDITOR_ID);
- }
- }
- } catch (CoreException e) {
- AdtPlugin.log(e, null);
- }
- }
- };
- }
-
- if (result == null && adapter == IContentOutlinePage.class) {
- return getStructuredTextEditor().getAdapter(adapter);
- }
-
- return result;
- }
-
- /**
- * Creates the pages of the multi-page editor.
- */
- @Override
- protected void addPages() {
- createAndroidPages();
- selectDefaultPage(null /* defaultPageId */);
- }
-
- /**
- * Creates the page for the Android Editors
- */
- public void createAndroidPages() {
- mIsCreatingPage = true;
- createFormPages();
- createTextEditor();
- updateActionBindings();
- postCreatePages();
- mIsCreatingPage = false;
- }
-
- /**
- * Returns whether the editor is currently creating its pages.
- */
- public boolean isCreatingPages() {
- return mIsCreatingPage;
- }
-
- /**
- * {@inheritDoc}
- * <p/>
- * If the page is an instance of {@link IPageImageProvider}, the image returned by
- * by {@link IPageImageProvider#getPageImage()} will be set on the page's tab.
- */
- @Override
- public int addPage(IFormPage page) throws PartInitException {
- int index = super.addPage(page);
- if (page instanceof IPageImageProvider) {
- setPageImage(index, ((IPageImageProvider) page).getPageImage());
- }
- return index;
- }
-
- /**
- * {@inheritDoc}
- * <p/>
- * If the editor is an instance of {@link IPageImageProvider}, the image returned by
- * by {@link IPageImageProvider#getPageImage()} will be set on the page's tab.
- */
- @Override
- public int addPage(IEditorPart editor, IEditorInput input) throws PartInitException {
- int index = super.addPage(editor, input);
- if (editor instanceof IPageImageProvider) {
- setPageImage(index, ((IPageImageProvider) editor).getPageImage());
- }
- return index;
- }
-
- /**
- * Creates undo redo (etc) actions for the editor site (so that it works for any page of this
- * multi-page editor) by re-using the actions defined by the {@link StructuredTextEditor}
- * (aka the XML text editor.)
- */
- protected void updateActionBindings() {
- IActionBars bars = getEditorSite().getActionBars();
- if (bars != null) {
- IAction action = mTextEditor.getAction(ActionFactory.UNDO.getId());
- bars.setGlobalActionHandler(ActionFactory.UNDO.getId(), action);
-
- action = mTextEditor.getAction(ActionFactory.REDO.getId());
- bars.setGlobalActionHandler(ActionFactory.REDO.getId(), action);
-
- bars.setGlobalActionHandler(ActionFactory.DELETE.getId(),
- mTextEditor.getAction(ActionFactory.DELETE.getId()));
- bars.setGlobalActionHandler(ActionFactory.CUT.getId(),
- mTextEditor.getAction(ActionFactory.CUT.getId()));
- bars.setGlobalActionHandler(ActionFactory.COPY.getId(),
- mTextEditor.getAction(ActionFactory.COPY.getId()));
- bars.setGlobalActionHandler(ActionFactory.PASTE.getId(),
- mTextEditor.getAction(ActionFactory.PASTE.getId()));
- bars.setGlobalActionHandler(ActionFactory.SELECT_ALL.getId(),
- mTextEditor.getAction(ActionFactory.SELECT_ALL.getId()));
- bars.setGlobalActionHandler(ActionFactory.FIND.getId(),
- mTextEditor.getAction(ActionFactory.FIND.getId()));
- bars.setGlobalActionHandler(IDEActionFactory.BOOKMARK.getId(),
- mTextEditor.getAction(IDEActionFactory.BOOKMARK.getId()));
-
- bars.updateActionBars();
- }
- }
-
- /**
- * Clears the action bindings for the editor site.
- */
- protected void clearActionBindings(boolean includeUndoRedo) {
- IActionBars bars = getEditorSite().getActionBars();
- if (bars != null) {
- // For some reason, undo/redo doesn't seem to work in the form editor.
- // This appears to be the case for pure Eclipse form editors too, e.g. see
- // https://bugs.eclipse.org/bugs/show_bug.cgi?id=68423
- // However, as a workaround we can use the *text* editor's underlying undo
- // to revert operations being done in the UI, and the form automatically updates.
- // Therefore, to work around this, we simply leave the text editor bindings
- // in place if {@code includeUndoRedo} is not set
- if (includeUndoRedo) {
- bars.setGlobalActionHandler(ActionFactory.UNDO.getId(), null);
- bars.setGlobalActionHandler(ActionFactory.REDO.getId(), null);
- }
- bars.setGlobalActionHandler(ActionFactory.DELETE.getId(), null);
- bars.setGlobalActionHandler(ActionFactory.CUT.getId(), null);
- bars.setGlobalActionHandler(ActionFactory.COPY.getId(), null);
- bars.setGlobalActionHandler(ActionFactory.PASTE.getId(), null);
- bars.setGlobalActionHandler(ActionFactory.SELECT_ALL.getId(), null);
- bars.setGlobalActionHandler(ActionFactory.FIND.getId(), null);
- bars.setGlobalActionHandler(IDEActionFactory.BOOKMARK.getId(), null);
-
- bars.updateActionBars();
- }
- }
-
- /**
- * Selects the default active page.
- * @param defaultPageId the id of the page to show. If <code>null</code> the editor attempts to
- * find the default page in the properties of the {@link IResource} object being edited.
- */
- public void selectDefaultPage(String defaultPageId) {
- if (defaultPageId == null) {
- IFile file = getInputFile();
- if (file != null) {
- QualifiedName qname = new QualifiedName(AdtPlugin.PLUGIN_ID,
- getClass().getSimpleName() + PREF_CURRENT_PAGE);
- String pageId;
- try {
- pageId = file.getPersistentProperty(qname);
- if (pageId != null) {
- defaultPageId = pageId;
- }
- } catch (CoreException e) {
- // ignored
- }
- }
- }
-
- if (defaultPageId != null) {
- try {
- setActivePage(Integer.parseInt(defaultPageId));
- } catch (Exception e) {
- // We can get NumberFormatException from parseInt but also
- // AssertionError from setActivePage when the index is out of bounds.
- // Generally speaking we just want to ignore any exception and fall back on the
- // first page rather than crash the editor load. Logging the error is enough.
- AdtPlugin.log(e, "Selecting page '%s' in AndroidXmlEditor failed", defaultPageId);
- }
- } else if (AdtPrefs.getPrefs().isXmlEditorPreferred(getPersistenceCategory())) {
- setActivePage(mTextPageIndex);
- }
- }
-
- /** The layout editor */
- public static final int CATEGORY_LAYOUT = 1 << 0;
- /** The manifest editor */
- public static final int CATEGORY_MANIFEST = 1 << 1;
- /** Any other XML editor */
- public static final int CATEGORY_OTHER = 1 << 2;
-
- /**
- * Returns the persistence category to use for this editor; this should be
- * one of the {@code CATEGORY_} constants such as {@link #CATEGORY_MANIFEST},
- * {@link #CATEGORY_LAYOUT}, {@link #CATEGORY_OTHER}, ...
- * <p>
- * The persistence category is used to group editors together when it comes
- * to certain types of persistence metadata. For example, whether this type
- * of file was most recently edited graphically or with an XML text editor.
- * We'll open new files in the same text or graphical mode as the last time
- * the user edited a file of the same persistence category.
- * <p>
- * Before we added the persistence category, we had a single boolean flag
- * recording whether the XML files were most recently edited graphically or
- * not. However, this meant that users can't for example prefer to edit
- * Manifest files graphically and string files via XML. By splitting the
- * editors up into categories, we can track the mode at a finer granularity,
- * and still allow similar editors such as those used for animations and
- * colors to be treated the same way.
- *
- * @return the persistence category constant
- */
- protected int getPersistenceCategory() {
- return CATEGORY_OTHER;
- }
-
- /**
- * Removes all the pages from the editor.
- */
- protected void removePages() {
- int count = getPageCount();
- for (int i = count - 1 ; i >= 0 ; i--) {
- removePage(i);
- }
- }
-
- /**
- * Overrides the parent's setActivePage to be able to switch to the xml editor.
- *
- * If the special pageId TEXT_EDITOR_ID is given, switches to the mTextPageIndex page.
- * This is needed because the editor doesn't actually derive from IFormPage and thus
- * doesn't have the get-by-page-id method. In this case, the method returns null since
- * IEditorPart does not implement IFormPage.
- */
- @Override
- public IFormPage setActivePage(String pageId) {
- if (pageId.equals(TEXT_EDITOR_ID)) {
- super.setActivePage(mTextPageIndex);
- return null;
- } else {
- return super.setActivePage(pageId);
- }
- }
-
- /**
- * Notifies this multi-page editor that the page with the given id has been
- * activated. This method is called when the user selects a different tab.
- *
- * @see MultiPageEditorPart#pageChange(int)
- */
- @Override
- protected void pageChange(int newPageIndex) {
- super.pageChange(newPageIndex);
-
- // Do not record page changes during creation of pages
- if (mIsCreatingPage) {
- return;
- }
-
- IFile file = getInputFile();
- if (file != null) {
- QualifiedName qname = new QualifiedName(AdtPlugin.PLUGIN_ID,
- getClass().getSimpleName() + PREF_CURRENT_PAGE);
- try {
- file.setPersistentProperty(qname, Integer.toString(newPageIndex));
- } catch (CoreException e) {
- // ignore
- }
- }
-
- boolean isTextPage = newPageIndex == mTextPageIndex;
- AdtPrefs.getPrefs().setXmlEditorPreferred(getPersistenceCategory(), isTextPage);
- }
-
- /**
- * Returns true if the active page is the editor page
- *
- * @return true if the active page is the editor page
- */
- public boolean isEditorPageActive() {
- return getActivePage() == mTextPageIndex;
- }
-
- /**
- * Returns the {@link IFile} matching the editor's input or null.
- */
- @Nullable
- public IFile getInputFile() {
- IEditorInput input = getEditorInput();
- if (input instanceof IFileEditorInput) {
- return ((IFileEditorInput) input).getFile();
- }
- return null;
- }
-
- /**
- * Removes attached listeners.
- *
- * @see WorkbenchPart
- */
- @Override
- public void dispose() {
- IStructuredModel xml_model = getModelForRead();
- if (xml_model != null) {
- try {
- if (mXmlModelStateListener != null) {
- xml_model.removeModelStateListener(mXmlModelStateListener);
- }
-
- } finally {
- xml_model.releaseFromRead();
- }
- }
-
- if (mTargetListener != null) {
- AdtPlugin.getDefault().removeTargetListener(mTargetListener);
- mTargetListener = null;
- }
-
- super.dispose();
- }
-
- /**
- * Commit all dirty pages then saves the contents of the text editor.
- * <p/>
- * This works by committing all data to the XML model and then
- * asking the Structured XML Editor to save the XML.
- *
- * @see IEditorPart
- */
- @Override
- public void doSave(IProgressMonitor monitor) {
- commitPages(true /* onSave */);
-
- if (AdtPrefs.getPrefs().isFormatOnSave()) {
- IAction action = mTextEditor.getAction(ACTION_NAME_FORMAT_DOCUMENT);
- if (action != null) {
- try {
- mIgnoreXmlUpdate = true;
- action.run();
- } finally {
- mIgnoreXmlUpdate = false;
- }
- }
- }
-
- // The actual "save" operation is done by the Structured XML Editor
- getEditor(mTextPageIndex).doSave(monitor);
-
- // Check for errors on save, if enabled
- if (AdtPrefs.getPrefs().isLintOnSave()) {
- runLint();
- }
- }
-
- /**
- * Tells the editor to start a Lint check.
- * It's up to the caller to check whether this should be done depending on preferences.
- * <p/>
- * The default implementation is to call {@link #startLintJob()}.
- *
- * @return The Job started by {@link EclipseLintRunner} or null if no job was started.
- */
- protected Job runLint() {
- return startLintJob();
- }
-
- /**
- * Utility method that creates a Job to run Lint on the current document.
- * Does not wait for the job to finish - just returns immediately.
- *
- * @return a new job, or null
- * @see EclipseLintRunner#startLint(java.util.List, IResource, IDocument,
- * boolean, boolean)
- */
- @Nullable
- public Job startLintJob() {
- IFile file = getInputFile();
- if (file != null) {
- return EclipseLintRunner.startLint(Collections.singletonList(file), file,
- getStructuredDocument(), false /*fatalOnly*/, false /*show*/);
- }
-
- return null;
- }
-
- /* (non-Javadoc)
- * Saves the contents of this editor to another object.
- * <p>
- * Subclasses must override this method to implement the open-save-close lifecycle
- * for an editor. For greater details, see <code>IEditorPart</code>
- * </p>
- *
- * @see IEditorPart
- */
- @Override
- public void doSaveAs() {
- commitPages(true /* onSave */);
-
- IEditorPart editor = getEditor(mTextPageIndex);
- editor.doSaveAs();
- setPageText(mTextPageIndex, editor.getTitle());
- setInput(editor.getEditorInput());
- }
-
- /**
- * Commits all dirty pages in the editor. This method should
- * be called as a first step of a 'save' operation.
- * <p/>
- * This is the same implementation as in {@link FormEditor}
- * except it fixes two bugs: a cast to IFormPage is done
- * from page.get(i) <em>before</em> being tested with instanceof.
- * Another bug is that the last page might be a null pointer.
- * <p/>
- * The incorrect casting makes the original implementation crash due
- * to our {@link StructuredTextEditor} not being an {@link IFormPage}
- * so we have to override and duplicate to fix it.
- *
- * @param onSave <code>true</code> if commit is performed as part
- * of the 'save' operation, <code>false</code> otherwise.
- * @since 3.3
- */
- @Override
- public void commitPages(boolean onSave) {
- if (pages != null) {
- for (int i = 0; i < pages.size(); i++) {
- Object page = pages.get(i);
- if (page != null && page instanceof IFormPage) {
- IFormPage form_page = (IFormPage) page;
- IManagedForm managed_form = form_page.getManagedForm();
- if (managed_form != null && managed_form.isDirty()) {
- managed_form.commit(onSave);
- }
- }
- }
- }
- }
-
- /* (non-Javadoc)
- * Returns whether the "save as" operation is supported by this editor.
- * <p>
- * Subclasses must override this method to implement the open-save-close lifecycle
- * for an editor. For greater details, see <code>IEditorPart</code>
- * </p>
- *
- * @see IEditorPart
- */
- @Override
- public boolean isSaveAsAllowed() {
- return false;
- }
-
- /**
- * Returns the page index of the text editor (always the last page)
-
- * @return the page index of the text editor (always the last page)
- */
- public int getTextPageIndex() {
- return mTextPageIndex;
- }
-
- // ---- Local methods ----
-
-
- /**
- * Helper method that creates a new hyper-link Listener.
- * Used by derived classes which need active links in {@link FormText}.
- * <p/>
- * This link listener handles two kinds of URLs:
- * <ul>
- * <li> Links starting with "http" are simply sent to a local browser.
- * <li> Links starting with "file:/" are simply sent to a local browser.
- * <li> Links starting with "page:" are expected to be an editor page id to switch to.
- * <li> Other links are ignored.
- * </ul>
- *
- * @return A new hyper-link listener for FormText to use.
- */
- public final IHyperlinkListener createHyperlinkListener() {
- return new HyperlinkAdapter() {
- /**
- * Switch to the page corresponding to the link that has just been clicked.
- * For this purpose, the HREF of the &lt;a&gt; tags above is the page ID to switch to.
- */
- @Override
- public void linkActivated(HyperlinkEvent e) {
- super.linkActivated(e);
- String link = e.data.toString();
- if (link.startsWith("http") || //$NON-NLS-1$
- link.startsWith("file:/")) { //$NON-NLS-1$
- openLinkInBrowser(link);
- } else if (link.startsWith("page:")) { //$NON-NLS-1$
- // Switch to an internal page
- setActivePage(link.substring(5 /* strlen("page:") */));
- }
- }
- };
- }
-
- /**
- * Open the http link into a browser
- *
- * @param link The URL to open in a browser
- */
- private void openLinkInBrowser(String link) {
- try {
- IWorkbenchBrowserSupport wbs = WorkbenchBrowserSupport.getInstance();
- wbs.createBrowser(BROWSER_ID).openURL(new URL(link));
- } catch (PartInitException e1) {
- // pass
- } catch (MalformedURLException e1) {
- // pass
- }
- }
-
- /**
- * Creates the XML source editor.
- * <p/>
- * Memorizes the index page of the source editor (it's always the last page, but the number
- * of pages before can change.)
- * <br/>
- * Retrieves the underlying XML model from the StructuredEditor and attaches a listener to it.
- * Finally triggers modelChanged() on the model listener -- derived classes can use this
- * to initialize the model the first time.
- * <p/>
- * Called only once <em>after</em> createFormPages.
- */
- private void createTextEditor() {
- try {
- mTextEditor = new StructuredTextEditor() {
- @Override
- protected void createActions() {
- super.createActions();
-
- Action action = new RenameResourceXmlTextAction(mTextEditor);
- action.setActionDefinitionId(IJavaEditorActionDefinitionIds.RENAME_ELEMENT);
- setAction(IJavaEditorActionDefinitionIds.RENAME_ELEMENT, action);
- }
- };
- int index = addPage(mTextEditor, getEditorInput());
- mTextPageIndex = index;
- setPageText(index, mTextEditor.getTitle());
- setPageImage(index,
- IconFactory.getInstance().getIcon(ICON_XML_PAGE));
-
- if (!(mTextEditor.getTextViewer().getDocument() instanceof IStructuredDocument)) {
- Status status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
- "Error opening the Android XML editor. Is the document an XML file?");
- throw new RuntimeException("Android XML Editor Error", new CoreException(status));
- }
-
- IStructuredModel xml_model = getModelForRead();
- if (xml_model != null) {
- try {
- mXmlModelStateListener = new XmlModelStateListener();
- xml_model.addModelStateListener(mXmlModelStateListener);
- mXmlModelStateListener.modelChanged(xml_model);
- } catch (Exception e) {
- AdtPlugin.log(e, "Error while loading editor"); //$NON-NLS-1$
- } finally {
- xml_model.releaseFromRead();
- }
- }
- } catch (PartInitException e) {
- ErrorDialog.openError(getSite().getShell(),
- "Android XML Editor Error", null, e.getStatus());
- }
- }
-
- /**
- * Returns the ISourceViewer associated with the Structured Text editor.
- */
- public final ISourceViewer getStructuredSourceViewer() {
- if (mTextEditor != null) {
- // We can't access mDelegate.getSourceViewer() because it is protected,
- // however getTextViewer simply returns the SourceViewer casted, so we
- // can use it instead.
- return mTextEditor.getTextViewer();
- }
- return null;
- }
-
- /**
- * Return the {@link StructuredTextEditor} associated with this XML editor
- *
- * @return the associated {@link StructuredTextEditor}
- */
- public StructuredTextEditor getStructuredTextEditor() {
- return mTextEditor;
- }
-
- /**
- * Returns the {@link IStructuredDocument} used by the StructuredTextEditor (aka Source
- * Editor) or null if not available.
- */
- public IStructuredDocument getStructuredDocument() {
- if (mTextEditor != null && mTextEditor.getTextViewer() != null) {
- return (IStructuredDocument) mTextEditor.getTextViewer().getDocument();
- }
- return null;
- }
-
- /**
- * Returns a version of the model that has been shared for read.
- * <p/>
- * Callers <em>must</em> call model.releaseFromRead() when done, typically
- * in a try..finally clause.
- *
- * Portability note: this uses getModelManager which is part of wst.sse.core; however
- * the interface returned is part of wst.sse.core.internal.provisional so we can
- * expect it to change in a distant future if they start cleaning their codebase,
- * however unlikely that is.
- *
- * @return The model for the XML document or null if cannot be obtained from the editor
- */
- public IStructuredModel getModelForRead() {
- IStructuredDocument document = getStructuredDocument();
- if (document != null) {
- IModelManager mm = StructuredModelManager.getModelManager();
- if (mm != null) {
- // TODO simplify this by not using the internal IStructuredDocument.
- // Instead we can now use mm.getModelForRead(getFile()).
- // However we must first check that SSE for Eclipse 3.3 or 3.4 has this
- // method. IIRC 3.3 didn't have it.
-
- return mm.getModelForRead(document);
- }
- }
- return null;
- }
-
- /**
- * Returns a version of the model that has been shared for edit.
- * <p/>
- * Callers <em>must</em> call model.releaseFromEdit() when done, typically
- * in a try..finally clause.
- * <p/>
- * Because of this, it is mandatory to use the wrapper
- * {@link #wrapEditXmlModel(Runnable)} which executes a runnable into a
- * properly configured model and then performs whatever cleanup is necessary.
- *
- * @return The model for the XML document or null if cannot be obtained from the editor
- */
- private IStructuredModel getModelForEdit() {
- IStructuredDocument document = getStructuredDocument();
- if (document != null) {
- IModelManager mm = StructuredModelManager.getModelManager();
- if (mm != null) {
- // TODO simplify this by not using the internal IStructuredDocument.
- // Instead we can now use mm.getModelForRead(getFile()).
- // However we must first check that SSE for Eclipse 3.3 or 3.4 has this
- // method. IIRC 3.3 didn't have it.
-
- return mm.getModelForEdit(document);
- }
- }
- return null;
- }
-
- /**
- * Helper class to perform edits on the XML model whilst making sure the
- * model has been prepared to be changed.
- * <p/>
- * It first gets a model for edition using {@link #getModelForEdit()},
- * then calls {@link IStructuredModel#aboutToChangeModel()},
- * then performs the requested action
- * and finally calls {@link IStructuredModel#changedModel()}
- * and {@link IStructuredModel#releaseFromEdit()}.
- * <p/>
- * The method is synchronous. As soon as the {@link IStructuredModel#changedModel()} method
- * is called, XML model listeners will be triggered.
- * <p/>
- * Calls can be nested: only the first outer call will actually start and close the edit
- * session.
- * <p/>
- * This method is <em>not synchronized</em> and is not thread safe.
- * Callers must be using it from the the main UI thread.
- *
- * @param editAction Something that will change the XML.
- */
- public final void wrapEditXmlModel(Runnable editAction) {
- wrapEditXmlModel(editAction, null);
- }
-
- /**
- * Perform any editor-specific hooks after applying an edit. When edits are
- * nested, the hooks will only run after the final top level edit has been
- * performed.
- * <p>
- * Note that the edit hooks are performed outside of the edit lock so
- * the hooks should not perform edits on the model without acquiring
- * a lock first.
- */
- public void runEditHooks() {
- if (!mIgnoreXmlUpdate) {
- // Check for errors, if enabled
- if (AdtPrefs.getPrefs().isLintOnSave()) {
- runLint();
- }
- }
- }
-
- /**
- * Executor which performs the given action under an edit lock (and optionally as a
- * single undo event).
- *
- * @param editAction the action to be executed
- * @param undoLabel if non null, the edit action will be run as a single undo event
- * and the label used as the name of the undoable action
- */
- private final void wrapEditXmlModel(final Runnable editAction, final String undoLabel) {
- Display display = mTextEditor.getSite().getShell().getDisplay();
- if (display.getThread() != Thread.currentThread()) {
- display.syncExec(new Runnable() {
- @Override
- public void run() {
- if (!mTextEditor.getTextViewer().getControl().isDisposed()) {
- wrapEditXmlModel(editAction, undoLabel);
- }
- }
- });
- return;
- }
-
- IStructuredModel model = null;
- int undoReverseCount = 0;
- try {
-
- if (mIsEditXmlModelPending == 0) {
- try {
- model = getModelForEdit();
- if (undoLabel != null) {
- // Run this action as an undoable unit.
- // We have to do it more than once, because in some scenarios
- // Eclipse WTP decides to cancel the current undo command on its
- // own -- see http://code.google.com/p/android/issues/detail?id=15901
- // for one such call chain. By nesting these calls several times
- // we've incrementing the command count such that a couple of
- // cancellations are ignored. Interfering with this mechanism may
- // sound dangerous, but it appears that this undo-termination is
- // done for UI reasons to anticipate what the user wants, and we know
- // that in *our* scenarios we want the entire unit run as a single
- // unit. Here's what the documentation for
- // IStructuredTextUndoManager#forceEndOfPendingCommand says
- // "Normally, the undo manager can figure out the best
- // times when to end a pending command and begin a new
- // one ... to the structure of a structured
- // document. There are times, however, when clients may
- // wish to override those algorithms and end one earlier
- // than normal. The one known case is for multi-page
- // editors. If a user is on one page, and type '123' as
- // attribute value, then click around to other parts of
- // page, or different pages, then return to '123|' and
- // type 456, then "undo" they typically expect the undo
- // to just undo what they just typed, the 456, not the
- // whole attribute value."
- for (int i = 0; i < 4; i++) {
- model.beginRecording(this, undoLabel);
- undoReverseCount++;
- }
- }
- model.aboutToChangeModel();
- } catch (Throwable t) {
- // This is never supposed to happen unless we suddenly don't have a model.
- // If it does, we don't want to even try to modify anyway.
- AdtPlugin.log(t, "XML Editor failed to get model to edit"); //$NON-NLS-1$
- return;
- }
- }
- mIsEditXmlModelPending++;
- editAction.run();
- } finally {
- mIsEditXmlModelPending--;
- if (model != null) {
- try {
- boolean oldIgnore = mIgnoreXmlUpdate;
- try {
- mIgnoreXmlUpdate = true;
-
- if (AdtPrefs.getPrefs().getFormatGuiXml() && mFormatNode != null) {
- if (mFormatNode == getUiRootNode()) {
- reformatDocument();
- } else {
- Node node = mFormatNode.getXmlNode();
- if (node instanceof IndexedRegion) {
- IndexedRegion region = (IndexedRegion) node;
- int begin = region.getStartOffset();
- int end = region.getEndOffset();
-
- if (!mFormatChildren) {
- // This will format just the attribute list
- end = begin + 1;
- }
-
- if (mFormatChildren
- && node == node.getOwnerDocument().getDocumentElement()) {
- reformatDocument();
- } else {
- reformatRegion(begin, end);
- }
- }
- }
- mFormatNode = null;
- mFormatChildren = false;
- }
-
- // Notify the model we're done modifying it. This must *always* be executed.
- model.changedModel();
-
- // Clean up the undo unit. This is done more than once as explained
- // above for beginRecording.
- for (int i = 0; i < undoReverseCount; i++) {
- model.endRecording(this);
- }
- } finally {
- mIgnoreXmlUpdate = oldIgnore;
- }
- } catch (Exception e) {
- AdtPlugin.log(e, "Failed to clean up undo unit");
- }
- model.releaseFromEdit();
-
- if (mIsEditXmlModelPending < 0) {
- AdtPlugin.log(IStatus.ERROR,
- "wrapEditXmlModel finished with invalid nested counter==%1$d", //$NON-NLS-1$
- mIsEditXmlModelPending);
- mIsEditXmlModelPending = 0;
- }
-
- runEditHooks();
-
- // Notify listeners
- IStructuredModel readModel = getModelForRead();
- if (readModel != null) {
- try {
- mXmlModelStateListener.modelChanged(readModel);
- } catch (Exception e) {
- AdtPlugin.log(e, "Error while notifying changes"); //$NON-NLS-1$
- } finally {
- readModel.releaseFromRead();
- }
- }
- }
- }
- }
-
- /**
- * Does this editor participate in the "format GUI editor changes" option?
- *
- * @return true if this editor supports automatically formatting XML
- * affected by GUI changes
- */
- public boolean supportsFormatOnGuiEdit() {
- return false;
- }
-
- /**
- * Mark the given node as needing to be formatted when the current edits are
- * done, provided the user has turned that option on (see
- * {@link AdtPrefs#getFormatGuiXml()}).
- *
- * @param node the node to be scheduled for formatting
- * @param attributesOnly if true, only update the attributes list of the
- * node, otherwise update the node recursively (e.g. all children
- * too)
- */
- public void scheduleNodeReformat(UiElementNode node, boolean attributesOnly) {
- if (!supportsFormatOnGuiEdit()) {
- return;
- }
-
- if (node == mFormatNode) {
- if (!attributesOnly) {
- mFormatChildren = true;
- }
- } else if (mFormatNode == null) {
- mFormatNode = node;
- mFormatChildren = !attributesOnly;
- } else {
- if (mFormatNode.isAncestorOf(node)) {
- mFormatChildren = true;
- } else if (node.isAncestorOf(mFormatNode)) {
- mFormatNode = node;
- mFormatChildren = true;
- } else {
- // Two independent nodes; format their closest common ancestor.
- // Later we could consider having a small number of independent nodes
- // and formatting those, and only switching to formatting the common ancestor
- // when the number of individual nodes gets large.
- mFormatChildren = true;
- mFormatNode = UiElementNode.getCommonAncestor(mFormatNode, node);
- }
- }
- }
-
- /**
- * Creates an "undo recording" session by calling the undoableAction runnable
- * under an undo session.
- * <p/>
- * This also automatically starts an edit XML session, as if
- * {@link #wrapEditXmlModel(Runnable)} had been called.
- * <p>
- * You can nest several calls to {@link #wrapUndoEditXmlModel(String, Runnable)}, only one
- * recording session will be created.
- *
- * @param label The label for the undo operation. Can be null. Ideally we should really try
- * to put something meaningful if possible.
- * @param undoableAction the action to be run as a single undoable unit
- */
- public void wrapUndoEditXmlModel(String label, Runnable undoableAction) {
- assert label != null : "All undoable actions should have a label";
- wrapEditXmlModel(undoableAction, label == null ? "" : label); //$NON-NLS-1$
- }
-
- /**
- * Returns true when the runnable of {@link #wrapEditXmlModel(Runnable)} is currently
- * being executed. This means it is safe to actually edit the XML model.
- *
- * @return true if the XML model is already locked for edits
- */
- public boolean isEditXmlModelPending() {
- return mIsEditXmlModelPending > 0;
- }
-
- /**
- * Returns the XML {@link Document} or null if we can't get it
- */
- public final Document getXmlDocument(IStructuredModel model) {
- if (model == null) {
- AdtPlugin.log(IStatus.WARNING, "Android Editor: No XML model for root node."); //$NON-NLS-1$
- return null;
- }
-
- if (model instanceof IDOMModel) {
- IDOMModel dom_model = (IDOMModel) model;
- return dom_model.getDocument();
- }
- return null;
- }
-
- /**
- * Returns the {@link IProject} for the edited file.
- */
- @Nullable
- public IProject getProject() {
- IFile file = getInputFile();
- if (file != null) {
- return file.getProject();
- }
-
- return null;
- }
-
- /**
- * Returns the {@link AndroidTargetData} for the edited file.
- */
- @Nullable
- public AndroidTargetData getTargetData() {
- IProject project = getProject();
- if (project != null) {
- Sdk currentSdk = Sdk.getCurrent();
- if (currentSdk != null) {
- IAndroidTarget target = currentSdk.getTarget(project);
-
- if (target != null) {
- return currentSdk.getTargetData(target);
- }
- }
- }
-
- IEditorInput input = getEditorInput();
- if (input instanceof IURIEditorInput) {
- IURIEditorInput urlInput = (IURIEditorInput) input;
- Sdk currentSdk = Sdk.getCurrent();
- if (currentSdk != null) {
- try {
- String path = AdtUtils.getFile(urlInput.getURI().toURL()).getPath();
- IAndroidTarget[] targets = currentSdk.getTargets();
- for (IAndroidTarget target : targets) {
- if (path.startsWith(target.getLocation())) {
- return currentSdk.getTargetData(target);
- }
- }
- } catch (MalformedURLException e) {
- // File might be in some other weird random location we can't
- // handle: Just ignore these
- }
- }
- }
-
- return null;
- }
-
- /**
- * Shows the editor range corresponding to the given XML node. This will
- * front the editor and select the text range.
- *
- * @param xmlNode The DOM node to be shown. The DOM node should be an XML
- * node from the existing XML model used by the structured XML
- * editor; it will not do attribute matching to find a
- * "corresponding" element in the document from some foreign DOM
- * tree.
- * @return True if the node was shown.
- */
- public boolean show(Node xmlNode) {
- if (xmlNode instanceof IndexedRegion) {
- IndexedRegion region = (IndexedRegion)xmlNode;
-
- IEditorPart textPage = getEditor(mTextPageIndex);
- if (textPage instanceof StructuredTextEditor) {
- StructuredTextEditor editor = (StructuredTextEditor) textPage;
-
- setActivePage(AndroidXmlEditor.TEXT_EDITOR_ID);
-
- // Note - we cannot use region.getLength() because that seems to
- // always return 0.
- int regionLength = region.getEndOffset() - region.getStartOffset();
- editor.selectAndReveal(region.getStartOffset(), regionLength);
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Selects and reveals the given range in the text editor
- *
- * @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, boolean frontTab) {
- IEditorPart textPage = getEditor(mTextPageIndex);
- if (textPage instanceof StructuredTextEditor) {
- StructuredTextEditor editor = (StructuredTextEditor) textPage;
- 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.
- * @return The XML representation of the {@link Node}, or null if there was an error.
- */
- public String getXmlText(Node xmlNode) {
- String data = null;
- IStructuredModel model = getModelForRead();
- try {
- IStructuredDocument document = getStructuredDocument();
- if (xmlNode instanceof NodeContainer) {
- // The easy way to get the source of an SSE XML node.
- data = ((NodeContainer) xmlNode).getSource();
- } else if (xmlNode instanceof IndexedRegion && document != null) {
- // Try harder.
- IndexedRegion region = (IndexedRegion) xmlNode;
- int start = region.getStartOffset();
- int end = region.getEndOffset();
-
- if (end > start) {
- data = document.get(start, end - start);
- }
- }
- } catch (BadLocationException e) {
- // the region offset was invalid. ignore.
- } finally {
- model.releaseFromRead();
- }
- return data;
- }
-
- /**
- * Formats the text around the given caret range, using the current Eclipse
- * XML formatter settings.
- *
- * @param begin The starting offset of the range to be reformatted.
- * @param end The ending offset of the range to be reformatted.
- */
- public void reformatRegion(int begin, int end) {
- ISourceViewer textViewer = getStructuredSourceViewer();
-
- // Clamp text range to valid offsets.
- IDocument document = textViewer.getDocument();
- int documentLength = document.getLength();
- end = Math.min(end, documentLength);
- begin = Math.min(begin, end);
-
- if (!AdtPrefs.getPrefs().getUseCustomXmlFormatter()) {
- // Workarounds which only apply to the builtin Eclipse formatter:
- //
- // It turns out the XML formatter does *NOT* format things correctly if you
- // select just a region of text. You *MUST* also include the leading whitespace
- // on the line, or it will dedent all the content to column 0. Therefore,
- // we must figure out the offset of the start of the line that contains the
- // beginning of the tag.
- try {
- IRegion lineInformation = document.getLineInformationOfOffset(begin);
- if (lineInformation != null) {
- int lineBegin = lineInformation.getOffset();
- if (lineBegin != begin) {
- begin = lineBegin;
- } else if (begin > 0) {
- // Trick #2: It turns out that, if an XML element starts in column 0,
- // then the XML formatter will NOT indent it (even if its parent is
- // indented). If you on the other hand include the end of the previous
- // line (the newline), THEN the formatter also correctly inserts the
- // element. Therefore, we adjust the beginning range to include the
- // previous line (if we are not already in column 0 of the first line)
- // in the case where the element starts the line.
- begin--;
- }
- }
- } catch (BadLocationException e) {
- // This cannot happen because we already clamped the offsets
- AdtPlugin.log(e, e.toString());
- }
- }
-
- if (textViewer instanceof StructuredTextViewer) {
- StructuredTextViewer structuredTextViewer = (StructuredTextViewer) textViewer;
- int operation = ISourceViewer.FORMAT;
- boolean canFormat = structuredTextViewer.canDoOperation(operation);
- if (canFormat) {
- StyledText textWidget = textViewer.getTextWidget();
- textWidget.setSelection(begin, end);
-
- boolean oldIgnore = mIgnoreXmlUpdate;
- try {
- // Formatting does not affect the XML model so ignore notifications
- // about model edits from this
- mIgnoreXmlUpdate = true;
- structuredTextViewer.doOperation(operation);
- } finally {
- mIgnoreXmlUpdate = oldIgnore;
- }
-
- textWidget.setSelection(0, 0);
- }
- }
- }
-
- /**
- * Invokes content assist in this editor at the given offset
- *
- * @param offset the offset to invoke content assist at, or -1 to leave
- * caret alone
- */
- public void invokeContentAssist(int offset) {
- ISourceViewer textViewer = getStructuredSourceViewer();
- if (textViewer instanceof StructuredTextViewer) {
- StructuredTextViewer structuredTextViewer = (StructuredTextViewer) textViewer;
- int operation = ISourceViewer.CONTENTASSIST_PROPOSALS;
- boolean allowed = structuredTextViewer.canDoOperation(operation);
- if (allowed) {
- if (offset != -1) {
- StyledText textWidget = textViewer.getTextWidget();
- // Clamp text range to valid offsets.
- IDocument document = textViewer.getDocument();
- int documentLength = document.getLength();
- offset = Math.max(0, Math.min(offset, documentLength));
- textWidget.setSelection(offset, offset);
- }
- structuredTextViewer.doOperation(operation);
- }
- }
- }
-
- /**
- * Formats the XML region corresponding to the given node.
- *
- * @param node The node to be formatted.
- */
- public void reformatNode(Node node) {
- if (mIsCreatingPage) {
- return;
- }
-
- if (node instanceof IndexedRegion) {
- IndexedRegion region = (IndexedRegion) node;
- int begin = region.getStartOffset();
- int end = region.getEndOffset();
- reformatRegion(begin, end);
- }
- }
-
- /**
- * Formats the XML document according to the user's XML formatting settings.
- */
- public void reformatDocument() {
- ISourceViewer textViewer = getStructuredSourceViewer();
- if (textViewer instanceof StructuredTextViewer) {
- StructuredTextViewer structuredTextViewer = (StructuredTextViewer) textViewer;
- int operation = StructuredTextViewer.FORMAT_DOCUMENT;
- boolean canFormat = structuredTextViewer.canDoOperation(operation);
- if (canFormat) {
- boolean oldIgnore = mIgnoreXmlUpdate;
- try {
- // Formatting does not affect the XML model so ignore notifications
- // about model edits from this
- mIgnoreXmlUpdate = true;
- structuredTextViewer.doOperation(operation);
- } finally {
- mIgnoreXmlUpdate = oldIgnore;
- }
- }
- }
- }
-
- /**
- * Returns the indentation String of the given node.
- *
- * @param xmlNode The node whose indentation we want.
- * @return The indent-string of the given node, or "" if the indentation for some reason could
- * not be computed.
- */
- public String getIndent(Node xmlNode) {
- return getIndent(getStructuredDocument(), xmlNode);
- }
-
- /**
- * Returns the indentation String of the given node.
- *
- * @param document The Eclipse document containing the XML
- * @param xmlNode The node whose indentation we want.
- * @return The indent-string of the given node, or "" if the indentation for some reason could
- * not be computed.
- */
- public static String getIndent(IDocument document, Node xmlNode) {
- if (xmlNode instanceof IndexedRegion) {
- IndexedRegion region = (IndexedRegion)xmlNode;
- int startOffset = region.getStartOffset();
- return getIndentAtOffset(document, startOffset);
- }
-
- return ""; //$NON-NLS-1$
- }
-
- /**
- * Returns the indentation String at the line containing the given offset
- *
- * @param document the document containing the offset
- * @param offset The offset of a character on a line whose indentation we seek
- * @return The indent-string of the given node, or "" if the indentation for some
- * reason could not be computed.
- */
- public static String getIndentAtOffset(IDocument document, int offset) {
- try {
- IRegion lineInformation = document.getLineInformationOfOffset(offset);
- if (lineInformation != null) {
- int lineBegin = lineInformation.getOffset();
- if (lineBegin != offset) {
- String prefix = document.get(lineBegin, offset - lineBegin);
-
- // It's possible that the tag whose indentation we seek is not
- // at the beginning of the line. In that case we'll just return
- // the indentation of the line itself.
- for (int i = 0; i < prefix.length(); i++) {
- if (!Character.isWhitespace(prefix.charAt(i))) {
- return prefix.substring(0, i);
- }
- }
-
- return prefix;
- }
- }
- } catch (BadLocationException e) {
- AdtPlugin.log(e, "Could not obtain indentation"); //$NON-NLS-1$
- }
-
- return ""; //$NON-NLS-1$
- }
-
- /**
- * Returns the active {@link AndroidXmlEditor}, provided it matches the given source
- * viewer
- *
- * @param viewer the source viewer to ensure the active editor is associated with
- * @return the active editor provided it matches the given source viewer or null.
- */
- public static AndroidXmlEditor fromTextViewer(ITextViewer viewer) {
- IWorkbenchWindow wwin = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
- if (wwin != null) {
- // Try the active editor first.
- IWorkbenchPage page = wwin.getActivePage();
- if (page != null) {
- IEditorPart editor = page.getActiveEditor();
- if (editor instanceof AndroidXmlEditor) {
- ISourceViewer ssviewer =
- ((AndroidXmlEditor) editor).getStructuredSourceViewer();
- if (ssviewer == viewer) {
- return (AndroidXmlEditor) editor;
- }
- }
- }
-
- // If that didn't work, try all the editors
- for (IWorkbenchPage page2 : wwin.getPages()) {
- if (page2 != null) {
- for (IEditorReference editorRef : page2.getEditorReferences()) {
- IEditorPart editor = editorRef.getEditor(false /*restore*/);
- if (editor instanceof AndroidXmlEditor) {
- ISourceViewer ssviewer =
- ((AndroidXmlEditor) editor).getStructuredSourceViewer();
- if (ssviewer == viewer) {
- return (AndroidXmlEditor) editor;
- }
- }
- }
- }
- }
- }
-
- return null;
- }
-
- /** Called when this editor is activated */
- public void activated() {
- if (getActivePage() == mTextPageIndex) {
- updateActionBindings();
- }
- }
-
- /** Called when this editor is deactivated */
- public void deactivated() {
- }
-
- /**
- * Listen to changes in the underlying XML model in the structured editor.
- */
- private class XmlModelStateListener implements IModelStateListener {
-
- /**
- * A model is about to be changed. This typically is initiated by one
- * client of the model, to signal a large change and/or a change to the
- * model's ID or base Location. A typical use might be if a client might
- * want to suspend processing until all changes have been made.
- * <p/>
- * This AndroidXmlEditor implementation of IModelChangedListener is empty.
- */
- @Override
- public void modelAboutToBeChanged(IStructuredModel model) {
- // pass
- }
-
- /**
- * Signals that the changes foretold by modelAboutToBeChanged have been
- * made. A typical use might be to refresh, or to resume processing that
- * was suspended as a result of modelAboutToBeChanged.
- * <p/>
- * This AndroidXmlEditor implementation calls the xmlModelChanged callback.
- */
- @Override
- public void modelChanged(IStructuredModel model) {
- if (mIgnoreXmlUpdate) {
- return;
- }
- xmlModelChanged(getXmlDocument(model));
- }
-
- /**
- * Notifies that a model's dirty state has changed, and passes that state
- * in isDirty. A model becomes dirty when any change is made, and becomes
- * not-dirty when the model is saved.
- * <p/>
- * This AndroidXmlEditor implementation of IModelChangedListener is empty.
- */
- @Override
- public void modelDirtyStateChanged(IStructuredModel model, boolean isDirty) {
- // pass
- }
-
- /**
- * A modelDeleted means the underlying resource has been deleted. The
- * model itself is not removed from model management until all have
- * released it. Note: baseLocation is not (necessarily) changed in this
- * event, but may not be accurate.
- * <p/>
- * This AndroidXmlEditor implementation of IModelChangedListener is empty.
- */
- @Override
- public void modelResourceDeleted(IStructuredModel model) {
- // pass
- }
-
- /**
- * A model has been renamed or copied (as in saveAs..). In the renamed
- * case, the two parameters are the same instance, and only contain the
- * new info for id and base location.
- * <p/>
- * This AndroidXmlEditor implementation of IModelChangedListener is empty.
- */
- @Override
- public void modelResourceMoved(IStructuredModel oldModel, IStructuredModel newModel) {
- // pass
- }
-
- /**
- * This AndroidXmlEditor implementation of IModelChangedListener is empty.
- */
- @Override
- public void modelAboutToBeReinitialized(IStructuredModel structuredModel) {
- // pass
- }
-
- /**
- * This AndroidXmlEditor implementation of IModelChangedListener is empty.
- */
- @Override
- public void modelReinitialized(IStructuredModel structuredModel) {
- // pass
- }
- }
-}