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