blob: 9f3160762d094e8bb2e411ce8cfc433e26c88c45 [file] [log] [blame]
/*
* Copyright 2002-2007 Sascha Weinreuter
*
* Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
*
* 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 org.intellij.lang.xpath.xslt.refactoring.extractTemplate;
import com.intellij.psi.codeStyle.CodeStyleManager;
import org.intellij.lang.xpath.psi.XPathVariable;
import org.intellij.lang.xpath.psi.XPathVariableReference;
import org.intellij.lang.xpath.xslt.XsltSupport;
import org.intellij.lang.xpath.xslt.psi.XsltElementFactory;
import org.intellij.lang.xpath.xslt.psi.XsltVariable;
import org.intellij.lang.xpath.xslt.refactoring.RefactoringUtil;
import org.intellij.lang.xpath.xslt.refactoring.XsltRefactoringActionBase;
import org.intellij.lang.xpath.xslt.util.XsltCodeInsightUtil;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.application.Result;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.SelectionModel;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.search.LocalSearchScope;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.xml.*;
import com.intellij.util.Query;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
/**
* Created by IntelliJ IDEA.
* User: sweinreuter
* Date: 10.06.2007
*/
@SuppressWarnings({ "ComponentNotRegistered" })
public class XsltExtractTemplateAction extends XsltRefactoringActionBase {
public String getRefactoringName() {
return "Extract Template";
}
@Override
public void invoke(@NotNull Project project, Editor editor, PsiFile file, DataContext dataContext) {
if (!invokeImpl(editor, file)) {
super.invoke(project, editor, file, dataContext);
}
}
private boolean invokeImpl(Editor editor, PsiFile file) {
return invokeImpl(editor, file, null);
}
public boolean invokeImpl(Editor editor, PsiFile file, String newName) {
final SelectionModel selectionModel = editor.getSelectionModel();
if (!selectionModel.hasSelection()) {
return false;
}
int startOffset = selectionModel.getSelectionStart();
PsiElement start = file.findElementAt(startOffset);
if (start instanceof PsiWhiteSpace) {
if ((start = PsiTreeUtil.nextLeaf(start)) != null) {
startOffset = start.getTextOffset();
}
}
int endOffset = selectionModel.getSelectionEnd();
PsiElement end = file.findElementAt(endOffset - 1);
if (end instanceof PsiWhiteSpace) {
if ((end = PsiTreeUtil.prevLeaf(end)) != null) {
endOffset = end.getTextRange().getEndOffset();
}
}
if (start == null || end == null) {
return false;
}
final PsiElement parent = start.getParent();
if (!(parent instanceof XmlTag || parent instanceof XmlComment)) {
return false;
}
if (start == end) {
if (start.getTextRange().getStartOffset() != startOffset) {
return false;
}
if (end.getTextRange().getEndOffset() != endOffset) {
return false;
}
if (extractFrom(start, end, newName)) {
// tests dislike empty selection
selectionModel.removeSelection();
return true;
}
} else {
final XmlElement startTag = PsiTreeUtil.getParentOfType(start, XmlTag.class, XmlComment.class);
if (startTag == null) {
return false;
}
if (startTag.getTextRange().getStartOffset() != startOffset) {
return false;
}
final XmlElement endTag = PsiTreeUtil.getParentOfType(end, XmlTag.class, XmlComment.class);
if (endTag == null) {
return false;
}
if (endTag.getTextRange().getEndOffset() != endOffset) {
return false;
}
if (startTag != endTag) {
if (startTag.getParent() != endTag.getParent()) {
return false;
}
}
if (extractFrom(startTag, endTag, newName)) {
// tests dislike empty selection
selectionModel.removeSelection();
return true;
}
}
return false;
}
private boolean extractFrom(final @NotNull PsiElement start, final PsiElement end, String newName) {
final XmlTag outerTemplate = XsltCodeInsightUtil.getTemplateTag(start, false);
if (outerTemplate == null) {
return false;
}
final XmlTag parentScope = PsiTreeUtil.getParentOfType(start, XmlTag.class, true);
if (parentScope == null) {
return false;
}
final StringBuilder sb = new StringBuilder("\n");
final Set<String> vars = new LinkedHashSet<String>();
final int startOffset = start.getTextRange().getStartOffset();
final int endOffset = end.getTextRange().getEndOffset();
PsiElement e = start;
while (e != null) {
if (e instanceof XmlTag) {
final XmlTag tag = (XmlTag)e;
if (XsltSupport.isVariable(tag)) {
final XsltVariable variable = XsltElementFactory.getInstance().wrapElement(tag, XsltVariable.class);
final LocalSearchScope searchScope = new LocalSearchScope(parentScope);
final Query<PsiReference> query = ReferencesSearch.search(variable, searchScope);
for (PsiReference reference : query) {
final XmlElement context = PsiTreeUtil.getContextOfType(reference.getElement(), XmlElement.class, true);
if (context == null || context.getTextRange().getStartOffset() > endOffset) {
return false;
}
}
}
}
sb.append(e.getText());
final List<XPathVariableReference> references = RefactoringUtil.collectVariableReferences(e);
for (XPathVariableReference reference : references) {
final XPathVariable variable = reference.resolve();
if (variable instanceof XsltVariable) {
final XmlTag var = ((XsltVariable)variable).getTag();
if (var.getTextRange().getStartOffset() < startOffset) {
// don't pass through global parameters and variables
if (XsltCodeInsightUtil.getTemplateTag(variable, false) != null) {
vars.add(variable.getName());
}
}
} else if (variable == null) {
// TODO just copy unresolved refs or fail?
vars.add(reference.getReferencedName());
}
}
if (e == end) {
break;
}
e = e.getNextSibling();
}
sb.append("\n");
final String s = newName == null ?
Messages.showInputDialog(start.getProject(), "Template Name: ", getRefactoringName(), Messages.getQuestionIcon()) :
newName;
if (s != null) {
new WriteCommandAction(start.getProject()) {
protected void run(Result result) throws Throwable {
final PsiFile containingFile = start.getContainingFile();
XmlTag templateTag = parentScope.createChildTag("template", XsltSupport.XSLT_NS, sb.toString(), false);
templateTag.setAttribute("name", s);
final PsiElement dummy = XmlElementFactory.getInstance(start.getProject()).createDisplayText(" ");
final PsiElement outerParent = outerTemplate.getParent();
final PsiElement element = outerParent.addAfter(dummy, outerTemplate);
templateTag = (XmlTag)outerParent.addAfter(templateTag, element);
final TextRange adjust = templateTag.getTextRange();
final PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance(start.getProject());
final Document doc = psiDocumentManager.getDocument(containingFile);
assert doc != null;
psiDocumentManager.doPostponedOperationsAndUnblockDocument(doc);
CodeStyleManager.getInstance(start.getManager().getProject()).adjustLineIndent(containingFile, adjust);
final PsiElement parent = start.getParent();
XmlTag callTag = parentScope.createChildTag("call-template", XsltSupport.XSLT_NS, null, false);
callTag.setAttribute("name", s);
if (start instanceof XmlToken && ((XmlToken)start).getTokenType() == XmlTokenType.XML_DATA_CHARACTERS) {
assert start == end;
callTag = (XmlTag)start.replace(callTag);
} else {
callTag = (XmlTag)parent.addBefore(callTag, start);
parent.deleteChildRange(start, end);
}
for (String var : vars) {
final XmlTag param = templateTag.createChildTag("param", XsltSupport.XSLT_NS, null, false);
param.setAttribute("name", var);
RefactoringUtil.addParameter(templateTag, param);
final XmlTag arg = RefactoringUtil.addWithParam(callTag);
arg.setAttribute("name", var);
arg.setAttribute("select", "$" + var);
}
}
}.execute().logException(Logger.getInstance(getClass().getName()));
}
return true;
}
protected boolean actionPerformedImpl(PsiFile file, Editor editor, XmlAttribute context, int offset) {
return false;
}
@Override
@Nullable
public String getErrorMessage(Editor editor, PsiFile file, XmlAttribute context) {
if (!editor.getSelectionModel().hasSelection()) {
return "Please select the code that should be extracted.";
}
return null;
}
}