Home | History | Annotate | Download | only in refactoring
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.eclipse.org/org/documents/epl-v10.php
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.ide.eclipse.adt.internal.editors.layout.refactoring;
     18 
     19 import com.android.ide.common.resources.ResourceUrl;
     20 import com.android.ide.eclipse.adt.AdtPlugin;
     21 import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
     22 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
     23 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
     24 import com.android.ide.eclipse.adt.internal.refactorings.core.RenameResourceProcessor;
     25 import com.android.ide.eclipse.adt.internal.refactorings.core.RenameResourceWizard;
     26 import com.android.ide.eclipse.adt.internal.refactorings.core.RenameResourceXmlTextAction;
     27 import com.android.ide.eclipse.adt.internal.refactorings.extractstring.ExtractStringRefactoring;
     28 import com.android.ide.eclipse.adt.internal.refactorings.extractstring.ExtractStringWizard;
     29 import com.android.resources.ResourceType;
     30 
     31 import org.eclipse.core.resources.IFile;
     32 import org.eclipse.jface.text.IDocument;
     33 import org.eclipse.jface.text.ITextSelection;
     34 import org.eclipse.jface.text.TextSelection;
     35 import org.eclipse.jface.text.contentassist.ICompletionProposal;
     36 import org.eclipse.jface.text.contentassist.IContextInformation;
     37 import org.eclipse.jface.text.quickassist.IQuickAssistInvocationContext;
     38 import org.eclipse.jface.text.quickassist.IQuickAssistProcessor;
     39 import org.eclipse.jface.text.source.Annotation;
     40 import org.eclipse.jface.text.source.ISourceViewer;
     41 import org.eclipse.jface.viewers.ISelection;
     42 import org.eclipse.jface.viewers.ISelectionProvider;
     43 import org.eclipse.ltk.core.refactoring.Refactoring;
     44 import org.eclipse.ltk.core.refactoring.participants.RenameRefactoring;
     45 import org.eclipse.ltk.ui.refactoring.RefactoringWizard;
     46 import org.eclipse.ltk.ui.refactoring.RefactoringWizardOpenOperation;
     47 import org.eclipse.swt.graphics.Image;
     48 import org.eclipse.swt.graphics.Point;
     49 import org.eclipse.ui.IWorkbenchWindow;
     50 import org.eclipse.ui.PlatformUI;
     51 import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
     52 import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
     53 import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
     54 import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion;
     55 import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion;
     56 import org.eclipse.wst.sse.ui.StructuredTextEditor;
     57 import org.eclipse.wst.xml.core.internal.regions.DOMRegionContext;
     58 import org.w3c.dom.Node;
     59 
     60 import java.util.ArrayList;
     61 import java.util.List;
     62 
     63 /**
     64  * QuickAssistProcessor which helps invoke refactoring operations on text elements.
     65  */
     66 @SuppressWarnings("restriction") // XML model
     67 public class RefactoringAssistant implements IQuickAssistProcessor {
     68 
     69     /**
     70      * Creates a new {@link RefactoringAssistant}
     71      */
     72     public RefactoringAssistant() {
     73     }
     74 
     75     @Override
     76     public boolean canAssist(IQuickAssistInvocationContext invocationContext) {
     77         return true;
     78     }
     79 
     80     @Override
     81     public boolean canFix(Annotation annotation) {
     82         return true;
     83     }
     84 
     85     @Override
     86     public ICompletionProposal[] computeQuickAssistProposals(
     87             IQuickAssistInvocationContext invocationContext) {
     88 
     89         ISourceViewer sourceViewer = invocationContext.getSourceViewer();
     90         AndroidXmlEditor xmlEditor = AndroidXmlEditor.fromTextViewer(sourceViewer);
     91         if (xmlEditor == null) {
     92             return null;
     93         }
     94 
     95         IFile file = xmlEditor.getInputFile();
     96         if (file == null) {
     97             return null;
     98         }
     99         int offset = invocationContext.getOffset();
    100 
    101         // Ensure that we are over a tag name (for element-based refactoring
    102         // operations) or a value (for the extract include refactoring)
    103 
    104         boolean isValue = false;
    105         boolean isReferenceValue = false;
    106         boolean isTagName = false;
    107         boolean isAttributeName = false;
    108         boolean isStylableAttribute = false;
    109         ResourceUrl resource = null;
    110         IStructuredModel model = null;
    111         try {
    112             model = xmlEditor.getModelForRead();
    113             IStructuredDocument doc = model.getStructuredDocument();
    114             IStructuredDocumentRegion region = doc.getRegionAtCharacterOffset(offset);
    115             ITextRegion subRegion = region.getRegionAtCharacterOffset(offset);
    116             if (subRegion != null) {
    117                 String type = subRegion.getType();
    118                 if (type.equals(DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE)) {
    119                     String value = region.getText(subRegion);
    120                     // Only extract values that aren't already resources
    121                     // (and value includes leading ' or ")
    122                     isValue = true;
    123                     if (value.startsWith("'@") || value.startsWith("\"@")) { //$NON-NLS-1$ //$NON-NLS-2$
    124                         isReferenceValue = true;
    125                         resource = RenameResourceXmlTextAction.findResource(doc, offset);
    126                     }
    127                 } else if (type.equals(DOMRegionContext.XML_TAG_NAME)
    128                         || type.equals(DOMRegionContext.XML_TAG_OPEN)
    129                         || type.equals(DOMRegionContext.XML_TAG_CLOSE)) {
    130                     isTagName = true;
    131                 } else if (type.equals(DOMRegionContext.XML_TAG_ATTRIBUTE_NAME) ) {
    132                     isAttributeName = true;
    133                     String name = region.getText(subRegion);
    134                     int index = name.indexOf(':');
    135                     if (index != -1) {
    136                         name = name.substring(index + 1);
    137                     }
    138                     isStylableAttribute = ExtractStyleRefactoring.isStylableAttribute(name);
    139                 } else if (type.equals(DOMRegionContext.XML_TAG_ATTRIBUTE_EQUALS)) {
    140                     // On the edge of an attribute name and an attribute value
    141                     isAttributeName = true;
    142                     isStylableAttribute = true;
    143                 } else if (type.equals(DOMRegionContext.XML_CONTENT)) {
    144                     resource = RenameResourceXmlTextAction.findResource(doc, offset);
    145                 }
    146             }
    147         } finally {
    148             if (model != null) {
    149                 model.releaseFromRead();
    150             }
    151         }
    152 
    153         List<ICompletionProposal> proposals = new ArrayList<ICompletionProposal>();
    154         if (isTagName || isAttributeName || isValue || resource != null) {
    155             StructuredTextEditor structuredEditor = xmlEditor.getStructuredTextEditor();
    156             ISelectionProvider provider = structuredEditor.getSelectionProvider();
    157             ISelection selection = provider.getSelection();
    158             if (selection instanceof ITextSelection) {
    159                 ITextSelection textSelection = (ITextSelection) selection;
    160 
    161                 ITextSelection originalSelection = textSelection;
    162 
    163                 // Most of the visual refactorings do not work on text ranges
    164                 // ...except for Extract Style where the actual attributes overlapping
    165                 // the selection is going to be the set of eligible attributes
    166                 boolean selectionOkay = false;
    167 
    168                 if (textSelection.getLength() == 0 && !isValue) {
    169                     selectionOkay = true;
    170                     ISourceViewer textViewer = xmlEditor.getStructuredSourceViewer();
    171                     int caretOffset = textViewer.getTextWidget().getCaretOffset();
    172                     if (caretOffset >= 0) {
    173                         Node node = DomUtilities.getNode(textViewer.getDocument(), caretOffset);
    174                         if (node instanceof IndexedRegion) {
    175                             IndexedRegion region = (IndexedRegion) node;
    176                             int startOffset = region.getStartOffset();
    177                             int length = region.getEndOffset() - region.getStartOffset();
    178                             textSelection = new TextSelection(startOffset, length);
    179                         }
    180                     }
    181                 }
    182 
    183                 if (isValue && !isReferenceValue) {
    184                     proposals.add(new RefactoringProposal(xmlEditor,
    185                             new ExtractStringRefactoring(file, xmlEditor, textSelection)));
    186                 } else if (resource != null) {
    187                     RenameResourceProcessor processor = new RenameResourceProcessor(
    188                             file.getProject(), resource.type, resource.name, null);
    189                     RenameRefactoring refactoring = new RenameRefactoring(processor);
    190                     proposals.add(new RefactoringProposal(xmlEditor, refactoring));
    191                 }
    192 
    193                 LayoutEditorDelegate delegate = LayoutEditorDelegate.fromEditor(xmlEditor);
    194                 if (delegate != null) {
    195                     boolean showStyleFirst = isValue || (isAttributeName && isStylableAttribute);
    196                     if (showStyleFirst) {
    197                         proposals.add(new RefactoringProposal(
    198                                 xmlEditor,
    199                                 new ExtractStyleRefactoring(
    200                                         file,
    201                                         delegate,
    202                                         originalSelection,
    203                                         null)));
    204                     }
    205 
    206                     if (selectionOkay) {
    207                         proposals.add(new RefactoringProposal(
    208                                 xmlEditor,
    209                                 new WrapInRefactoring(
    210                                         file,
    211                                         delegate,
    212                                         textSelection,
    213                                         null)));
    214                         proposals.add(new RefactoringProposal(
    215                                 xmlEditor,
    216                                 new UnwrapRefactoring(
    217                                         file,
    218                                         delegate,
    219                                         textSelection,
    220                                         null)));
    221                         proposals.add(new RefactoringProposal(
    222                                 xmlEditor,
    223                                 new ChangeViewRefactoring(
    224                                         file,
    225                                         delegate,
    226                                         textSelection,
    227                                         null)));
    228                         proposals.add(new RefactoringProposal(
    229                                 xmlEditor,
    230                                 new ChangeLayoutRefactoring(
    231                                         file,
    232                                         delegate,
    233                                         textSelection,
    234                                         null)));
    235                     }
    236 
    237                     // Extract Include must always have an actual block to be extracted
    238                     if (textSelection.getLength() > 0) {
    239                         proposals.add(new RefactoringProposal(
    240                                 xmlEditor,
    241                                 new ExtractIncludeRefactoring(
    242                                         file,
    243                                         delegate,
    244                                         textSelection,
    245                                         null)));
    246                     }
    247 
    248                     // If it's not a value or attribute name, don't place it on top
    249                     if (!showStyleFirst) {
    250                         proposals.add(new RefactoringProposal(
    251                                 xmlEditor,
    252                                 new ExtractStyleRefactoring(
    253                                         file,
    254                                         delegate,
    255                                         originalSelection,
    256                                         null)));
    257                     }
    258                 }
    259             }
    260         }
    261 
    262         if (proposals.size() == 0) {
    263             return null;
    264         } else {
    265             return proposals.toArray(new ICompletionProposal[proposals.size()]);
    266         }
    267     }
    268 
    269     @Override
    270     public String getErrorMessage() {
    271         return null;
    272     }
    273 
    274     private static class RefactoringProposal
    275             implements ICompletionProposal {
    276         private final AndroidXmlEditor mEditor;
    277         private final Refactoring mRefactoring;
    278 
    279         RefactoringProposal(AndroidXmlEditor editor, Refactoring refactoring) {
    280             super();
    281             mEditor = editor;
    282             mRefactoring = refactoring;
    283         }
    284 
    285         @Override
    286         public void apply(IDocument document) {
    287             RefactoringWizard wizard = null;
    288             if (mRefactoring instanceof VisualRefactoring) {
    289                 wizard = ((VisualRefactoring) mRefactoring).createWizard();
    290             } else if (mRefactoring instanceof ExtractStringRefactoring) {
    291                 wizard = new ExtractStringWizard((ExtractStringRefactoring) mRefactoring,
    292                         mEditor.getProject());
    293             } else if (mRefactoring instanceof RenameRefactoring) {
    294                 RenameRefactoring refactoring = (RenameRefactoring) mRefactoring;
    295                 RenameResourceProcessor processor =
    296                         (RenameResourceProcessor) refactoring.getProcessor();
    297                 ResourceType type = processor.getType();
    298                 wizard = new RenameResourceWizard((RenameRefactoring) mRefactoring, type, false);
    299             } else {
    300                 throw new IllegalArgumentException();
    301             }
    302 
    303             RefactoringWizardOpenOperation op = new RefactoringWizardOpenOperation(wizard);
    304             try {
    305                 IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
    306                 op.run(window.getShell(), wizard.getDefaultPageTitle());
    307             } catch (InterruptedException e) {
    308             }
    309         }
    310 
    311         @Override
    312         public String getAdditionalProposalInfo() {
    313             return String.format("Initiates the \"%1$s\" refactoring", mRefactoring.getName());
    314         }
    315 
    316         @Override
    317         public IContextInformation getContextInformation() {
    318             return null;
    319         }
    320 
    321         @Override
    322         public String getDisplayString() {
    323             return mRefactoring.getName();
    324         }
    325 
    326         @Override
    327         public Image getImage() {
    328             return AdtPlugin.getAndroidLogo();
    329         }
    330 
    331         @Override
    332         public Point getSelection(IDocument document) {
    333             return null;
    334         }
    335     }
    336 }
    337