/* * Copyright 2000-2009 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; } }