blob: aa8c119992938ed59cc7a68464435f95f3a95065 [file] [log] [blame]
/*
* 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;
}
}
}