blob: 811e695b6603e0dbf1ee01b4d2175bbd957d1d18 [file] [log] [blame]
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* 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 com.intellij.refactoring.lang;
import com.intellij.codeInsight.PsiEquivalenceUtil;
import com.intellij.codeInsight.highlighting.HighlightManager;
import com.intellij.find.FindManager;
import com.intellij.ide.TitledHandler;
import com.intellij.lang.Language;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ApplicationNamesInfo;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.LogicalPosition;
import com.intellij.openapi.editor.ScrollType;
import com.intellij.openapi.editor.colors.EditorColors;
import com.intellij.openapi.editor.colors.EditorColorsManager;
import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.fileTypes.FileTypeManager;
import com.intellij.openapi.fileTypes.LanguageFileType;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.impl.source.resolve.reference.impl.providers.PsiFileSystemItemUtil;
import com.intellij.refactoring.RefactoringActionHandler;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.util.CommonRefactoringUtil;
import com.intellij.ui.ReplacePromptDialog;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.PairConsumer;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
/**
* @author ven
*/
public abstract class ExtractIncludeFileBase<T extends PsiElement> implements RefactoringActionHandler, TitledHandler {
private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.lang.ExtractIncludeFileBase");
private static final String REFACTORING_NAME = RefactoringBundle.message("extract.include.file.title");
protected PsiFile myIncludingFile;
public static final String HELP_ID = "refactoring.extractInclude";
private static class IncludeDuplicate<E extends PsiElement> {
private final SmartPsiElementPointer<E> myStart;
private final SmartPsiElementPointer<E> myEnd;
private IncludeDuplicate(E start, E end) {
myStart = SmartPointerManager.getInstance(start.getProject()).createSmartPsiElementPointer(start);
myEnd = SmartPointerManager.getInstance(start.getProject()).createSmartPsiElementPointer(end);
}
E getStart() {
return myStart.getElement();
}
E getEnd() {
return myEnd.getElement();
}
}
protected abstract void doReplaceRange(final String includePath, final T first, final T last);
@NotNull
protected String doExtract(final PsiDirectory targetDirectory,
final String targetfileName,
final T first,
final T last,
final Language includingLanguage) throws IncorrectOperationException {
final PsiFile file = targetDirectory.createFile(targetfileName);
Project project = targetDirectory.getProject();
final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project);
final Document document = documentManager.getDocument(file);
document.replaceString(0, document.getTextLength(), first.getText().trim());
documentManager.commitDocument(document);
CodeStyleManager.getInstance(PsiManager.getInstance(project).getProject()).reformat(file); //TODO: adjustLineIndent
final String relativePath = PsiFileSystemItemUtil.getRelativePath(first.getContainingFile(), file);
if (relativePath == null) throw new IncorrectOperationException("Cannot extract!");
return relativePath;
}
protected abstract boolean verifyChildRange (final T first, final T last);
private void replaceDuplicates(final String includePath,
final List<IncludeDuplicate<T>> duplicates,
final Editor editor,
final Project project) {
if (duplicates.size() > 0) {
final String message = RefactoringBundle.message("idea.has.found.fragments.that.can.be.replaced.with.include.directive",
ApplicationNamesInfo.getInstance().getProductName());
final int exitCode = Messages.showYesNoDialog(project, message, getRefactoringName(), Messages.getInformationIcon());
if (exitCode == Messages.YES) {
CommandProcessor.getInstance().executeCommand(project, new Runnable() {
@Override
public void run() {
boolean replaceAll = false;
for (IncludeDuplicate<T> pair : duplicates) {
if (!replaceAll) {
highlightInEditor(project, pair, editor);
ReplacePromptDialog promptDialog = new ReplacePromptDialog(false, RefactoringBundle.message("replace.fragment"), project);
promptDialog.show();
final int promptResult = promptDialog.getExitCode();
if (promptResult == FindManager.PromptResult.SKIP) continue;
if (promptResult == FindManager.PromptResult.CANCEL) break;
if (promptResult == FindManager.PromptResult.OK) {
doReplaceRange(includePath, pair.getStart(), pair.getEnd());
}
else if (promptResult == FindManager.PromptResult.ALL) {
doReplaceRange(includePath, pair.getStart(), pair.getEnd());
replaceAll = true;
}
else {
LOG.error("Unknown return status");
}
}
else {
doReplaceRange(includePath, pair.getStart(), pair.getEnd());
}
}
}
}, RefactoringBundle.message("remove.duplicates.command"), null);
}
}
}
private static void highlightInEditor(final Project project, final IncludeDuplicate pair, final Editor editor) {
final HighlightManager highlightManager = HighlightManager.getInstance(project);
EditorColorsManager colorsManager = EditorColorsManager.getInstance();
TextAttributes attributes = colorsManager.getGlobalScheme().getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES);
final int startOffset = pair.getStart().getTextRange().getStartOffset();
final int endOffset = pair.getEnd().getTextRange().getEndOffset();
highlightManager.addRangeHighlight(editor, startOffset, endOffset, attributes, true, null);
final LogicalPosition logicalPosition = editor.offsetToLogicalPosition(startOffset);
editor.getScrollingModel().scrollTo(logicalPosition, ScrollType.MAKE_VISIBLE);
}
@Override
public void invoke(@NotNull Project project, @NotNull PsiElement[] elements, DataContext dataContext) {
}
@NotNull
protected Language getLanguageForExtract(PsiElement firstExtracted) {
return firstExtracted.getLanguage();
}
@Nullable
private static FileType getFileType(final Language language) {
final FileType[] fileTypes = FileTypeManager.getInstance().getRegisteredFileTypes();
for (FileType fileType : fileTypes) {
if (fileType instanceof LanguageFileType && language.equals(((LanguageFileType)fileType).getLanguage())) return fileType;
}
return null;
}
@Override
public void invoke(@NotNull final Project project, final Editor editor, final PsiFile file, DataContext dataContext) {
myIncludingFile = file;
if (!editor.getSelectionModel().hasSelection()) {
String message = RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message("no.selection"));
CommonRefactoringUtil.showErrorHint(project, editor, message, getRefactoringName(), HELP_ID);
return;
}
final int start = editor.getSelectionModel().getSelectionStart();
final int end = editor.getSelectionModel().getSelectionEnd();
final Pair<T, T> children = findPairToExtract(start, end);
if (children == null) {
String message = RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message("selection.does.not.form.a.fragment.for.extraction"));
CommonRefactoringUtil.showErrorHint(project, editor, message, getRefactoringName(), HELP_ID);
return;
}
if (!verifyChildRange(children.getFirst(), children.getSecond())) {
String message = RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message("cannot.extract.selected.elements.into.include.file"));
CommonRefactoringUtil.showErrorHint(project, editor, message, getRefactoringName(), HELP_ID);
return;
}
final FileType fileType = getFileType(getLanguageForExtract(children.getFirst()));
if (!(fileType instanceof LanguageFileType)) {
String message = RefactoringBundle.message("the.language.for.selected.elements.has.no.associated.file.type");
CommonRefactoringUtil.showErrorHint(project, editor, message, getRefactoringName(), HELP_ID);
return;
}
if (!CommonRefactoringUtil.checkReadOnlyStatus(project, file)) return;
ExtractIncludeDialog dialog = createDialog(file.getContainingDirectory(), getExtractExtension(fileType, children.first));
dialog.show();
if (dialog.getExitCode() == DialogWrapper.OK_EXIT_CODE) {
final PsiDirectory targetDirectory = dialog.getTargetDirectory();
LOG.assertTrue(targetDirectory != null);
final String targetfileName = dialog.getTargetFileName();
CommandProcessor.getInstance().executeCommand(project, new Runnable() {
@Override
public void run() {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
try {
final List<IncludeDuplicate<T>> duplicates = new ArrayList<IncludeDuplicate<T>>();
final T first = children.getFirst();
final T second = children.getSecond();
PsiEquivalenceUtil.findChildRangeDuplicates(first, second, file, new PairConsumer<PsiElement, PsiElement>() {
@Override
public void consume(final PsiElement start, final PsiElement end) {
duplicates.add(new IncludeDuplicate<T>((T) start, (T) end));
}
});
final String includePath = processPrimaryFragment(first, second, targetDirectory, targetfileName, file);
editor.getCaretModel().moveToOffset(first.getTextRange().getStartOffset());
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
replaceDuplicates(includePath, duplicates, editor, project);
}
});
}
catch (IncorrectOperationException e) {
CommonRefactoringUtil.showErrorMessage(getRefactoringName(), e.getMessage(), null, project);
}
editor.getSelectionModel().removeSelection();
}
});
}
}, getRefactoringName(), null);
}
}
protected ExtractIncludeDialog createDialog(final PsiDirectory containingDirectory, final String extractExtension) {
return new ExtractIncludeDialog(containingDirectory, extractExtension);
}
@Nullable
protected abstract Pair<T, T> findPairToExtract(int start, int end);
@NonNls
protected String getExtractExtension(final FileType extractFileType, final T first) {
return extractFileType.getDefaultExtension();
}
public boolean isValidRange(final T firstToExtract, final T lastToExtract) {
return verifyChildRange(firstToExtract, lastToExtract);
}
public String processPrimaryFragment(final T firstToExtract,
final T lastToExtract,
final PsiDirectory targetDirectory,
final String targetfileName,
final PsiFile srcFile) throws IncorrectOperationException {
final String includePath = doExtract(targetDirectory, targetfileName, firstToExtract, lastToExtract,
srcFile.getLanguage());
doReplaceRange(includePath, firstToExtract, lastToExtract);
return includePath;
}
@Override
public String getActionTitle() {
return "Extract Include File...";
}
protected String getRefactoringName() {
return REFACTORING_NAME;
}
}