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