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