IDEA-78831 Remove unnecessary semicolons intention

This commit is contained in:
Maxim.Medvedev
2011-12-17 19:12:46 +04:00
parent 53522e6c31
commit 28c93ce970
9 changed files with 766 additions and 2 deletions
@@ -59,7 +59,7 @@ public class CommonRefactoringUtil {
}
}
public static void showErrorHint(Project project, @Nullable Editor editor, String message, String title, String helpId) {
public static void showErrorHint(Project project, @Nullable Editor editor, String message, String title, @Nullable @NonNls String helpId) {
if (ApplicationManager.getApplication().isUnitTestMode()) throw new RefactoringErrorHintException(message);
if (editor == null) {
@@ -0,0 +1,2 @@
print 2<spot>;</spot>
print 3
@@ -0,0 +1,5 @@
<html>
<body>
<span style="font-family: verdana,serif; font-size: small;">Removes unnecessary semicolons from selected text or a semicolon under caret if it is unnecessary.</span>
</body>
</html>
+5
View File
@@ -914,6 +914,11 @@
<categoryKey>intention.category.groovy/intention.category.groovy.style</categoryKey>
<className>org.jetbrains.plugins.groovy.intentions.style.JavaStylePropertiesInvocationIntention</className>
</intentionAction>
<intentionAction>
<bundleName>org.jetbrains.plugins.groovy.intentions.GroovyIntentionsBundle</bundleName>
<categoryKey>intention.category.groovy/intention.category.groovy.style</categoryKey>
<className>org.jetbrains.plugins.groovy.intentions.style.RemoveUnnecessarySemicolonsIntention</className>
</intentionAction>
<intentionAction>
<bundleName>org.jetbrains.plugins.groovy.intentions.GroovyIntentionsBundle</bundleName>
<categoryKey>intention.category.groovy/intention.category.groovy.style</categoryKey>
@@ -138,3 +138,6 @@ convert.to.geese.braces.intention.family.name=Format closing closure braces in o
convert.from.geese.braces.intention.name=Format braces in separate lines
convert.from.geese.braces.intention.family.name=Format braces in separate lines
remove.unnecessary.semicolons.name=Remove unnecessary semicolons
remove.unnecessary.semicolons.family.name=Remove unnecessary semicolons from selected text
no.unnecessary.semicolons.found=No unnecessary semicolons found
@@ -0,0 +1,198 @@
/*
* Copyright 2000-2011 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 org.jetbrains.plugins.groovy.intentions.style;
import com.intellij.codeInsight.intention.IntentionAction;
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.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiRecursiveElementVisitor;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.refactoring.util.CommonRefactoringUtil;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.intentions.GroovyIntentionsBundle;
import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes;
import org.jetbrains.plugins.groovy.lang.psi.GroovyFile;
import org.jetbrains.plugins.groovy.lang.psi.GroovyFileBase;
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrStatement;
import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil;
import java.util.ArrayList;
/**
* @author Max Medvedev
*/
public class RemoveUnnecessarySemicolonsIntention implements IntentionAction {
@NotNull
@Override
public String getText() {
return GroovyIntentionsBundle.message("remove.unnecessary.semicolons.name");
}
@NotNull
@Override
public String getFamilyName() {
return GroovyIntentionsBundle.message("remove.unnecessary.semicolons.family.name");
}
@Override
public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
if (!(file instanceof GroovyFileBase) || editor.getSelectionModel().hasBlockSelection()) {
return false;
}
if (editor.getSelectionModel().hasSelection()) return true;
int offset = editor.getCaretModel().getOffset();
if (offset >= editor.getDocument().getTextLength()) offset = editor.getDocument().getTextLength() - 1;
final PsiElement element = file.findElementAt(offset);
if (element == null) return false;
if (element.getNode().getElementType() == GroovyTokenTypes.mSEMI) return true;
final PsiElement next = PsiTreeUtil.nextLeaf(element);
if (next != null && next.getNode().getElementType() == GroovyTokenTypes.mSEMI) return true;
final PsiElement prev = PsiTreeUtil.prevLeaf(element);
if (prev != null && prev.getNode().getElementType() == GroovyTokenTypes.mSEMI) return true;
return false;
}
@Override
public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
final SelectionModel selectionModel = editor.getSelectionModel();
if (selectionModel.hasBlockSelection()) return;
Document document = editor.getDocument();
if (selectionModel.hasSelection()) {
final int start = selectionModel.getSelectionStart();
final int end = selectionModel.getSelectionEnd();
final TextRange range = new TextRange(start, end);
final ArrayList<PsiElement> colons = new ArrayList<PsiElement>();
file.accept(new PsiRecursiveElementVisitor() {
@Override
public void visitElement(PsiElement element) {
if (!range.intersects(element.getTextRange())) return;
final IElementType elementType = element.getNode().getElementType();
if (elementType == GroovyTokenTypes.mSEMI) {
colons.add(element);
}
else {
super.visitElement(element);
}
}
});
boolean removed = false;
for (PsiElement colon : colons) {
final boolean b = checkAndRemove(project, colon, document);
removed = removed || b;
}
if (!removed) {
CommonRefactoringUtil.showErrorHint(project, editor, GroovyIntentionsBundle.message("no.unnecessary.semicolons.found"),
GroovyIntentionsBundle.message("remove.unnecessary.semicolons.name"), null);
}
}
else {
int offset = editor.getCaretModel().getOffset();
if (offset >= document.getTextLength()) offset = document.getTextLength() - 1;
final PsiElement element = file.findElementAt(offset);
if (element == null) return;
if (checkAndRemove(project, element, document)) return;
if (checkAndRemove(project, PsiTreeUtil.nextLeaf(element), document)) return;
if (checkAndRemove(project, PsiTreeUtil.prevLeaf(element), document)) return;
CommonRefactoringUtil.showErrorHint(project, editor, GroovyIntentionsBundle.message("no.unnecessary.semicolons.found"),
GroovyIntentionsBundle.message("remove.unnecessary.semicolons.name"), null);
}
}
private static boolean checkAndRemove(Project project, @Nullable PsiElement element, Document document) {
if (element != null && element.getNode().getElementType() == GroovyTokenTypes.mSEMI) {
if (isColonUnnecessary(element, document.getText(), project)) {
element.delete();
return true;
}
}
return false;
}
private static boolean isColonUnnecessary(PsiElement colon, String text, Project project) {
final GrStatement prev = getPreviousStatement(colon);
final GrStatement next = getNextStatement(colon);
if (prev == null || next == null) return true;
final int startOffset = prev.getTextRange().getStartOffset();
final int endOffset = next.getTextRange().getEndOffset();
final int offset = colon.getTextRange().getStartOffset();
final String statementWithoutColon = text.substring(startOffset, offset) + text.substring(offset + 1, endOffset);
final GroovyFile file = GroovyPsiElementFactory.getInstance(project).createGroovyFile(statementWithoutColon, false, null);
final GrStatement[] statements = file.getStatements();
if (statements.length != 2) return false;
return checkEquality(prev, statements[0]) && checkEquality(next, statements[1]);
}
private static boolean checkEquality(PsiElement l, PsiElement r) {
if (!l.getText().equals(r.getText())) return false;
if (l.getNode().getElementType() != r.getNode().getElementType()) return false;
final PsiElement[] lChildren = l.getChildren();
final PsiElement[] rChildren = r.getChildren();
if (lChildren.length != rChildren.length) return false;
for (int i = 0; i < rChildren.length; i++) {
if (!checkEquality(lChildren[i], rChildren[i])) return false;
}
return true;
}
@Nullable
private static GrStatement getPreviousStatement(PsiElement colon) {
final PsiElement prev = PsiUtil.skipWhitespaces(colon.getPrevSibling(), false);
if (prev instanceof GrStatement) return (GrStatement)prev;
return null;
}
@Nullable
private static GrStatement getNextStatement(PsiElement colon) {
final PsiElement next = PsiUtil.skipWhitespaces(colon.getNextSibling(), true);
if (next instanceof GrStatement) return (GrStatement)next;
return null;
}
@Override
public boolean startInWriteAction() {
return true;
}
}
@@ -188,7 +188,7 @@ public abstract class GroovyPsiElementFactory implements JVMElementFactory {
public abstract GrAnnotation createAnnotationFromText(String annoText);
public abstract GroovyFile createGroovyFile(String text, boolean isPhysical, PsiElement context);
public abstract GroovyFile createGroovyFile(String text, boolean isPhysical, @Nullable PsiElement context);
public abstract GrMethod createMethodFromText(String modifier, String name, String type, String[] paramTypes, PsiElement context);
@@ -0,0 +1,549 @@
/*
* Copyright 2000-2011 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 org.jetbrains.plugins.groovy.intentions
import com.intellij.refactoring.util.CommonRefactoringUtil.RefactoringErrorHintException
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase
import org.jetbrains.plugins.groovy.util.TestUtils
/**
* @author Max Medvedev
*/
class RemoveUnnecessarySemicolonTest extends LightCodeInsightFixtureTestCase {
private static final String hint = GroovyIntentionsBundle.message('remove.unnecessary.semicolons.name');
@Override
protected String getBasePath() {
return "${TestUtils.testDataPath}intentions/removeUnnecessaryBraces/";
}
void testSimpleCase1() {
doTest('print 2;<caret>\nprint 3', 'print 2\nprint 3')
}
void testSimpleCase2() {
doTest('print 2<caret>;\nprint 3', 'print 2\nprint 3')
}
void testSimpleCase3() {
doTest('prin<selection>t 2;\np</selection>rint 3', 'prin<selection>t 2\np</selection>rint 3')
}
void testDontRemoveSimple() {
doTest('print 2<caret>; print 3')
}
void testClosure() {
doTest('print( a )<caret>;\n{->print 3}')
}
private doTest(String before, String after) {
myFixture.configureByText('a.groovy', before)
def intentions = myFixture.filterAvailableIntentions(hint)
assertEquals(1, intentions.size())
myFixture.launchAction(intentions[0])
myFixture.checkResult(after)
}
private doTest(String text) {
try {
doTest(text, text)
}
catch (RefactoringErrorHintException e) {
return;
}
fail('RefactoringErrorHintException should be thrown')
}
private doFalseTest(String before) {
myFixture.configureByText('a.groovy', before)
def intentions = myFixture.filterAvailableIntentions(hint)
assertEmpty(intentions)
}
void testBig() {
doTest('''\
<selection>/*
* Copyright 2000-2011 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.util;
import com.intellij.codeInsight.hint.HintManager;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileTypes.FileTypeManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.JarFileSystem;
import com.intellij.openapi.vfs.ReadonlyStatusHandler;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.util.containers.ContainerUtil;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
/**
* @author ven
*/
public class CommonRefactoringUtil {
private CommonRefactoringUtil() {}
public static void showErrorMessage(String title, String message, @Nullable @NonNls String helpId, @NotNull Project project) {
if (ApplicationManager.getApplication().isUnitTestMode()) throw new RuntimeException(message);
RefactoringMessageDialog dialog = new RefactoringMessageDialog(title, message, helpId, "OptionPane.errorIcon", false, project);
dialog.show();
}
/**
* Fatal refactoring problem during unit test run. Corresponds to message of modal dialog shown during user driven refactoring.
*/
public static class RefactoringErrorHintException extends RuntimeException {
public RefactoringErrorHintException(String message) {
super(message);
}
}
public static void showErrorHint(Project project, @Nullable Editor editor, String message, String title, @Nullable @NonNls String helpId) {
if (ApplicationManager.getApplication().isUnitTestMode()) throw new RefactoringErrorHintException(message);
if (editor == null) {
showErrorMessage(title, message, helpId, project);
}
else {
HintManager.getInstance().showErrorHint(editor, message);
}
}
@NonNls
public static String htmlEmphasize(String text) {
return "<b><code>" + text + "</code></b>";
}
public static boolean checkReadOnlyStatus(@NotNull PsiElement element) {
final VirtualFile file = element.getContainingFile().getVirtualFile();
return file != null && !ReadonlyStatusHandler.getInstance(element.getProject()).ensureFilesWritable(file).hasReadonlyFiles();
}
public static boolean checkReadOnlyStatus(@NotNull Project project, @NotNull PsiElement element) {
return checkReadOnlyStatus(element, project, RefactoringBundle.message("refactoring.cannot.be.performed"));
}
public static boolean checkReadOnlyStatus(@NotNull Project project, @NotNull PsiElement... elements) {
return checkReadOnlyStatus(Arrays.asList(elements), project, RefactoringBundle.message("refactoring.cannot.be.performed"), false, true);
}
public static boolean checkReadOnlyStatus(@NotNull PsiElement element, @NotNull Project project, String messagePrefix) {
return element.isWritable() ||
checkReadOnlyStatus(Collections.singleton(element), project, messagePrefix, false, true);
}
public static boolean checkReadOnlyStatusRecursively(@NotNull Project project, @NotNull Collection<? extends PsiElement> elements) {
return checkReadOnlyStatus(elements, project, RefactoringBundle.message("refactoring.cannot.be.performed"), true, false);
}
public static boolean checkReadOnlyStatusRecursively(@NotNull Project project, @NotNull Collection<? extends PsiElement> elements, boolean notifyOnFail) {
return checkReadOnlyStatus(elements, project, RefactoringBundle.message("refactoring.cannot.be.performed"), true, notifyOnFail);
}
private static boolean checkReadOnlyStatus(@NotNull Collection<? extends PsiElement> elements,
@NotNull Project project,
final String messagePrefix,
boolean recursively,
final boolean notifyOnFail) {
final Collection<VirtualFile> readonly = new THashSet<VirtualFile>(); // not writable, but could be checked out
final Collection<VirtualFile> failed = new THashSet<VirtualFile>(); // those located in jars
boolean seenNonWritablePsiFilesWithoutVirtualFile = false;
for (PsiElement element : elements) {
if (element instanceof PsiDirectory) {
final PsiDirectory dir = (PsiDirectory)element;
final VirtualFile vFile = dir.getVirtualFile();
if (vFile.getFileSystem() instanceof JarFileSystem) {
failed.add(vFile);
}
else {
if (recursively) {
addVirtualFiles(vFile, readonly);
}
else {
readonly.add(vFile);
}
}
}
else if (element instanceof PsiDirectoryContainer) {
final PsiDirectory[] directories = ((PsiDirectoryContainer)element).getDirectories();
for (PsiDirectory directory : directories) {
VirtualFile virtualFile = directory.getVirtualFile();
if (recursively) {
if (virtualFile.getFileSystem() instanceof JarFileSystem) {
failed.add(virtualFile);
}
else {
addVirtualFiles(virtualFile, readonly);
}
}
else {
if (virtualFile.getFileSystem() instanceof JarFileSystem) {
failed.add(virtualFile);
}
else {
readonly.add(virtualFile);
}
}
}
}
else if (element instanceof PsiCompiledElement) {
final PsiFile file = element.getContainingFile();
if (file != null) {
failed.add(file.getVirtualFile());
}
}
else {
PsiFile file = element.getContainingFile();
if (file == null) {
if (!element.isWritable()) {
seenNonWritablePsiFilesWithoutVirtualFile = true;
}
}
else {
final VirtualFile vFile = file.getVirtualFile();
if (vFile != null) {
readonly.add(vFile);
}
else {
if (!element.isWritable()) {
seenNonWritablePsiFilesWithoutVirtualFile = true;
}
}
}
}
}
final VirtualFile[] files = VfsUtil.toVirtualFileArray(readonly);
final ReadonlyStatusHandler.OperationStatus status = ReadonlyStatusHandler.getInstance(project).ensureFilesWritable(files);
ContainerUtil.addAll(failed, status.getReadonlyFiles());
if (notifyOnFail && (!failed.isEmpty() || seenNonWritablePsiFilesWithoutVirtualFile && readonly.isEmpty())) {
StringBuilder message = new StringBuilder(messagePrefix);
message.append('\\n');
int i = 0;
for (VirtualFile virtualFile : failed) {
final String presentableUrl = virtualFile.getPresentableUrl();
final String subj = virtualFile.isDirectory()
? RefactoringBundle.message("directory.description", presentableUrl)
: RefactoringBundle.message("file.description", presentableUrl);
if (virtualFile.getFileSystem() instanceof JarFileSystem) {
message.append(RefactoringBundle.message("0.is.located.in.a.jar.file", subj));
}
else {
message.append(RefactoringBundle.message("0.is.read.only", subj));
}
if (i++ > 20) {
message.append("...\\n");
break;
}
}
showErrorMessage(RefactoringBundle.message("error.title"), message.toString(), null, project);
return false;
}
return failed.isEmpty();
}
private static void addVirtualFiles(final VirtualFile vFile, final Collection<VirtualFile> list) {
if (!vFile.isWritable()) {
list.add(vFile);
}
if (!vFile.isSymLink()) {
final VirtualFile[] children = vFile.getChildren();
if (children != null) {
final FileTypeManager fileTypeManager = FileTypeManager.getInstance();
for (VirtualFile virtualFile : children) {
if (fileTypeManager.isFileIgnored(virtualFile)) continue;
addVirtualFiles(virtualFile, list);
}
}
}
}
public static String capitalize(String text) {
return Character.toUpperCase(text.charAt(0)) + text.substring(1);
}
public static boolean isAncestor(final PsiElement resolved, final Collection<? extends PsiElement> scopes) {
for (final PsiElement scope : scopes) {
if (PsiTreeUtil.isAncestor(scope, resolved, false)) return true;
}
return false;
}
}
</selection>''', '''\
<selection>/*
* Copyright 2000-2011 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.util
import com.intellij.codeInsight.hint.HintManager
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.fileTypes.FileTypeManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.JarFileSystem
import com.intellij.openapi.vfs.ReadonlyStatusHandler
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.*
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.refactoring.RefactoringBundle
import com.intellij.util.containers.ContainerUtil
import gnu.trove.THashSet
import org.jetbrains.annotations.NonNls
import org.jetbrains.annotations.NotNull
import org.jetbrains.annotations.Nullable
import java.util.Arrays
import java.util.Collection
import java.util.Collections
/**
* @author ven
*/
public class CommonRefactoringUtil {
private CommonRefactoringUtil() {}
public static void showErrorMessage(String title, String message, @Nullable @NonNls String helpId, @NotNull Project project) {
if (ApplicationManager.getApplication().isUnitTestMode()) throw new RuntimeException(message)
RefactoringMessageDialog dialog = new RefactoringMessageDialog(title, message, helpId, "OptionPane.errorIcon", false, project)
dialog.show()
}
/**
* Fatal refactoring problem during unit test run. Corresponds to message of modal dialog shown during user driven refactoring.
*/
public static class RefactoringErrorHintException extends RuntimeException {
public RefactoringErrorHintException(String message) {
super(message)
}
}
public static void showErrorHint(Project project, @Nullable Editor editor, String message, String title, @Nullable @NonNls String helpId) {
if (ApplicationManager.getApplication().isUnitTestMode()) throw new RefactoringErrorHintException(message)
if (editor == null) {
showErrorMessage(title, message, helpId, project)
}
else {
HintManager.getInstance().showErrorHint(editor, message)
}
}
@NonNls
public static String htmlEmphasize(String text) {
return "<b><code>" + text + "</code></b>"
}
public static boolean checkReadOnlyStatus(@NotNull PsiElement element) {
final VirtualFile file = element.getContainingFile().getVirtualFile()
return file != null && !ReadonlyStatusHandler.getInstance(element.getProject()).ensureFilesWritable(file).hasReadonlyFiles()
}
public static boolean checkReadOnlyStatus(@NotNull Project project, @NotNull PsiElement element) {
return checkReadOnlyStatus(element, project, RefactoringBundle.message("refactoring.cannot.be.performed"))
}
public static boolean checkReadOnlyStatus(@NotNull Project project, @NotNull PsiElement... elements) {
return checkReadOnlyStatus(Arrays.asList(elements), project, RefactoringBundle.message("refactoring.cannot.be.performed"), false, true)
}
public static boolean checkReadOnlyStatus(@NotNull PsiElement element, @NotNull Project project, String messagePrefix) {
return element.isWritable() ||
checkReadOnlyStatus(Collections.singleton(element), project, messagePrefix, false, true)
}
public static boolean checkReadOnlyStatusRecursively(@NotNull Project project, @NotNull Collection<? extends PsiElement> elements) {
return checkReadOnlyStatus(elements, project, RefactoringBundle.message("refactoring.cannot.be.performed"), true, false)
}
public static boolean checkReadOnlyStatusRecursively(@NotNull Project project, @NotNull Collection<? extends PsiElement> elements, boolean notifyOnFail) {
return checkReadOnlyStatus(elements, project, RefactoringBundle.message("refactoring.cannot.be.performed"), true, notifyOnFail)
}
private static boolean checkReadOnlyStatus(@NotNull Collection<? extends PsiElement> elements,
@NotNull Project project,
final String messagePrefix,
boolean recursively,
final boolean notifyOnFail) {
final Collection<VirtualFile> readonly = new THashSet<VirtualFile>() // not writable, but could be checked out
final Collection<VirtualFile> failed = new THashSet<VirtualFile>() // those located in jars
boolean seenNonWritablePsiFilesWithoutVirtualFile = false
for (PsiElement element : elements) {
if (element instanceof PsiDirectory) {
final PsiDirectory dir = (PsiDirectory)element
final VirtualFile vFile = dir.getVirtualFile()
if (vFile.getFileSystem() instanceof JarFileSystem) {
failed.add(vFile)
}
else {
if (recursively) {
addVirtualFiles(vFile, readonly)
}
else {
readonly.add(vFile)
}
}
}
else if (element instanceof PsiDirectoryContainer) {
final PsiDirectory[] directories = ((PsiDirectoryContainer)element).getDirectories()
for (PsiDirectory directory : directories) {
VirtualFile virtualFile = directory.getVirtualFile()
if (recursively) {
if (virtualFile.getFileSystem() instanceof JarFileSystem) {
failed.add(virtualFile)
}
else {
addVirtualFiles(virtualFile, readonly)
}
}
else {
if (virtualFile.getFileSystem() instanceof JarFileSystem) {
failed.add(virtualFile)
}
else {
readonly.add(virtualFile)
}
}
}
}
else if (element instanceof PsiCompiledElement) {
final PsiFile file = element.getContainingFile()
if (file != null) {
failed.add(file.getVirtualFile())
}
}
else {
PsiFile file = element.getContainingFile()
if (file == null) {
if (!element.isWritable()) {
seenNonWritablePsiFilesWithoutVirtualFile = true
}
}
else {
final VirtualFile vFile = file.getVirtualFile()
if (vFile != null) {
readonly.add(vFile)
}
else {
if (!element.isWritable()) {
seenNonWritablePsiFilesWithoutVirtualFile = true
}
}
}
}
}
final VirtualFile[] files = VfsUtil.toVirtualFileArray(readonly)
final ReadonlyStatusHandler.OperationStatus status = ReadonlyStatusHandler.getInstance(project).ensureFilesWritable(files)
ContainerUtil.addAll(failed, status.getReadonlyFiles())
if (notifyOnFail && (!failed.isEmpty() || seenNonWritablePsiFilesWithoutVirtualFile && readonly.isEmpty())) {
StringBuilder message = new StringBuilder(messagePrefix)
message.append('\\n')
int i = 0
for (VirtualFile virtualFile : failed) {
final String presentableUrl = virtualFile.getPresentableUrl()
final String subj = virtualFile.isDirectory()
? RefactoringBundle.message("directory.description", presentableUrl)
: RefactoringBundle.message("file.description", presentableUrl)
if (virtualFile.getFileSystem() instanceof JarFileSystem) {
message.append(RefactoringBundle.message("0.is.located.in.a.jar.file", subj))
}
else {
message.append(RefactoringBundle.message("0.is.read.only", subj))
}
if (i++ > 20) {
message.append("...\\n")
break
}
}
showErrorMessage(RefactoringBundle.message("error.title"), message.toString(), null, project)
return false
}
return failed.isEmpty()
}
private static void addVirtualFiles(final VirtualFile vFile, final Collection<VirtualFile> list) {
if (!vFile.isWritable()) {
list.add(vFile)
}
if (!vFile.isSymLink()) {
final VirtualFile[] children = vFile.getChildren()
if (children != null) {
final FileTypeManager fileTypeManager = FileTypeManager.getInstance()
for (VirtualFile virtualFile : children) {
if (fileTypeManager.isFileIgnored(virtualFile)) continue
addVirtualFiles(virtualFile, list)
}
}
}
}
public static String capitalize(String text) {
return Character.toUpperCase(text.charAt(0)) + text.substring(1)
}
public static boolean isAncestor(final PsiElement resolved, final Collection<? extends PsiElement> scopes) {
for (final PsiElement scope : scopes) {
if (PsiTreeUtil.isAncestor(scope, resolved, false)) return true
}
return false
}
}
</selection>''')
}
}