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
;
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
);
75 return myStart
.getElement();
79 return myEnd
.getElement();
84 protected abstract void doReplaceRange(final String includePath
, final T first
, final T last
);
87 protected String
doExtract(final PsiDirectory targetDirectory
,
88 final String targetfileName
,
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!");
105 protected abstract boolean verifyChildRange (final T first
, final T last
);
107 private void replaceDuplicates(final String includePath
,
108 final List
<IncludeDuplicate
<T
>> duplicates
,
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() {
118 boolean replaceAll
= false;
119 for (IncludeDuplicate
<T
> pair
: duplicates
) {
122 highlightInEditor(project
, pair
, editor
);
124 ReplacePromptDialog promptDialog
= new ReplacePromptDialog(false, RefactoringBundle
.message("replace.fragment"), project
);
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());
138 LOG
.error("Unknown return status");
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
) {
166 protected Language
getLanguageForExtract(PsiElement firstExtracted
) {
167 return firstExtracted
.getLanguage();
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
;
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
);
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
);
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
);
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
);
210 if (!CommonRefactoringUtil
.checkReadOnlyStatus(project
, file
)) return;
212 ExtractIncludeDialog dialog
= createDialog(file
.getContainingDirectory(), getExtractExtension(fileType
, children
.first
));
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() {
220 ApplicationManager
.getApplication().runWriteAction(new Runnable() {
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() {
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
);
257 protected abstract Pair
<T
, T
> findPairToExtract(int start
, int end
);
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
);
280 public String
getActionTitle() {
281 return "Extract Include File...";
284 protected String
getRefactoringName() {
285 return REFACTORING_NAME
;