ComponentWithBrowseButton - optional remove listener on hide
[fedora-idea.git] / platform / lang-impl / src / com / intellij / refactoring / lang / ExtractIncludeFileBase.java
blobce090b57bba1801d388dd1ee9559b4b01bf44428
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.intellij.refactoring.lang;
19 import com.intellij.codeInsight.PsiEquivalenceUtil;
20 import com.intellij.codeInsight.highlighting.HighlightManager;
21 import com.intellij.find.FindManager;
22 import com.intellij.lang.Language;
23 import com.intellij.openapi.actionSystem.DataContext;
24 import com.intellij.openapi.application.ApplicationManager;
25 import com.intellij.openapi.application.ApplicationNamesInfo;
26 import com.intellij.openapi.command.CommandProcessor;
27 import com.intellij.openapi.diagnostic.Logger;
28 import com.intellij.openapi.editor.Document;
29 import com.intellij.openapi.editor.Editor;
30 import com.intellij.openapi.editor.LogicalPosition;
31 import com.intellij.openapi.editor.ScrollType;
32 import com.intellij.openapi.editor.colors.EditorColors;
33 import com.intellij.openapi.editor.colors.EditorColorsManager;
34 import com.intellij.openapi.editor.markup.TextAttributes;
35 import com.intellij.openapi.fileTypes.FileType;
36 import com.intellij.openapi.fileTypes.FileTypeManager;
37 import com.intellij.openapi.fileTypes.LanguageFileType;
38 import com.intellij.openapi.project.Project;
39 import com.intellij.openapi.ui.DialogWrapper;
40 import com.intellij.openapi.ui.Messages;
41 import com.intellij.openapi.util.Pair;
42 import com.intellij.psi.*;
43 import com.intellij.psi.impl.source.resolve.reference.impl.providers.PsiFileSystemItemUtil;
44 import com.intellij.refactoring.RefactoringBundle;
45 import com.intellij.refactoring.util.CommonRefactoringUtil;
46 import com.intellij.ui.ReplacePromptDialog;
47 import com.intellij.util.IncorrectOperationException;
48 import com.intellij.util.PairConsumer;
49 import org.jetbrains.annotations.NonNls;
50 import org.jetbrains.annotations.NotNull;
51 import org.jetbrains.annotations.Nullable;
53 import java.util.ArrayList;
54 import java.util.List;
56 /**
57 * @author ven
59 public abstract class ExtractIncludeFileBase<T extends PsiElement> implements TitledHandler {
60 private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.lang.ExtractIncludeFileBase");
61 private static final String REFACTORING_NAME = RefactoringBundle.message("extract.include.file.title");
62 protected PsiFile myIncludingFile;
63 public static final String HELP_ID = "refactoring.extractInclude";
65 private static class IncludeDuplicate<E extends PsiElement> {
66 private final SmartPsiElementPointer<E> myStart;
67 private final SmartPsiElementPointer<E> myEnd;
69 private IncludeDuplicate(E start, E end) {
70 myStart = SmartPointerManager.getInstance(start.getProject()).createSmartPsiElementPointer(start);
71 myEnd = SmartPointerManager.getInstance(start.getProject()).createSmartPsiElementPointer(end);
74 E getStart() {
75 return myStart.getElement();
78 E getEnd() {
79 return myEnd.getElement();
84 protected abstract void doReplaceRange(final String includePath, final T first, final T last);
86 @NotNull
87 protected String doExtract(final PsiDirectory targetDirectory,
88 final String targetfileName,
89 final T first,
90 final T last,
91 final Language includingLanguage) throws IncorrectOperationException {
92 final PsiFile file = targetDirectory.createFile(targetfileName);
93 Project project = targetDirectory.getProject();
94 final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project);
95 final Document document = documentManager.getDocument(file);
96 document.replaceString(0, document.getTextLength(), first.getText().trim());
97 documentManager.commitDocument(document);
98 PsiManager.getInstance(project).getCodeStyleManager().reformat(file); //TODO: adjustLineIndent
100 final String relativePath = PsiFileSystemItemUtil.getRelativePath(first.getContainingFile(), file);
101 if (relativePath == null) throw new IncorrectOperationException("Cannot extract!");
102 return relativePath;
105 protected abstract boolean verifyChildRange (final T first, final T last);
107 private void replaceDuplicates(final String includePath,
108 final List<IncludeDuplicate<T>> duplicates,
109 final Editor editor,
110 final Project project) {
111 if (duplicates.size() > 0) {
112 final String message = RefactoringBundle.message("idea.has.found.fragments.that.can.be.replaced.with.include.directive",
113 ApplicationNamesInfo.getInstance().getProductName());
114 final int exitCode = Messages.showYesNoDialog(project, message, getRefactoringName(), Messages.getInformationIcon());
115 if (exitCode == DialogWrapper.OK_EXIT_CODE) {
116 CommandProcessor.getInstance().executeCommand(project, new Runnable() {
117 public void run() {
118 boolean replaceAll = false;
119 for (IncludeDuplicate<T> pair : duplicates) {
120 if (!replaceAll) {
122 highlightInEditor(project, pair, editor);
124 ReplacePromptDialog promptDialog = new ReplacePromptDialog(false, RefactoringBundle.message("replace.fragment"), project);
125 promptDialog.show();
126 final int promptResult = promptDialog.getExitCode();
127 if (promptResult == FindManager.PromptResult.SKIP) continue;
128 if (promptResult == FindManager.PromptResult.CANCEL) break;
130 if (promptResult == FindManager.PromptResult.OK) {
131 doReplaceRange(includePath, pair.getStart(), pair.getEnd());
133 else if (promptResult == FindManager.PromptResult.ALL) {
134 doReplaceRange(includePath, pair.getStart(), pair.getEnd());
135 replaceAll = true;
137 else {
138 LOG.error("Unknown return status");
141 else {
142 doReplaceRange(includePath, pair.getStart(), pair.getEnd());
146 }, RefactoringBundle.message("remove.duplicates.command"), null);
151 private static void highlightInEditor(final Project project, final IncludeDuplicate pair, final Editor editor) {
152 final HighlightManager highlightManager = HighlightManager.getInstance(project);
153 EditorColorsManager colorsManager = EditorColorsManager.getInstance();
154 TextAttributes attributes = colorsManager.getGlobalScheme().getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES);
155 final int startOffset = pair.getStart().getTextRange().getStartOffset();
156 final int endOffset = pair.getEnd().getTextRange().getEndOffset();
157 highlightManager.addRangeHighlight(editor, startOffset, endOffset, attributes, true, null);
158 final LogicalPosition logicalPosition = editor.offsetToLogicalPosition(startOffset);
159 editor.getScrollingModel().scrollTo(logicalPosition, ScrollType.MAKE_VISIBLE);
162 public void invoke(@NotNull Project project, @NotNull PsiElement[] elements, DataContext dataContext) {
165 @NotNull
166 protected Language getLanguageForExtract(PsiElement firstExtracted) {
167 return firstExtracted.getLanguage();
170 @Nullable
171 private static FileType getFileType(final Language language) {
172 final FileType[] fileTypes = FileTypeManager.getInstance().getRegisteredFileTypes();
173 for (FileType fileType : fileTypes) {
174 if (fileType instanceof LanguageFileType && language.equals(((LanguageFileType)fileType).getLanguage())) return fileType;
177 return null;
180 public void invoke(@NotNull final Project project, final Editor editor, final PsiFile file, DataContext dataContext) {
181 myIncludingFile = file;
182 if (!editor.getSelectionModel().hasSelection()) {
183 String message = RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message("no.selection"));
184 CommonRefactoringUtil.showErrorHint(project, editor, message, getRefactoringName(), HELP_ID);
185 return;
187 final int start = editor.getSelectionModel().getSelectionStart();
188 final int end = editor.getSelectionModel().getSelectionEnd();
190 final Pair<T, T> children = findPairToExtract(start, end);
191 if (children == null) {
192 String message = RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message("selection.does.not.form.a.fragment.for.extraction"));
193 CommonRefactoringUtil.showErrorHint(project, editor, message, getRefactoringName(), HELP_ID);
194 return;
197 if (!verifyChildRange(children.getFirst(), children.getSecond())) {
198 String message = RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message("cannot.extract.selected.elements.into.include.file"));
199 CommonRefactoringUtil.showErrorHint(project, editor, message, getRefactoringName(), HELP_ID);
200 return;
203 final FileType fileType = getFileType(getLanguageForExtract(children.getFirst()));
204 if (!(fileType instanceof LanguageFileType)) {
205 String message = RefactoringBundle.message("the.language.for.selected.elements.has.no.associated.file.type");
206 CommonRefactoringUtil.showErrorHint(project, editor, message, getRefactoringName(), HELP_ID);
207 return;
210 if (!CommonRefactoringUtil.checkReadOnlyStatus(project, file)) return;
212 ExtractIncludeDialog dialog = createDialog(file.getContainingDirectory(), getExtractExtension(fileType, children.first));
213 dialog.show();
214 if (dialog.getExitCode() == DialogWrapper.OK_EXIT_CODE) {
215 final PsiDirectory targetDirectory = dialog.getTargetDirectory();
216 LOG.assertTrue(targetDirectory != null);
217 final String targetfileName = dialog.getTargetFileName();
218 CommandProcessor.getInstance().executeCommand(project, new Runnable() {
219 public void run() {
220 ApplicationManager.getApplication().runWriteAction(new Runnable() {
221 public void run() {
222 try {
223 final List<IncludeDuplicate<T>> duplicates = new ArrayList<IncludeDuplicate<T>>();
224 final T first = children.getFirst();
225 final T second = children.getSecond();
226 PsiEquivalenceUtil.findChildRangeDuplicates(first, second, file, new PairConsumer<PsiElement, PsiElement>() {
227 public void consume(final PsiElement start, final PsiElement end) {
228 duplicates.add(new IncludeDuplicate<T>((T) start, (T) end));
231 final String includePath = processPrimaryFragment(first, second, targetDirectory, targetfileName, file);
233 ApplicationManager.getApplication().invokeLater(new Runnable() {
234 public void run() {
235 replaceDuplicates(includePath, duplicates, editor, project);
239 catch (IncorrectOperationException e) {
240 CommonRefactoringUtil.showErrorMessage(getRefactoringName(), e.getMessage(), null, project);
243 editor.getSelectionModel().removeSelection();
247 }, getRefactoringName(), null);
252 protected ExtractIncludeDialog createDialog(final PsiDirectory containingDirectory, final String extractExtension) {
253 return new ExtractIncludeDialog(containingDirectory, extractExtension);
256 @Nullable
257 protected abstract Pair<T, T> findPairToExtract(int start, int end);
259 @NonNls
260 protected String getExtractExtension(final FileType extractFileType, final T first) {
261 return extractFileType.getDefaultExtension();
264 public boolean isValidRange(final T firstToExtract, final T lastToExtract) {
265 return verifyChildRange(firstToExtract, lastToExtract);
268 public String processPrimaryFragment(final T firstToExtract,
269 final T lastToExtract,
270 final PsiDirectory targetDirectory,
271 final String targetfileName,
272 final PsiFile srcFile) throws IncorrectOperationException {
273 final String includePath = doExtract(targetDirectory, targetfileName, firstToExtract, lastToExtract,
274 srcFile.getLanguage());
276 doReplaceRange(includePath, firstToExtract, lastToExtract);
277 return includePath;
280 public String getActionTitle() {
281 return "Extract Include File...";
284 protected String getRefactoringName() {
285 return REFACTORING_NAME;