| /* |
| * Copyright (C) 2011 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.layout.refactoring; |
| |
| import com.android.ide.common.resources.ResourceUrl; |
| import com.android.ide.eclipse.adt.AdtPlugin; |
| import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; |
| import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate; |
| import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities; |
| import com.android.ide.eclipse.adt.internal.refactorings.core.RenameResourceProcessor; |
| import com.android.ide.eclipse.adt.internal.refactorings.core.RenameResourceWizard; |
| import com.android.ide.eclipse.adt.internal.refactorings.core.RenameResourceXmlTextAction; |
| import com.android.ide.eclipse.adt.internal.refactorings.extractstring.ExtractStringRefactoring; |
| import com.android.ide.eclipse.adt.internal.refactorings.extractstring.ExtractStringWizard; |
| import com.android.resources.ResourceType; |
| |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.jface.text.IDocument; |
| import org.eclipse.jface.text.ITextSelection; |
| import org.eclipse.jface.text.TextSelection; |
| import org.eclipse.jface.text.contentassist.ICompletionProposal; |
| import org.eclipse.jface.text.contentassist.IContextInformation; |
| import org.eclipse.jface.text.quickassist.IQuickAssistInvocationContext; |
| import org.eclipse.jface.text.quickassist.IQuickAssistProcessor; |
| import org.eclipse.jface.text.source.Annotation; |
| import org.eclipse.jface.text.source.ISourceViewer; |
| import org.eclipse.jface.viewers.ISelection; |
| import org.eclipse.jface.viewers.ISelectionProvider; |
| import org.eclipse.ltk.core.refactoring.Refactoring; |
| import org.eclipse.ltk.core.refactoring.participants.RenameRefactoring; |
| import org.eclipse.ltk.ui.refactoring.RefactoringWizard; |
| import org.eclipse.ltk.ui.refactoring.RefactoringWizardOpenOperation; |
| import org.eclipse.swt.graphics.Image; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.ui.IWorkbenchWindow; |
| import org.eclipse.ui.PlatformUI; |
| 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.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.regions.DOMRegionContext; |
| import org.w3c.dom.Node; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| /** |
| * QuickAssistProcessor which helps invoke refactoring operations on text elements. |
| */ |
| @SuppressWarnings("restriction") // XML model |
| public class RefactoringAssistant implements IQuickAssistProcessor { |
| |
| /** |
| * Creates a new {@link RefactoringAssistant} |
| */ |
| public RefactoringAssistant() { |
| } |
| |
| @Override |
| public boolean canAssist(IQuickAssistInvocationContext invocationContext) { |
| return true; |
| } |
| |
| @Override |
| public boolean canFix(Annotation annotation) { |
| return true; |
| } |
| |
| @Override |
| public ICompletionProposal[] computeQuickAssistProposals( |
| IQuickAssistInvocationContext invocationContext) { |
| |
| ISourceViewer sourceViewer = invocationContext.getSourceViewer(); |
| AndroidXmlEditor xmlEditor = AndroidXmlEditor.fromTextViewer(sourceViewer); |
| if (xmlEditor == null) { |
| return null; |
| } |
| |
| IFile file = xmlEditor.getInputFile(); |
| if (file == null) { |
| return null; |
| } |
| int offset = invocationContext.getOffset(); |
| |
| // Ensure that we are over a tag name (for element-based refactoring |
| // operations) or a value (for the extract include refactoring) |
| |
| boolean isValue = false; |
| boolean isReferenceValue = false; |
| boolean isTagName = false; |
| boolean isAttributeName = false; |
| boolean isStylableAttribute = false; |
| ResourceUrl resource = null; |
| IStructuredModel model = null; |
| try { |
| model = xmlEditor.getModelForRead(); |
| IStructuredDocument doc = model.getStructuredDocument(); |
| IStructuredDocumentRegion region = doc.getRegionAtCharacterOffset(offset); |
| ITextRegion subRegion = region.getRegionAtCharacterOffset(offset); |
| if (subRegion != null) { |
| String type = subRegion.getType(); |
| if (type.equals(DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE)) { |
| String value = region.getText(subRegion); |
| // Only extract values that aren't already resources |
| // (and value includes leading ' or ") |
| isValue = true; |
| if (value.startsWith("'@") || value.startsWith("\"@")) { //$NON-NLS-1$ //$NON-NLS-2$ |
| isReferenceValue = true; |
| resource = RenameResourceXmlTextAction.findResource(doc, offset); |
| } |
| } else if (type.equals(DOMRegionContext.XML_TAG_NAME) |
| || type.equals(DOMRegionContext.XML_TAG_OPEN) |
| || type.equals(DOMRegionContext.XML_TAG_CLOSE)) { |
| isTagName = true; |
| } else if (type.equals(DOMRegionContext.XML_TAG_ATTRIBUTE_NAME) ) { |
| isAttributeName = true; |
| String name = region.getText(subRegion); |
| int index = name.indexOf(':'); |
| if (index != -1) { |
| name = name.substring(index + 1); |
| } |
| isStylableAttribute = ExtractStyleRefactoring.isStylableAttribute(name); |
| } else if (type.equals(DOMRegionContext.XML_TAG_ATTRIBUTE_EQUALS)) { |
| // On the edge of an attribute name and an attribute value |
| isAttributeName = true; |
| isStylableAttribute = true; |
| } else if (type.equals(DOMRegionContext.XML_CONTENT)) { |
| resource = RenameResourceXmlTextAction.findResource(doc, offset); |
| } |
| } |
| } finally { |
| if (model != null) { |
| model.releaseFromRead(); |
| } |
| } |
| |
| List<ICompletionProposal> proposals = new ArrayList<ICompletionProposal>(); |
| if (isTagName || isAttributeName || isValue || resource != null) { |
| StructuredTextEditor structuredEditor = xmlEditor.getStructuredTextEditor(); |
| ISelectionProvider provider = structuredEditor.getSelectionProvider(); |
| ISelection selection = provider.getSelection(); |
| if (selection instanceof ITextSelection) { |
| ITextSelection textSelection = (ITextSelection) selection; |
| |
| ITextSelection originalSelection = textSelection; |
| |
| // Most of the visual refactorings do not work on text ranges |
| // ...except for Extract Style where the actual attributes overlapping |
| // the selection is going to be the set of eligible attributes |
| boolean selectionOkay = false; |
| |
| if (textSelection.getLength() == 0 && !isValue) { |
| selectionOkay = true; |
| ISourceViewer textViewer = xmlEditor.getStructuredSourceViewer(); |
| int caretOffset = textViewer.getTextWidget().getCaretOffset(); |
| if (caretOffset >= 0) { |
| Node node = DomUtilities.getNode(textViewer.getDocument(), caretOffset); |
| if (node instanceof IndexedRegion) { |
| IndexedRegion region = (IndexedRegion) node; |
| int startOffset = region.getStartOffset(); |
| int length = region.getEndOffset() - region.getStartOffset(); |
| textSelection = new TextSelection(startOffset, length); |
| } |
| } |
| } |
| |
| if (isValue && !isReferenceValue) { |
| proposals.add(new RefactoringProposal(xmlEditor, |
| new ExtractStringRefactoring(file, xmlEditor, textSelection))); |
| } else if (resource != null) { |
| RenameResourceProcessor processor = new RenameResourceProcessor( |
| file.getProject(), resource.type, resource.name, null); |
| RenameRefactoring refactoring = new RenameRefactoring(processor); |
| proposals.add(new RefactoringProposal(xmlEditor, refactoring)); |
| } |
| |
| LayoutEditorDelegate delegate = LayoutEditorDelegate.fromEditor(xmlEditor); |
| if (delegate != null) { |
| boolean showStyleFirst = isValue || (isAttributeName && isStylableAttribute); |
| if (showStyleFirst) { |
| proposals.add(new RefactoringProposal( |
| xmlEditor, |
| new ExtractStyleRefactoring( |
| file, |
| delegate, |
| originalSelection, |
| null))); |
| } |
| |
| if (selectionOkay) { |
| proposals.add(new RefactoringProposal( |
| xmlEditor, |
| new WrapInRefactoring( |
| file, |
| delegate, |
| textSelection, |
| null))); |
| proposals.add(new RefactoringProposal( |
| xmlEditor, |
| new UnwrapRefactoring( |
| file, |
| delegate, |
| textSelection, |
| null))); |
| proposals.add(new RefactoringProposal( |
| xmlEditor, |
| new ChangeViewRefactoring( |
| file, |
| delegate, |
| textSelection, |
| null))); |
| proposals.add(new RefactoringProposal( |
| xmlEditor, |
| new ChangeLayoutRefactoring( |
| file, |
| delegate, |
| textSelection, |
| null))); |
| } |
| |
| // Extract Include must always have an actual block to be extracted |
| if (textSelection.getLength() > 0) { |
| proposals.add(new RefactoringProposal( |
| xmlEditor, |
| new ExtractIncludeRefactoring( |
| file, |
| delegate, |
| textSelection, |
| null))); |
| } |
| |
| // If it's not a value or attribute name, don't place it on top |
| if (!showStyleFirst) { |
| proposals.add(new RefactoringProposal( |
| xmlEditor, |
| new ExtractStyleRefactoring( |
| file, |
| delegate, |
| originalSelection, |
| null))); |
| } |
| } |
| } |
| } |
| |
| if (proposals.size() == 0) { |
| return null; |
| } else { |
| return proposals.toArray(new ICompletionProposal[proposals.size()]); |
| } |
| } |
| |
| @Override |
| public String getErrorMessage() { |
| return null; |
| } |
| |
| private static class RefactoringProposal |
| implements ICompletionProposal { |
| private final AndroidXmlEditor mEditor; |
| private final Refactoring mRefactoring; |
| |
| RefactoringProposal(AndroidXmlEditor editor, Refactoring refactoring) { |
| super(); |
| mEditor = editor; |
| mRefactoring = refactoring; |
| } |
| |
| @Override |
| public void apply(IDocument document) { |
| RefactoringWizard wizard = null; |
| if (mRefactoring instanceof VisualRefactoring) { |
| wizard = ((VisualRefactoring) mRefactoring).createWizard(); |
| } else if (mRefactoring instanceof ExtractStringRefactoring) { |
| wizard = new ExtractStringWizard((ExtractStringRefactoring) mRefactoring, |
| mEditor.getProject()); |
| } else if (mRefactoring instanceof RenameRefactoring) { |
| RenameRefactoring refactoring = (RenameRefactoring) mRefactoring; |
| RenameResourceProcessor processor = |
| (RenameResourceProcessor) refactoring.getProcessor(); |
| ResourceType type = processor.getType(); |
| wizard = new RenameResourceWizard((RenameRefactoring) mRefactoring, type, false); |
| } else { |
| throw new IllegalArgumentException(); |
| } |
| |
| RefactoringWizardOpenOperation op = new RefactoringWizardOpenOperation(wizard); |
| try { |
| IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); |
| op.run(window.getShell(), wizard.getDefaultPageTitle()); |
| } catch (InterruptedException e) { |
| } |
| } |
| |
| @Override |
| public String getAdditionalProposalInfo() { |
| return String.format("Initiates the \"%1$s\" refactoring", mRefactoring.getName()); |
| } |
| |
| @Override |
| public IContextInformation getContextInformation() { |
| return null; |
| } |
| |
| @Override |
| public String getDisplayString() { |
| return mRefactoring.getName(); |
| } |
| |
| @Override |
| public Image getImage() { |
| return AdtPlugin.getAndroidLogo(); |
| } |
| |
| @Override |
| public Point getSelection(IDocument document) { |
| return null; |
| } |
| } |
| } |