diff --git a/java/java-impl/src/com/intellij/lang/java/JavaImportOptimizer.java b/java/java-impl/src/com/intellij/lang/java/JavaImportOptimizer.java index bd2d1a9a29fc..eddd536d19fb 100644 --- a/java/java-impl/src/com/intellij/lang/java/JavaImportOptimizer.java +++ b/java/java-impl/src/com/intellij/lang/java/JavaImportOptimizer.java @@ -28,6 +28,7 @@ import com.intellij.psi.PsiJavaFile; import com.intellij.psi.codeStyle.JavaCodeStyleManager; import com.intellij.util.IncorrectOperationException; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; /** * @author max @@ -44,7 +45,10 @@ public class JavaImportOptimizer implements ImportOptimizer { Project project = file.getProject(); final PsiImportList newImportList = JavaCodeStyleManager.getInstance(project).prepareOptimizeImportsResult((PsiJavaFile)file); if (newImportList == null) return EmptyRunnable.getInstance(); - return new Runnable() { + + return new CollectingInfoRunnable() { + private int myImportListLengthDiff = 0; + @Override public void run() { try { @@ -55,12 +59,23 @@ public class JavaImportOptimizer implements ImportOptimizer { } final PsiImportList oldImportList = ((PsiJavaFile)file).getImportList(); assert oldImportList != null; + int importsBefore = oldImportList.getAllImportStatements().length; oldImportList.replace(newImportList); + myImportListLengthDiff = importsBefore - newImportList.getAllImportStatements().length; } catch (IncorrectOperationException e) { LOG.error(e); } } + + @Nullable + @Override + public String getUserNotificationInfo() { + if (myImportListLengthDiff > 0) { + return "removed " + myImportListLengthDiff + " import" + (myImportListLengthDiff > 1 ? "s" : ""); + } + return null; + } }; } diff --git a/java/java-tests/testData/actions/reformatFileInEditor/selectedTextAndOptimizeImports_after.java b/java/java-tests/testData/actions/reformatFileInEditor/selectedTextAndOptimizeImports_after.java new file mode 100644 index 000000000000..edb36d680a69 --- /dev/null +++ b/java/java-tests/testData/actions/reformatFileInEditor/selectedTextAndOptimizeImports_after.java @@ -0,0 +1,14 @@ +import java.util.ArrayList; +import java.util.List; + + +public class Test { + + public static void main(String[] args) { + List list = new ArrayList(); + int a = 3; + +int b = 3; + } + +} diff --git a/java/java-tests/testData/actions/reformatFileInEditor/selectedTextAndOptimizeImports_before.java b/java/java-tests/testData/actions/reformatFileInEditor/selectedTextAndOptimizeImports_before.java new file mode 100644 index 000000000000..2256d473f765 --- /dev/null +++ b/java/java-tests/testData/actions/reformatFileInEditor/selectedTextAndOptimizeImports_before.java @@ -0,0 +1,17 @@ +import java.lang.String; +import java.util.List; +import java.util.HashMap; +import java.util.ArrayList; +import java.util.HashSet; + + +public class Test { + + public static void main(String[] args) { +List list = new ArrayList(); +int a = 3; + +int b = 3; + } + +} diff --git a/java/java-tests/testData/actions/reformatFileInEditor/selectionReformat_after.java b/java/java-tests/testData/actions/reformatFileInEditor/selectionReformat_after.java new file mode 100644 index 000000000000..7e145c3004f0 --- /dev/null +++ b/java/java-tests/testData/actions/reformatFileInEditor/selectionReformat_after.java @@ -0,0 +1,11 @@ +public class Test { + + int a = 2; + int b = 3; + + + public void run() { + int myFirst = 12; + } + +} \ No newline at end of file diff --git a/java/java-tests/testData/actions/reformatFileInEditor/selectionReformat_before.java b/java/java-tests/testData/actions/reformatFileInEditor/selectionReformat_before.java new file mode 100644 index 000000000000..d753fc6562c6 --- /dev/null +++ b/java/java-tests/testData/actions/reformatFileInEditor/selectionReformat_before.java @@ -0,0 +1,11 @@ +public class Test { + + int a = 2; + int b = 3; + + + public void run() { + int myFirst = 12; + } + +} \ No newline at end of file diff --git a/java/java-tests/testData/actions/reformatFileInEditor/wholeFileReformatAndOptimize_after.java b/java/java-tests/testData/actions/reformatFileInEditor/wholeFileReformatAndOptimize_after.java new file mode 100644 index 000000000000..b3c340724e6b --- /dev/null +++ b/java/java-tests/testData/actions/reformatFileInEditor/wholeFileReformatAndOptimize_after.java @@ -0,0 +1,11 @@ +import java.util.ArrayList; +import java.util.List; + + +public class Test { + + public static void main(String[] args) { + List list = new ArrayList(); + } + +} diff --git a/java/java-tests/testData/actions/reformatFileInEditor/wholeFileReformatAndOptimize_before.java b/java/java-tests/testData/actions/reformatFileInEditor/wholeFileReformatAndOptimize_before.java new file mode 100644 index 000000000000..a996d55202f7 --- /dev/null +++ b/java/java-tests/testData/actions/reformatFileInEditor/wholeFileReformatAndOptimize_before.java @@ -0,0 +1,14 @@ +import java.lang.String; +import java.util.List; +import java.util.HashMap; +import java.util.ArrayList; +import java.util.HashSet; + + +public class Test { + + public static void main(String[] args) { +List list = new ArrayList(); + } + +} diff --git a/java/java-tests/testData/actions/reformatFileInEditor/wholeFileReformat_after.java b/java/java-tests/testData/actions/reformatFileInEditor/wholeFileReformat_after.java new file mode 100644 index 000000000000..05460c51a74b --- /dev/null +++ b/java/java-tests/testData/actions/reformatFileInEditor/wholeFileReformat_after.java @@ -0,0 +1,10 @@ +public class Test { + + public int a = 3; + + + public void test() { + int c = 3; + } + +} \ No newline at end of file diff --git a/java/java-tests/testData/actions/reformatFileInEditor/wholeFileReformat_before.java b/java/java-tests/testData/actions/reformatFileInEditor/wholeFileReformat_before.java new file mode 100644 index 000000000000..e8d0ad4e5260 --- /dev/null +++ b/java/java-tests/testData/actions/reformatFileInEditor/wholeFileReformat_before.java @@ -0,0 +1,10 @@ +public class Test { + + public int a = 3; + + + public void test() { + int c = 3; + } + +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/actions/AbstractLayoutCodeProcessorTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/actions/AbstractLayoutCodeProcessorTest.java index 366fd626baaf..b780665d1c63 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/actions/AbstractLayoutCodeProcessorTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/actions/AbstractLayoutCodeProcessorTest.java @@ -316,14 +316,9 @@ class AdditionalEventInfo { } } -class MockReformatFileSettings implements LayoutCodeOptions { - private boolean myProcessWholeFile; - private boolean myProcessDirectories; - private boolean myRearrange; - private boolean myIncludeSubdirs; +class MockReformatFileSettings implements ReformatFilesOptions { private boolean myOptimizeImports; - private boolean myProcessOnlyChangedText; - private boolean myIsOK = true; + private boolean myIncludeSubdirs; @Nullable @Override @@ -338,33 +333,13 @@ class MockReformatFileSettings implements LayoutCodeOptions { } @Override - public boolean isProcessWholeFile() { - return myProcessWholeFile; - } - - MockReformatFileSettings setProcessWholeFile(boolean processWholeFile) { - myProcessWholeFile = processWholeFile; - return this; + public TextRangeType getTextRangeType() { + return TextRangeType.WHOLE_FILE; } @Override - public boolean isProcessDirectory() { - return myProcessDirectories; - } - - MockReformatFileSettings setProcessDirectory(boolean processDirectories) { - myProcessDirectories = processDirectories; - return this; - } - - @Override - public boolean isRearrangeEntries() { - return myRearrange; - } - - @Override - public boolean isIncludeSubdirectories() { - return myIncludeSubdirs; + public boolean isRearrangeCode() { + return false; } @Override @@ -378,23 +353,6 @@ class MockReformatFileSettings implements LayoutCodeOptions { return this; } - @Override - public boolean isProcessOnlyChangedText() { - return myProcessOnlyChangedText; - } - - @NotNull - MockReformatFileSettings setProcessOnlyChangedText(boolean processOnlyChangedText) { - myProcessOnlyChangedText = processOnlyChangedText; - return this; - } - - @NotNull - MockReformatFileSettings setRearrange(boolean rearrange) { - myRearrange = rearrange; - return this; - } - @NotNull MockReformatFileSettings setIncludeSubdirs(boolean includeSubdirs) { myIncludeSubdirs = includeSubdirs; diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/actions/ReformatCodeActionInEditorTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/actions/ReformatCodeActionInEditorTest.java index 0dd2bcebc27d..058d165c3ddf 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/actions/ReformatCodeActionInEditorTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/actions/ReformatCodeActionInEditorTest.java @@ -16,106 +16,90 @@ package com.intellij.codeInsight.actions; import com.intellij.JavaTestUtil; -import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vcs.FilePath; -import com.intellij.openapi.vcs.VcsException; -import com.intellij.openapi.vcs.changes.Change; -import com.intellij.openapi.vcs.changes.ContentRevision; -import com.intellij.openapi.vcs.history.VcsRevisionNumber; import com.intellij.psi.PsiFile; -import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; +import com.intellij.testFramework.fixtures.LightPlatformCodeInsightFixtureTestCase; -public class ReformatCodeActionInEditorTest extends LightCodeInsightFixtureTestCase { +import static com.intellij.codeInsight.actions.TextRangeType.*; + +public class ReformatCodeActionInEditorTest extends LightPlatformCodeInsightFixtureTestCase { @Override protected String getTestDataPath() { return JavaTestUtil.getJavaTestDataPath() + "/actions/reformatFileInEditor/"; } - public void doTest(@NotNull ReformatFilesOptions options) { - setOptions(options); - - String before = null; - if (options.isProcessOnlyChangedText()) { - myFixture.configureByFile(getTestName(true) + "_revision.java"); - PsiFile file = myFixture.getFile(); - Document document = myFixture.getDocument(file); - before = document.getText(); - } - - myFixture.configureByFile(getTestName(true) + "_before.java"); - - if (before != null) { - myFixture.getFile().putUserData(FormatChangedTextUtil.TEST_REVISION_CONTENT, before); - } - - final String actionId = IdeActions.ACTION_EDITOR_REFORMAT; - AnAction action = ActionManager.getInstance().getAction(actionId); - - AnActionEvent event = createEventFor(action, getProject(), myFixture.getEditor()); - - action.actionPerformed(event); - myFixture.checkResultByFile(getTestName(true) + "_after.java"); - } - @Override public void tearDown() throws Exception { myFixture.getFile().putUserData(FormatChangedTextUtil.TEST_REVISION_CONTENT, null); super.tearDown(); } - protected AnActionEvent createEventFor(@NotNull AnAction action, @NotNull final Project project, @NotNull final Editor editor) { - return new AnActionEvent(null, new DataContext() { - @Nullable - @Override - public Object getData(@NonNls String dataId) { - if (CommonDataKeys.PROJECT.is(dataId)) return project; - if (CommonDataKeys.EDITOR.is(dataId)) return editor; - return null; - } - }, "", action.getTemplatePresentation(), ActionManager.getInstance(), 0); + public void doTest(LayoutCodeOptions options) { + CharSequence revisionContent = null; + if (options.getTextRangeType() == VCS_CHANGED_TEXT) { + myFixture.configureByFile(getTestName(true) + "_revision.java"); + PsiFile file = myFixture.getFile(); + Document document = myFixture.getDocument(file); + revisionContent = document.getCharsSequence(); + } + + myFixture.configureByFile(getTestName(true) + "_before.java"); + if (revisionContent != null) { + myFixture.getFile().putUserData(FormatChangedTextUtil.TEST_REVISION_CONTENT, revisionContent); + } + + FileInEditorProcessor processor = new FileInEditorProcessor(myFixture.getFile(), myFixture.getEditor(), options); + + processor.processCode(); + myFixture.checkResultByFile(getTestName(true) + "_after.java"); } - protected void setOptions(ReformatFilesOptions options) { - ReformatCodeAction.setTestOptions(options); + public void testSelectionReformat() { + doTest(new ReformatCodeRunOptions(SELECTED_TEXT)); + } + + public void testWholeFileReformat() { + doTest(new ReformatCodeRunOptions(WHOLE_FILE)); + } + + public void testWholeFileReformatAndOptimize() { + doTest(new ReformatCodeRunOptions(WHOLE_FILE).setOptimizeImports(true)); + } + + public void testSelectedTextAndOptimizeImports() { + doTest(new ReformatCodeRunOptions(SELECTED_TEXT).setOptimizeImports(true)); } public void testFormatWholeFile() { - doTest(new MockReformatFileSettings().setProcessWholeFile(true)); + doTest(new ReformatCodeRunOptions(WHOLE_FILE)); } public void testFormatOptimizeWholeFile() { - doTest(new MockReformatFileSettings().setProcessWholeFile(true).setOptimizeImports(true)); + doTest(new ReformatCodeRunOptions(WHOLE_FILE).setOptimizeImports(true)); } public void testFormatOptimizeRearrangeWholeFile() { - doTest(new MockReformatFileSettings().setProcessWholeFile(true).setOptimizeImports(true).setRearrange(true)); + doTest(new ReformatCodeRunOptions(WHOLE_FILE).setOptimizeImports(true).setRearrangeCode(true)); } public void testFormatSelection() { - doTest(new MockReformatFileSettings().setProcessWholeFile(false)); + doTest(new ReformatCodeRunOptions(SELECTED_TEXT)); } public void testFormatRearrangeSelection() { - doTest(new MockReformatFileSettings().setProcessWholeFile(false).setRearrange(true)); + doTest(new ReformatCodeRunOptions(SELECTED_TEXT).setRearrangeCode(true)); } public void testFormatVcsChanges() { - doTest(new MockReformatFileSettings().setProcessOnlyChangedText(true)); + doTest(new ReformatCodeRunOptions(VCS_CHANGED_TEXT)); } public void testFormatOptimizeVcsChanges() { - doTest(new MockReformatFileSettings().setProcessOnlyChangedText(true).setOptimizeImports(true)); + doTest(new ReformatCodeRunOptions(VCS_CHANGED_TEXT).setOptimizeImports(true)); } public void testFormatOptimizeRearrangeVcsChanges() { - doTest(new MockReformatFileSettings().setProcessOnlyChangedText(true).setOptimizeImports(true).setRearrange(true)); + doTest(new ReformatCodeRunOptions(VCS_CHANGED_TEXT).setOptimizeImports(true).setRearrangeCode(true)); } - } diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/actions/ReformatCodeActionTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/actions/ReformatCodeActionTest.java index b0e4024ff076..09dff801a2f5 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/actions/ReformatCodeActionTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/actions/ReformatCodeActionTest.java @@ -35,35 +35,10 @@ public class ReformatCodeActionTest extends AbstractLayoutCodeProcessorTest { public void testReformatAndOptimizeMultipleFiles() throws IOException { List files = createTestFiles(getTempRootDirectory(), classNames); injectMockDialogFlags(new MockReformatFileSettings().setOptimizeImports(true)); - performReformatActionOnSelectedFiles(files); checkFormationAndImportsOptimizationFor(files); } - public void testReformatSingleSelectedFile() throws IOException { - List files = createTestFiles(getTempRootDirectory(), classNames); - PsiFile fileToReformat = files.get(0); - List shouldNotBeFormatted = files.subList(1, files.size()); - injectMockDialogFlags(new MockReformatFileSettings().setOptimizeImports(true)); - - performReformatActionOnSelectedFile(fileToReformat); - - checkFormationAndImportsOptimizationFor(Arrays.asList(fileToReformat)); - checkNoProcessingWasPerformedOn(shouldNotBeFormatted); - } - - public void testReformatAndOptimizeFileFromEditor() throws IOException { - List files = createTestFiles(getTempRootDirectory(), classNames); - injectMockDialogFlags(new MockReformatFileSettings().setOptimizeImports(true)); - - PsiFile fileToFormat = files.get(0); - List shouldNotBeFormatted = files.subList(1, files.size()); - - performReformatActionOnFileInEditor(fileToFormat); - checkFormationAndImportsOptimizationFor(Arrays.asList(fileToFormat)); - checkNoProcessingWasPerformedOn(shouldNotBeFormatted); - } - public void testOptimizeAndReformatOnlySelectedFiles() throws IOException { List files = createTestFiles(getTempRootDirectory(), classNames); List forProcessing = ContainerUtil.newArrayList(files.get(0), files.get(1)); @@ -77,28 +52,6 @@ public class ReformatCodeActionTest extends AbstractLayoutCodeProcessorTest { checkNoProcessingWasPerformedOn(noProcessing); } - public void testOptimizeAndReformatAllFilesInDirectoryIncludeSubdirs() throws IOException { - TestFileStructure fileStructure = getThreeLevelDirectoryStructure(); - injectMockDialogFlags(new MockReformatFileSettings().setOptimizeImports(true).setProcessDirectory(true).setIncludeSubdirs(true)); - PsiFile fileInEditor = fileStructure.getFilesAtLevel(2).get(0); - List shouldNotBeFormatted = fileStructure.getFilesAtLevel(1); - - performReformatActionOnFileInEditor(fileInEditor); - - checkFormationAndImportsOptimizationFor(fileStructure.getFilesAtLevel(2), fileStructure.getFilesAtLevel(3)); - checkNoProcessingWasPerformedOn(shouldNotBeFormatted); - } - - public void testOptimizeAndReformatAllFilesInDirectoryExcludeSubdirs() throws IOException { - TestFileStructure fileStructure = getThreeLevelDirectoryStructure(); - injectMockDialogFlags(new MockReformatFileSettings().setOptimizeImports(true).setIncludeSubdirs(false)); - - performReformatActionOnSelectedFiles(fileStructure.getFilesAtLevel(2)); - - checkFormationAndImportsOptimizationFor(fileStructure.getFilesAtLevel(2)); - checkNoProcessingWasPerformedOn(fileStructure.getFilesAtLevel(1), fileStructure.getFilesAtLevel(3)); - } - public void testOptimizeAndReformatInModule() throws IOException { Module module = createModuleWithSourceRoot("newModule"); VirtualFile srcDir = ModuleRootManager.getInstance(module).getSourceRoots()[0]; @@ -109,26 +62,4 @@ public class ReformatCodeActionTest extends AbstractLayoutCodeProcessorTest { checkFormationAndImportsOptimizationFor(files); } - - private TestFileStructure getThreeLevelDirectoryStructure() throws IOException { - TestFileStructure fileStructure = new TestFileStructure(getModule(), getTempRootDirectory()); - - fileStructure.createDirectoryAndMakeItCurrent("dir"); - addFilesToCurrentDirectory(fileStructure); - - fileStructure.createDirectoryAndMakeItCurrent("innerDir"); - addFilesToCurrentDirectory(fileStructure); - - fileStructure.createDirectoryAndMakeItCurrent("innerInnerDir"); - addFilesToCurrentDirectory(fileStructure); - - return fileStructure; - } - - private void addFilesToCurrentDirectory(TestFileStructure fileStructure) throws IOException { - for (int i = 0; i < 5; i++) { - String className = "Test" + i; - fileStructure.addTestFile("Test" + i + ".java", getUntouchedJavaSourceForTotalProcessing(className)); - } - } } diff --git a/platform/lang-api/src/com/intellij/lang/ImportOptimizer.java b/platform/lang-api/src/com/intellij/lang/ImportOptimizer.java index 2e2f12af1d0f..4aca7d3abec8 100644 --- a/platform/lang-api/src/com/intellij/lang/ImportOptimizer.java +++ b/platform/lang-api/src/com/intellij/lang/ImportOptimizer.java @@ -18,6 +18,7 @@ package com.intellij.lang; import com.intellij.psi.PsiFile; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; /** * Implementers of the interface encapsulate optimize imports process for the language. @@ -50,4 +51,11 @@ public interface ImportOptimizer { @NotNull Runnable processFile(PsiFile file); + /** + * In order to customize notification popup after reformat code action just return it from {@link #processFile} with proper information, + * by default "imports optimized" is shown. + */ + interface CollectingInfoRunnable extends Runnable { + @Nullable String getUserNotificationInfo(); + } } diff --git a/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/action/RearrangeCodeAction.java b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/action/RearrangeCodeAction.java index 843f8880d713..959bcf75e7d2 100644 --- a/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/action/RearrangeCodeAction.java +++ b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/action/RearrangeCodeAction.java @@ -21,6 +21,7 @@ import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.CommonDataKeys; 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.psi.PsiDocumentManager; import com.intellij.psi.PsiFile; @@ -61,6 +62,13 @@ public class RearrangeCodeAction extends AnAction { if (file == null) { return; } - new RearrangeCodeProcessor(project, file, editor.getSelectionModel()).run(); + + SelectionModel model = editor.getSelectionModel(); + if (model.hasSelection()) { + new RearrangeCodeProcessor(file, model).run(); + } + else { + new RearrangeCodeProcessor(file).run(); + } } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/actions/AbstractLayoutCodeProcessor.java b/platform/lang-impl/src/com/intellij/codeInsight/actions/AbstractLayoutCodeProcessor.java index e2447f775f6a..5613b489e71c 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/actions/AbstractLayoutCodeProcessor.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/AbstractLayoutCodeProcessor.java @@ -18,12 +18,16 @@ package com.intellij.codeInsight.actions; import com.intellij.codeInsight.CodeInsightBundle; import com.intellij.lang.LanguageFormatting; +import com.intellij.notification.Notification; +import com.intellij.notification.NotificationType; +import com.intellij.openapi.application.ApplicationBundle; import com.intellij.openapi.application.ApplicationBundle; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.SelectionModel; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.module.Module; import com.intellij.openapi.progress.ProcessCanceledException; @@ -37,6 +41,7 @@ import com.intellij.openapi.project.ProjectUtil; import com.intellij.openapi.roots.GeneratedSourcesFilter; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.ui.ex.MessagesEx; +import com.intellij.openapi.util.TextRange; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiBundle; import com.intellij.psi.PsiDirectory; @@ -45,7 +50,9 @@ import com.intellij.psi.PsiFile; import com.intellij.util.IncorrectOperationException; import com.intellij.util.SequentialModalProgressTask; import com.intellij.util.SequentialTask; +import com.intellij.util.SmartList; import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.diff.FilesTooBigForDiffException; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -69,12 +76,14 @@ public abstract class AbstractLayoutCodeProcessor { private final String myProgressText; private final String myCommandName; - private final Runnable myPostRunnable; + private Runnable myPostRunnable; private boolean myProcessChangedTextOnly; protected AbstractLayoutCodeProcessor myPreviousCodeProcessor; private List myFilters = ContainerUtil.newArrayList(); + private LayoutCodeInfoCollector myInfoCollector; + protected AbstractLayoutCodeProcessor(Project project, String commandName, String progressText, boolean processChangedTextOnly) { this(project, (Module)null, commandName, progressText, processChangedTextOnly); } @@ -96,6 +105,7 @@ public abstract class AbstractLayoutCodeProcessor { myCommandName = commandName; myPreviousCodeProcessor = previous; myFilters = previous.myFilters; + myInfoCollector = previous.myInfoCollector; } protected AbstractLayoutCodeProcessor(Project project, @@ -171,12 +181,26 @@ public abstract class AbstractLayoutCodeProcessor { return list; } + public void setPostRunnable(Runnable postRunnable) { + myPostRunnable = postRunnable; + } + @Nullable private FutureTask getPreviousProcessorTask(@NotNull PsiFile file, boolean processChangedTextOnly) { return myPreviousCodeProcessor != null ? myPreviousCodeProcessor.preprocessFile(file, processChangedTextOnly) : null; } + public void setCollectInfo(boolean isCollectInfo) { + myInfoCollector = isCollectInfo ? new LayoutCodeInfoCollector() : null; + + AbstractLayoutCodeProcessor current = this; + while (current.myPreviousCodeProcessor != null) { + current = current.myPreviousCodeProcessor; + current.myInfoCollector = myInfoCollector; + } + } + public void addFileFilter(@NotNull FileFilter filter) { myFilters.add(filter); } @@ -584,4 +608,37 @@ public abstract class AbstractLayoutCodeProcessor { return true; } + + protected static List getSelectedRanges(@NotNull SelectionModel selectionModel) { + final List ranges = new SmartList(); + if (selectionModel.hasSelection()) { + TextRange range = TextRange.create(selectionModel.getSelectionStart(), selectionModel.getSelectionEnd()); + ranges.add(range); + } + else if (selectionModel.hasBlockSelection()) { + int[] starts = selectionModel.getBlockSelectionStarts(); + int[] ends = selectionModel.getBlockSelectionEnds(); + for (int i = 0; i < starts.length; i++) { + ranges.add(TextRange.create(starts[i], ends[i])); + } + } + + return ranges; + } + + protected void handleFileTooBigException(Logger logger, FilesTooBigForDiffException e, @NotNull PsiFile file) { + logger.info("Error while calculating changed ranges for: " + file.getVirtualFile(), e); + if (!ApplicationManager.getApplication().isUnitTestMode()) { + Notification notification = new Notification(ApplicationBundle.message("reformat.changed.text.file.too.big.notification.groupId"), + ApplicationBundle.message("reformat.changed.text.file.too.big.notification.title"), + ApplicationBundle.message("reformat.changed.text.file.too.big.notification.text", file.getName()), + NotificationType.INFORMATION); + notification.notify(file.getProject()); + } + } + + @Nullable + public LayoutCodeInfoCollector getInfoCollector() { + return myInfoCollector; + } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/actions/FileInEditorProcessor.java b/platform/lang-impl/src/com/intellij/codeInsight/actions/FileInEditorProcessor.java new file mode 100644 index 000000000000..75f4c1f3c92e --- /dev/null +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/FileInEditorProcessor.java @@ -0,0 +1,206 @@ +/* + * Copyright 2000-2014 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.codeInsight.actions; + +import com.intellij.codeInsight.hint.HintManager; +import com.intellij.codeInsight.hint.HintManagerImpl; +import com.intellij.codeInsight.hint.HintUtil; +import com.intellij.openapi.actionSystem.ActionManager; +import com.intellij.openapi.application.Application; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.keymap.KeymapUtil; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.PsiFile; +import com.intellij.ui.ColorUtil; +import com.intellij.ui.JBColor; +import com.intellij.ui.LightweightHint; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; + +import static com.intellij.codeInsight.actions.TextRangeType.*; + +class FileInEditorProcessor { + private static final Logger LOG = Logger.getInstance(FileInEditorProcessor.class); + + private final Editor myEditor; + + private boolean myNoChangesDetected = false; + private final boolean myProcessChangesTextOnly; + + private final boolean myShouldOptimizeImports; + private final boolean myShouldRearrangeCode; + private final boolean myProcessSelectedText; + + private final Project myProject; + + private final PsiFile myFile; + private AbstractLayoutCodeProcessor myProcessor; + + public FileInEditorProcessor(PsiFile file, + Editor editor, + LayoutCodeOptions runOptions) + { + myFile = file; + myProject = file.getProject(); + myEditor = editor; + + myShouldOptimizeImports = runOptions.isOptimizeImports(); + myShouldRearrangeCode = runOptions.isRearrangeCode(); + myProcessSelectedText = myEditor != null && runOptions.getTextRangeType() == SELECTED_TEXT; + myProcessChangesTextOnly = runOptions.getTextRangeType() == VCS_CHANGED_TEXT; + } + + public void processCode() { + if (myShouldOptimizeImports) { + myProcessor = new OptimizeImportsProcessor(myProject, myFile); + } + + if (myProcessChangesTextOnly && !FormatChangedTextUtil.hasChanges(myFile)) { + myNoChangesDetected = true; + } + + myProcessor = mixWithReformatProcessor(myProcessor); + if (myShouldRearrangeCode) { + myProcessor = mixWithRearrangeProcessor(myProcessor); + } + + if (shouldNotify()) { + myProcessor.setCollectInfo(true); + myProcessor.setPostRunnable(new Runnable() { + @Override + public void run() { + String message = prepareMessage(); + if (!myEditor.isDisposed() && myEditor.getComponent().isShowing()) { + showHint(myEditor, message); + } + } + }); + } + + myProcessor.run(); + } + + private AbstractLayoutCodeProcessor mixWithRearrangeProcessor(@NotNull AbstractLayoutCodeProcessor processor) { + if (myProcessSelectedText) { + processor = new RearrangeCodeProcessor(processor, myEditor.getSelectionModel()); + } + else { + processor = new RearrangeCodeProcessor(processor); + } + return processor; + } + + @NotNull + private AbstractLayoutCodeProcessor mixWithReformatProcessor(@Nullable AbstractLayoutCodeProcessor processor) { + if (processor != null) { + if (myProcessSelectedText) { + processor = new ReformatCodeProcessor(processor, myEditor.getSelectionModel()); + } + else { + processor = new ReformatCodeProcessor(processor, myProcessChangesTextOnly); + } + } + else { + if (myProcessSelectedText) { + processor = new ReformatCodeProcessor(myFile, myEditor.getSelectionModel()); + } + else { + processor = new ReformatCodeProcessor(myFile, myProcessChangesTextOnly); + } + } + return processor; + } + + @NotNull + private String prepareMessage() { + StringBuilder builder = new StringBuilder(""); + LayoutCodeInfoCollector notifications = myProcessor.getInfoCollector(); + LOG.assertTrue(notifications != null); + + if (notifications.isEmpty() && !myNoChangesDetected) { + if (myProcessChangesTextOnly) { + builder.append("No lines changed: changes since last revision are already properly formatted").append("
"); + } + else { + builder.append("No lines changed: code is already properly formatted").append("
"); + } + } + else { + if (notifications.hasReformatOrRearrangeNotification()) { + String reformatInfo = notifications.getReformatCodeNotification(); + String rearrangeInfo = notifications.getRearrangeCodeNotification(); + + builder.append(joinWithCommaAndCapitalize(reformatInfo, rearrangeInfo)); + + if (myProcessChangesTextOnly) { + builder.append(" in changes since last revision"); + } + + builder.append("
"); + } + else if (myNoChangesDetected) { + builder.append("No lines changed: no changes since last revision").append("
"); + } + + String optimizeImportsNotification = notifications.getOptimizeImportsNotification(); + if (optimizeImportsNotification != null) { + builder.append(StringUtil.capitalize(optimizeImportsNotification)).append("
"); + } + } + + String shortcutText = KeymapUtil.getFirstKeyboardShortcutText(ActionManager.getInstance().getAction("ShowReformatFileDialog")); + String color = ColorUtil.toHex(JBColor.gray); + + builder.append("") + .append("Show reformat dialog: ").append(shortcutText).append("") + .append(""); + + return builder.toString(); + } + + @NotNull + private String joinWithCommaAndCapitalize(String reformatNotification, String rearrangeNotification) { + String firstNotificationLine = reformatNotification != null ? reformatNotification : rearrangeNotification; + if (reformatNotification != null && rearrangeNotification != null) { + firstNotificationLine += ", " + rearrangeNotification; + } + firstNotificationLine = StringUtil.capitalize(firstNotificationLine); + return firstNotificationLine; + } + + private static void showHint(@NotNull Editor editor, @NotNull String info) { + JComponent component = HintUtil.createInformationLabel(info); + LightweightHint hint = new LightweightHint(component); + HintManagerImpl.getInstanceImpl().showEditorHint(hint, editor, HintManager.UNDER, + HintManager.HIDE_BY_ANY_KEY | + HintManager.HIDE_BY_TEXT_CHANGE | + HintManager.HIDE_BY_SCROLLING, + 0, false); + } + + private boolean shouldNotify() { + Application application = ApplicationManager.getApplication(); + if (application.isUnitTestMode() || application.isHeadlessEnvironment()) { + return false; + } + return myEditor != null && !myProcessSelectedText; + } +} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/actions/FormatChangedTextUtil.java b/platform/lang-impl/src/com/intellij/codeInsight/actions/FormatChangedTextUtil.java index 03691d0749e0..0fe2e90f25c7 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/actions/FormatChangedTextUtil.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/FormatChangedTextUtil.java @@ -54,9 +54,10 @@ import org.jetbrains.annotations.Nullable; import java.util.*; public class FormatChangedTextUtil { - public static final Key TEST_REVISION_CONTENT = Key.create("test.revision.content"); + public static final Key TEST_REVISION_CONTENT = Key.create("test.revision.content"); private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.actions.FormatChangedTextUtil"); + private FormatChangedTextUtil() { } @@ -71,26 +72,9 @@ public class FormatChangedTextUtil { final VirtualFile virtualFile = file.getVirtualFile(); if (virtualFile != null) { final Change change = ChangeListManager.getInstance(project).getChange(virtualFile); - if (change != null && change.getType() == Change.Type.NEW) { - return true; - } + return change != null; } - - final LineStatusTrackerManagerI manager = LineStatusTrackerManager.getInstance(project); - if (manager == null) { - return false; - } - - final Document document = PsiDocumentManager.getInstance(project).getDocument(file); - if (document == null) { - return false; - } - final LineStatusTracker lineStatusTracker = manager.getLineStatusTracker(document); - if (lineStatusTracker == null) { - return false; - } - final List ranges = lineStatusTracker.getRanges(); - return !ranges.isEmpty(); + return false; } /** @@ -249,15 +233,18 @@ public class FormatChangedTextUtil { @NotNull public static List getChangedTextRanges(@NotNull Project project, @NotNull PsiFile file) throws FilesTooBigForDiffException { - List cachedChangedLines = getCachedChangedLines(project, file); + Document document = PsiDocumentManager.getInstance(project).getDocument(file); + if (document == null) return ContainerUtil.emptyList(); + + List cachedChangedLines = getCachedChangedLines(project, document); if (cachedChangedLines != null) { return cachedChangedLines; } if (ApplicationManager.getApplication().isUnitTestMode()) { - String testContent = file.getUserData(TEST_REVISION_CONTENT); + CharSequence testContent = file.getUserData(TEST_REVISION_CONTENT); if (testContent != null) { - return calculateChangedTextRanges(file.getProject(), file, testContent); + return calculateChangedTextRanges(document, testContent); } } @@ -270,17 +257,12 @@ public class FormatChangedTextUtil { } String contentFromVcs = getRevisionedContentFrom(change); - return contentFromVcs != null ? calculateChangedTextRanges(project, file, contentFromVcs) + return contentFromVcs != null ? calculateChangedTextRanges(document, contentFromVcs) : ContainerUtil.emptyList(); } @Nullable - private static List getCachedChangedLines(@NotNull Project project, @NotNull PsiFile file) { - Document document = PsiDocumentManager.getInstance(project).getDocument(file); - if (document == null) { - return ContainerUtil.emptyList(); - } - + private static List getCachedChangedLines(@NotNull Project project, @NotNull Document document) { LineStatusTracker tracker = LineStatusTrackerManager.getInstance(project).getLineStatusTracker(document); if (tracker != null) { List ranges = tracker.getRanges(); @@ -307,19 +289,49 @@ public class FormatChangedTextUtil { } @NotNull - private static List calculateChangedTextRanges(@NotNull Project project, - @NotNull PsiFile file, - @NotNull String contentFromVcs) throws FilesTooBigForDiffException + protected static List calculateChangedTextRanges(@NotNull Document document, + @NotNull CharSequence contentFromVcs) throws FilesTooBigForDiffException + { + return getChangedTextRanges(document, getRanges(document, contentFromVcs)); + } + + @NotNull + private static List getRanges(@NotNull Document document, + @NotNull CharSequence contentFromVcs) throws FilesTooBigForDiffException { Document documentFromVcs = ((EditorFactoryImpl)EditorFactory.getInstance()).createDocument(contentFromVcs, true, false); - Document document = PsiDocumentManager.getInstance(project).getDocument(file); + return new RangesBuilder(document, documentFromVcs).getRanges(); + } - if (document == null) { - return ContainerUtil.emptyList(); + protected static int calculateChangedLinesNumber(@NotNull Document document, @NotNull CharSequence contentFromVcs) { + try { + List changedRanges = getRanges(document, contentFromVcs); + int linesChanges = 0; + for (Range range : changedRanges) { + linesChanges += countLines(range); + } + return linesChanges; + } catch (FilesTooBigForDiffException e) { + LOG.info("File too big, can not calculate changed lines number"); + return -1; + } + } + + private static int countLines(Range range) { + byte rangeType = range.getType(); + if (rangeType == Range.MODIFIED) { + int currentChangedLines = range.getLine2() - range.getLine1(); + int revisionLinesChanged = range.getVcsLine2() - range.getVcsLine1(); + return Math.max(currentChangedLines, revisionLinesChanged); + } + else if (rangeType == Range.DELETED) { + return range.getVcsLine2() - range.getVcsLine1(); + } + else if (rangeType == Range.INSERTED) { + return range.getLine2() - range.getLine1(); } - List changedRanges = new RangesBuilder(document, documentFromVcs).getRanges(); - return getChangedTextRanges(document, changedRanges); + return 0; } @NotNull diff --git a/platform/lang-impl/src/com/intellij/codeInsight/actions/LastRunReformatCodeOptionsProvider.java b/platform/lang-impl/src/com/intellij/codeInsight/actions/LastRunReformatCodeOptionsProvider.java new file mode 100644 index 000000000000..9ba882fba3e3 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/LastRunReformatCodeOptionsProvider.java @@ -0,0 +1,85 @@ +/* + * Copyright 2000-2014 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.codeInsight.actions; + +import com.intellij.ide.util.PropertiesComponent; +import com.intellij.lang.Language; +import com.intellij.psi.PsiFile; +import org.jetbrains.annotations.NotNull; + +public class LastRunReformatCodeOptionsProvider { + + private static final String OPTIMIZE_IMPORTS_KEY = "LayoutCode.optimizeImports"; + private static final String REARRANGE_ENTRIES_KEY = "LayoutCode.rearrangeEntries"; + private static final String PROCESS_CHANGED_TEXT_KEY = "LayoutCode.processChangedText"; + + private final PropertiesComponent myPropertiesComponent; + + public LastRunReformatCodeOptionsProvider(@NotNull PropertiesComponent propertiesComponent) { + myPropertiesComponent = propertiesComponent; + } + + public ReformatCodeRunOptions getLastRunOptions(@NotNull PsiFile file) { + Language language = file.getLanguage(); + + ReformatCodeRunOptions settings = new ReformatCodeRunOptions(getLastTextRangeType()); + settings.setOptimizeImports(getLastOptimizeImports()); + settings.setRearrangeCode(isRearrangeCode(language)); + + return settings; + } + + public void saveRearrangeState(@NotNull Language language, boolean value) { + String key = getRearrangeCodeKeyFor(language); + myPropertiesComponent.setValue(key, Boolean.toString(value)); + } + + public void saveOptimizeImportsState(boolean value) { + String optimizeImports = Boolean.toString(value); + myPropertiesComponent.setValue(OPTIMIZE_IMPORTS_KEY, optimizeImports); + } + + public boolean getLastOptimizeImports() { + return myPropertiesComponent.getBoolean(OPTIMIZE_IMPORTS_KEY, false); + } + + public TextRangeType getLastTextRangeType() { + return myPropertiesComponent.getBoolean(PROCESS_CHANGED_TEXT_KEY, false) ? TextRangeType.VCS_CHANGED_TEXT : TextRangeType.WHOLE_FILE; + } + + public void saveProcessVcsChangedTextState(boolean value) { + String processOnlyVcsChangedText = Boolean.toString(value); + myPropertiesComponent.setValue(PROCESS_CHANGED_TEXT_KEY, processOnlyVcsChangedText); + } + + public void saveRearrangeCodeState(boolean value) { + myPropertiesComponent.setValue(REARRANGE_ENTRIES_KEY, Boolean.toString(value)); + } + + public boolean getLastRearrangeCode() { + return myPropertiesComponent.getBoolean(REARRANGE_ENTRIES_KEY, false); + } + + public boolean isRearrangeCode(@NotNull Language language) { + String key = getRearrangeCodeKeyFor(language); + return myPropertiesComponent.getBoolean(key, false); + } + + private static String getRearrangeCodeKeyFor(@NotNull Language language) { + return REARRANGE_ENTRIES_KEY + language.getDisplayName(); + } + +} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutCodeDialog.form b/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutCodeDialog.form new file mode 100644 index 000000000000..b6e07f4c8a5b --- /dev/null +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutCodeDialog.form @@ -0,0 +1,135 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutCodeDialog.java b/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutCodeDialog.java index 716d2f884844..6dc2971b800c 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutCodeDialog.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutCodeDialog.java @@ -16,279 +16,173 @@ package com.intellij.codeInsight.actions; -import com.intellij.CommonBundle; import com.intellij.codeInsight.CodeInsightBundle; import com.intellij.ide.util.PropertiesComponent; import com.intellij.lang.LanguageImportStatements; -import com.intellij.openapi.editor.ex.EditorSettingsExternalizable; import com.intellij.openapi.help.HelpManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; -import com.intellij.psi.PsiDirectory; import com.intellij.psi.PsiFile; -import com.intellij.psi.codeStyle.CodeStyleSettingsManager; -import com.intellij.psi.codeStyle.CommonCodeStyleSettings; import com.intellij.psi.codeStyle.arrangement.Rearranger; -import com.intellij.psi.search.SearchScope; +import com.intellij.testFramework.LightVirtualFile; +import com.intellij.vcsUtil.VcsUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; -import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -public class LayoutCodeDialog extends DialogWrapper implements LayoutCodeOptions { +public class LayoutCodeDialog extends DialogWrapper { @NotNull private final Project myProject; - @Nullable private final PsiFile myFile; - @Nullable private final PsiDirectory myDirectory; - private final Boolean myTextSelected; + @NotNull private final PsiFile myFile; - private JRadioButton myRbFile; - private JRadioButton myRbSelectedText; - private JRadioButton myRbDirectory; - private JCheckBox myCbIncludeSubdirs; - private JCheckBox myCbOptimizeImports; - private JCheckBox myCbArrangeEntries; - private JCheckBox myCbOnlyVcsChangedRegions; - private JCheckBox myDoNotAskMeCheckBox; + private final boolean myTextSelected; private final String myHelpId; - @Nullable private CommonCodeStyleSettings myCommonSettings; - private boolean myRearrangeAlwaysEnabled; + private final LastRunReformatCodeOptionsProvider myLastRunOptions; - private final boolean myOptimizeImportProcessorsForFileLanguageExists; - private final boolean myRearrangerProcessorsForFileLanguageExists; - private final boolean myFileHasChanges; + private JPanel myButtonsPanel; - private boolean myOptimizeImportsSelected; - private boolean myFormatOnlyVCSChangedRegionsSelected; - private boolean myDoNotShowDialogSelected; - private boolean myRearrangeEntriesSelected; + private JCheckBox myOptimizeImportsCb; + private JCheckBox myRearrangeCodeCb; + private JRadioButton myOnlyVCSChangedTextRb; + private JRadioButton mySelectedTextRadioButton; + private JRadioButton myWholeFileRadioButton; - public LayoutCodeDialog(@NotNull Project project, - @NotNull String title, - @Nullable PsiFile file, - @Nullable PsiDirectory directory, - Boolean isTextSelected, + private JPanel myActionsPanel; + private JPanel myScopePanel; + + private LayoutCodeOptions myRunOptions; + + public LayoutCodeDialog(@NotNull Project project, + @NotNull PsiFile file, + boolean textSelected, final String helpId) { super(project, true); myFile = file; myProject = project; - myDirectory = directory; - myTextSelected = isTextSelected; + myTextSelected = textSelected; + myHelpId = helpId; - myOptimizeImportProcessorsForFileLanguageExists = myFile != null && !LanguageImportStatements.INSTANCE.forFile(myFile).isEmpty(); - myRearrangerProcessorsForFileLanguageExists = myFile != null && Rearranger.EXTENSION.forLanguage(myFile.getLanguage()) != null; - myFileHasChanges = myFile != null && FormatChangedTextUtil.hasChanges(myFile); - - if (myFile != null) myCommonSettings = CodeStyleSettingsManager.getSettings(myProject).getCommonSettings(myFile.getLanguage()); - myRearrangeAlwaysEnabled = myCommonSettings != null - && myCommonSettings.isForceArrangeMenuAvailable() - && myCommonSettings.FORCE_REARRANGE_MODE == CommonCodeStyleSettings.REARRANGE_ALWAYS; + myLastRunOptions = new LastRunReformatCodeOptionsProvider(PropertiesComponent.getInstance()); + myRunOptions = createOptionsBundledOnDialog(); setOKButtonText(CodeInsightBundle.message("reformat.code.accept.button.text")); - setTitle(title); + setTitle("Reformat File: " + file.getName()); + init(); - myHelpId = helpId; } - @Override protected void init() { super.init(); - loadCbsStates(); - setUpInitialSelection(); - - myRbFile.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - saveEnabledCbsSelectedState(); - setUpCbsStateForFileFormatting(); - } - }); - - myRbDirectory.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - saveEnabledCbsSelectedState(); - setUpCbsStatesForDirectoryFormatting(); - } - }); - - myRbSelectedText.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - saveEnabledCbsSelectedState(); - setUpCbsStatesForSelectedTextFormatting(); - } - }); + setUpActions(); + setUpTextRangeMode(); } - private void setUpInitialSelection() { - if (myTextSelected == Boolean.TRUE) { - myRbSelectedText.setSelected(true); - setUpCbsStatesForSelectedTextFormatting(); + + private void setUpTextRangeMode() { + mySelectedTextRadioButton.setEnabled(myTextSelected); + if (!myTextSelected) { + mySelectedTextRadioButton.setToolTipText("No text selected in editor"); + } + + final boolean fileHasChanges = FormatChangedTextUtil.hasChanges(myFile); + if (myFile.getVirtualFile() instanceof LightVirtualFile) { + myOnlyVCSChangedTextRb.setVisible(false); } else { - if (myFile != null) { - myRbFile.setSelected(true); - setUpCbsStateForFileFormatting(); + myOnlyVCSChangedTextRb.setEnabled(fileHasChanges); + if (!fileHasChanges) { + String hint = getChangesNotAvailableHint(); + if (hint != null) myOnlyVCSChangedTextRb.setToolTipText(hint); + } + } + + myWholeFileRadioButton.setEnabled(true); + + if (myTextSelected) { + mySelectedTextRadioButton.setSelected(true); + } + else { + boolean lastRunProcessedChangedText = myLastRunOptions.getLastTextRangeType() == TextRangeType.VCS_CHANGED_TEXT; + if (lastRunProcessedChangedText && fileHasChanges) { + myOnlyVCSChangedTextRb.setSelected(true); } else { - myRbDirectory.setSelected(true); - setUpCbsStatesForDirectoryFormatting(); + myWholeFileRadioButton.setSelected(true); } } - myCbIncludeSubdirs.setSelected(true); } - private void loadCbsStates() { - myOptimizeImportsSelected = PropertiesComponent.getInstance().getBoolean(LayoutCodeConstants.OPTIMIZE_IMPORTS_KEY, false); - myRearrangeEntriesSelected = myRearrangeAlwaysEnabled || ReformatCodeAction.getLastSavedRearrangeCbState(myProject, myFile); - myFormatOnlyVCSChangedRegionsSelected = PropertiesComponent.getInstance().getBoolean(LayoutCodeConstants.PROCESS_CHANGED_TEXT_KEY, false); - } + private void setUpActions() { + boolean canOptimizeImports = !LanguageImportStatements.INSTANCE.forFile(myFile).isEmpty(); + myOptimizeImportsCb.setVisible(canOptimizeImports); + if (canOptimizeImports) { + myOptimizeImportsCb.setSelected(myLastRunOptions.getLastOptimizeImports()); + } - private void saveEnabledCbsSelectedState() { - if (myCbArrangeEntries.isEnabled()) { - myRearrangeEntriesSelected = myCbArrangeEntries.isSelected(); - } - if (myCbOptimizeImports.isEnabled()) { - myOptimizeImportsSelected = myCbOptimizeImports.isSelected(); - } - if (myCbOnlyVcsChangedRegions.isEnabled()) { - myFormatOnlyVCSChangedRegionsSelected = myCbOnlyVcsChangedRegions.isSelected(); - } - if (myDoNotAskMeCheckBox.isEnabled()) { - myDoNotShowDialogSelected = myDoNotAskMeCheckBox.isSelected(); + boolean canRearrangeCode = Rearranger.EXTENSION.forLanguage(myFile.getLanguage()) != null; + myRearrangeCodeCb.setVisible(canRearrangeCode); + if (canRearrangeCode) { + myRearrangeCodeCb.setSelected(myLastRunOptions.isRearrangeCode(myFile.getLanguage())); } } - private void setUpCbsStateForFileFormatting() { - myCbOptimizeImports.setEnabled(myOptimizeImportProcessorsForFileLanguageExists); - myCbOptimizeImports.setSelected(myOptimizeImportProcessorsForFileLanguageExists && myOptimizeImportsSelected); - - myCbArrangeEntries.setEnabled(myRearrangerProcessorsForFileLanguageExists); - myCbArrangeEntries.setSelected(myRearrangerProcessorsForFileLanguageExists && myRearrangeEntriesSelected); - - myCbOnlyVcsChangedRegions.setEnabled(myFileHasChanges); - myCbOnlyVcsChangedRegions.setSelected(myFileHasChanges && myFormatOnlyVCSChangedRegionsSelected); - - myDoNotAskMeCheckBox.setEnabled(true); - myDoNotAskMeCheckBox.setSelected(myDoNotShowDialogSelected); - - myCbIncludeSubdirs.setEnabled(false); + @Nullable + private String getChangesNotAvailableHint() { + if (!VcsUtil.isFileUnderVcs(myProject, VcsUtil.getFilePath(myFile.getVirtualFile()))) { + return "File not under VCS root"; + } + else if (!FormatChangedTextUtil.hasChanges(myFile)) { + return "File was not changed since last revision"; + } + return null; } - private void setUpCbsStatesForDirectoryFormatting() { - myCbOptimizeImports.setEnabled(true); - myCbOptimizeImports.setSelected(myOptimizeImportsSelected); + private void saveCurrentConfiguration() { + if (myOptimizeImportsCb.isEnabled()) { + myLastRunOptions.saveOptimizeImportsState(myRunOptions.isOptimizeImports()); + } + if (myRearrangeCodeCb.isEnabled()) { + myLastRunOptions.saveRearrangeState(myFile.getLanguage(), myRunOptions.isRearrangeCode()); + } - myCbArrangeEntries.setEnabled(true); - myCbArrangeEntries.setSelected(myRearrangeEntriesSelected); - - myCbOnlyVcsChangedRegions.setEnabled(myDirectory != null && FormatChangedTextUtil.hasChanges(myDirectory)); - myCbOnlyVcsChangedRegions.setSelected(false); - - myDoNotAskMeCheckBox.setEnabled(false); - myDoNotAskMeCheckBox.setSelected(false); - - myCbIncludeSubdirs.setEnabled(true); + if (!mySelectedTextRadioButton.isSelected()) { + myLastRunOptions.saveProcessVcsChangedTextState(myOnlyVCSChangedTextRb.isSelected()); + } } - private void setUpCbsStatesForSelectedTextFormatting() { - myCbOptimizeImports.setEnabled(false); - myCbOptimizeImports.setSelected(false); + @NotNull + private LayoutCodeOptions createOptionsBundledOnDialog() { + return new LayoutCodeOptions() { + @Override + public TextRangeType getTextRangeType() { + if (myOnlyVCSChangedTextRb.isSelected()) { + return TextRangeType.VCS_CHANGED_TEXT; + } + if (mySelectedTextRadioButton.isSelected()) { + return TextRangeType.SELECTED_TEXT; + } + return TextRangeType.WHOLE_FILE; + } - myCbArrangeEntries.setEnabled(true); - myCbArrangeEntries.setSelected(myRearrangeEntriesSelected); + @Override + public boolean isRearrangeCode() { + return myRearrangeCodeCb.isEnabled() && myRearrangeCodeCb.isSelected(); + } - myCbOnlyVcsChangedRegions.setEnabled(false); - myCbOnlyVcsChangedRegions.setSelected(false); - - myDoNotAskMeCheckBox.setEnabled(true); - myDoNotAskMeCheckBox.setSelected(myDoNotShowDialogSelected); - - myCbIncludeSubdirs.setEnabled(false); + @Override + public boolean isOptimizeImports() { + return myOptimizeImportsCb.isEnabled() && myOptimizeImportsCb.isSelected(); + } + }; } + @Nullable @Override protected JComponent createCenterPanel() { - JPanel panel = new JPanel(new GridBagLayout()); - panel.setBorder(BorderFactory.createEmptyBorder(4, 8, 8, 0)); - GridBagConstraints gbConstraints = new GridBagConstraints(); - gbConstraints.gridy = 0; - gbConstraints.gridx = 0; - gbConstraints.gridwidth = 3; - gbConstraints.gridheight = 1; - gbConstraints.weightx = 1; - - gbConstraints.fill = GridBagConstraints.BOTH; - gbConstraints.insets = new Insets(0, 0, 0, 0); - - myRbFile = new JRadioButton(CodeInsightBundle.message("process.scope.file", - (myFile != null ? "'" + myFile.getVirtualFile().getPresentableUrl() + "'" : ""))); - panel.add(myRbFile, gbConstraints); - - myRbSelectedText = new JRadioButton(CodeInsightBundle.message("reformat.option.selected.text")); - if (myTextSelected != null) { - gbConstraints.gridy++; - gbConstraints.insets = new Insets(0, 0, 0, 0); - panel.add(myRbSelectedText, gbConstraints); - } - - myRbDirectory = new JRadioButton(); - myCbIncludeSubdirs = new JCheckBox(CodeInsightBundle.message("reformat.option.include.subdirectories")); - if (myDirectory != null) { - myRbDirectory.setText(CodeInsightBundle.message("reformat.option.all.files.in.directory", - myDirectory.getVirtualFile().getPresentableUrl())); - gbConstraints.gridy++; - gbConstraints.insets = new Insets(0, 0, 0, 0); - panel.add(myRbDirectory, gbConstraints); - - if (myDirectory.getSubdirectories().length > 0) { - gbConstraints.gridy++; - gbConstraints.insets = new Insets(0, 20, 0, 0); - panel.add(myCbIncludeSubdirs, gbConstraints); - } - } - - myCbOptimizeImports = new JCheckBox(CodeInsightBundle.message("reformat.option.optimize.imports")); - if (myTextSelected != null && LanguageImportStatements.INSTANCE.hasAnyExtensions()) { - gbConstraints.gridy++; - gbConstraints.insets = new Insets(0, 0, 0, 0); - panel.add(myCbOptimizeImports, gbConstraints); - } - - myCbArrangeEntries = new JCheckBox(CodeInsightBundle.message("reformat.option.rearrange.entries")); - if (myDirectory != null || myFile != null && Rearranger.EXTENSION.forLanguage(myFile.getLanguage()) != null) - { - gbConstraints.gridy++; - gbConstraints.insets = new Insets(0, 0, 0, 0); - panel.add(myCbArrangeEntries, gbConstraints); - } - - myCbOnlyVcsChangedRegions = new JCheckBox(CodeInsightBundle.message("reformat.option.vcs.changed.region")); - gbConstraints.gridy++; - panel.add(myCbOnlyVcsChangedRegions, gbConstraints); - - ButtonGroup buttonGroup = new ButtonGroup(); - buttonGroup.add(myRbFile); - buttonGroup.add(myRbSelectedText); - buttonGroup.add(myRbDirectory); - - myRbFile.setEnabled(myFile != null); - myRbSelectedText.setEnabled(myTextSelected == Boolean.TRUE); - - return panel; - } - - @Override - protected JComponent createSouthPanel() { - JComponent southPanel = super.createSouthPanel(); - myDoNotAskMeCheckBox = new JCheckBox(CommonBundle.message("dialog.options.do.not.show")); - return DialogWrapper.addDoNotShowCheckBox(southPanel, myDoNotAskMeCheckBox); + return myButtonsPanel; } @NotNull @@ -302,81 +196,14 @@ public class LayoutCodeDialog extends DialogWrapper implements LayoutCodeOptions HelpManager.getInstance().invokeHelp(myHelpId); } - @Override - public boolean isProcessWholeFile() { - return myRbFile.isSelected(); - } - - @Override - public boolean isProcessDirectory() { - return myRbDirectory.isSelected(); - } - - @Override - public boolean isIncludeSubdirectories() { - return myCbIncludeSubdirs.isSelected(); - } - - @Override - public boolean isOptimizeImports() { - return myCbOptimizeImports.isSelected(); - } - - @Override - public boolean isRearrangeEntries() { - return myCbArrangeEntries.isSelected(); - } - - @Nullable - @Override - public String getFileTypeMask() { - return null; - } - - @Nullable - @Override - public SearchScope getSearchScope() { - return null; - } - - @Override - public boolean isProcessOnlyChangedText() { - return myCbOnlyVcsChangedRegions.isEnabled() && myCbOnlyVcsChangedRegions.isSelected(); - } - - public boolean isDoNotAskMe() { - if (myDoNotAskMeCheckBox.isEnabled()) { - return myDoNotAskMeCheckBox.isSelected(); - } - else { - return !EditorSettingsExternalizable.getInstance().getOptions().SHOW_REFORMAT_DIALOG; - } - } - @Override protected void doOKAction() { super.doOKAction(); - persistEnabledCbsStates(); + saveCurrentConfiguration(); } - private void persistEnabledCbsStates() { - if (myCbOptimizeImports.isEnabled()) { - String optimizeImports = Boolean.toString(myCbOptimizeImports.isSelected()); - PropertiesComponent.getInstance().setValue(LayoutCodeConstants.OPTIMIZE_IMPORTS_KEY, optimizeImports); - } - if (myCbOnlyVcsChangedRegions.isEnabled()) { - String formatVcsChangedRegions = Boolean.toString(myCbOnlyVcsChangedRegions.isSelected()); - PropertiesComponent.getInstance().setValue(LayoutCodeConstants.PROCESS_CHANGED_TEXT_KEY, formatVcsChangedRegions); - } - if (myCbArrangeEntries.isEnabled()) { - saveRearrangeCbState(myCbArrangeEntries.isSelected()); - } + public LayoutCodeOptions getRunOptions() { + return myRunOptions; } - private void saveRearrangeCbState(boolean isSelected) { - if (myFile != null) - LayoutCodeSettingsStorage.saveRearrangeEntriesOptionFor(myProject, myFile.getLanguage(), isSelected); - else - LayoutCodeSettingsStorage.saveRearrangeEntriesOptionFor(myProject, isSelected); - } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutCodeInfoCollector.java b/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutCodeInfoCollector.java new file mode 100644 index 000000000000..8fc7a60d87d1 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutCodeInfoCollector.java @@ -0,0 +1,58 @@ +/* + * Copyright 2000-2015 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.codeInsight.actions; + +public class LayoutCodeInfoCollector { + + private String optimizeImportsNotification = null; + private String reformatCodeNotification = null; + private String rearrangeCodeNotification = null; + + public String getOptimizeImportsNotification() { + return optimizeImportsNotification; + } + + public void setOptimizeImportsNotification(String optimizeImportsNotification) { + this.optimizeImportsNotification = optimizeImportsNotification; + } + + public String getReformatCodeNotification() { + return reformatCodeNotification; + } + + public void setReformatCodeNotification(String reformatCodeNotification) { + this.reformatCodeNotification = reformatCodeNotification; + } + + public String getRearrangeCodeNotification() { + return rearrangeCodeNotification; + } + + public void setRearrangeCodeNotification(String rearrangeCodeNotification) { + this.rearrangeCodeNotification = rearrangeCodeNotification; + } + + public boolean hasReformatOrRearrangeNotification() { + return rearrangeCodeNotification != null + || reformatCodeNotification != null; + } + + public boolean isEmpty() { + return optimizeImportsNotification == null + && rearrangeCodeNotification == null + && reformatCodeNotification == null; + } +} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutCodeOptions.java b/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutCodeOptions.java index 5a05a9ac13ab..2400e3b628af 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutCodeOptions.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutCodeOptions.java @@ -15,10 +15,14 @@ */ package com.intellij.codeInsight.actions; -public interface LayoutCodeOptions extends DirectoryFormattingOptions { +public interface LayoutCodeOptions extends OptionalReformatActions { - boolean isProcessWholeFile(); - - boolean isProcessDirectory(); + TextRangeType getTextRangeType(); } + +enum TextRangeType { + VCS_CHANGED_TEXT, + SELECTED_TEXT, + WHOLE_FILE +} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutCodeSettingsStorage.java b/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutCodeSettingsStorage.java deleted file mode 100644 index 5175cf522556..000000000000 --- a/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutCodeSettingsStorage.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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.codeInsight.actions; - -import com.intellij.ide.util.PropertiesComponent; -import com.intellij.lang.Language; -import com.intellij.openapi.project.Project; -import org.jetbrains.annotations.NotNull; - -public class LayoutCodeSettingsStorage { - - private LayoutCodeSettingsStorage() { - } - - public static void saveRearrangeEntriesOptionFor(@NotNull Project project, @NotNull Language language, boolean value) { - String key = getRearrangeEntriesKeyForLanguage(language); - PropertiesComponent.getInstance(project).setValue(key, Boolean.toString(value)); - } - - public static void saveRearrangeEntriesOptionFor(@NotNull Project project, boolean value) { - PropertiesComponent.getInstance(project).setValue(LayoutCodeConstants.REARRANGE_ENTRIES_KEY, Boolean.toString(value)); - } - - public static boolean getLastSavedRearrangeEntriesCbStateFor(@NotNull Project project) { - return PropertiesComponent.getInstance(project).getBoolean(LayoutCodeConstants.REARRANGE_ENTRIES_KEY, false); - } - - public static boolean getLastSavedRearrangeEntriesCbStateFor(@NotNull Project project, @NotNull Language language) { - String key = getRearrangeEntriesKeyForLanguage(language); - return PropertiesComponent.getInstance(project).getBoolean(key, false); - } - - private static String getRearrangeEntriesKeyForLanguage(@NotNull Language language) { - return LayoutCodeConstants.REARRANGE_ENTRIES_KEY + language.getDisplayName(); - } - -} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutProjectCodeDialog.java b/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutProjectCodeDialog.java index 7f76f1b012a5..7690e09339f8 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutProjectCodeDialog.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutProjectCodeDialog.java @@ -47,6 +47,7 @@ public class LayoutProjectCodeDialog extends DialogWrapper implements ReformatFi private final Project myProject; private final String myText; private final boolean myEnableOnlyVCSChangedTextCb; + private final LastRunReformatCodeOptionsProvider myLastRunOptions; private JLabel myTitle; protected JCheckBox myIncludeSubdirsCb; @@ -75,6 +76,7 @@ public class LayoutProjectCodeDialog extends DialogWrapper implements ReformatFi myText = text; myProject = project; myEnableOnlyVCSChangedTextCb = enableOnlyVCSChangedTextCb; + myLastRunOptions = new LastRunReformatCodeOptionsProvider(PropertiesComponent.getInstance()); setOKButtonText(CodeInsightBundle.message("reformat.code.accept.button.text")); setTitle(title); @@ -100,11 +102,11 @@ public class LayoutProjectCodeDialog extends DialogWrapper implements ReformatFi } private void restoreCbsStates() { - myCbOptimizeImports.setSelected(PropertiesComponent.getInstance().getBoolean(LayoutCodeConstants.OPTIMIZE_IMPORTS_KEY, false)); - myCbRearrangeEntries.setSelected(LayoutCodeSettingsStorage.getLastSavedRearrangeEntriesCbStateFor(myProject)); + myCbOptimizeImports.setSelected(myLastRunOptions.getLastOptimizeImports()); + myCbRearrangeEntries.setSelected(myLastRunOptions.getLastRearrangeCode()); myCbOnlyVcsChangedRegions.setEnabled(myEnableOnlyVCSChangedTextCb); myCbOnlyVcsChangedRegions.setSelected( - myEnableOnlyVCSChangedTextCb && PropertiesComponent.getInstance().getBoolean(LayoutCodeConstants.PROCESS_CHANGED_TEXT_KEY, false) + myEnableOnlyVCSChangedTextCb && myLastRunOptions.getLastTextRangeType() == TextRangeType.VCS_CHANGED_TEXT ); } @@ -177,7 +179,7 @@ public class LayoutProjectCodeDialog extends DialogWrapper implements ReformatFi } @Override - public boolean isRearrangeEntries() { + public boolean isRearrangeCode() { return myCbRearrangeEntries.isSelected(); } @@ -189,10 +191,10 @@ public class LayoutProjectCodeDialog extends DialogWrapper implements ReformatFi @Override protected void doOKAction() { super.doOKAction(); - PropertiesComponent.getInstance().setValue(LayoutCodeConstants.OPTIMIZE_IMPORTS_KEY, Boolean.toString(isOptimizeImports())); - LayoutCodeSettingsStorage.saveRearrangeEntriesOptionFor(myProject, isRearrangeEntries()); + myLastRunOptions.saveOptimizeImportsState(isOptimizeImports()); + myLastRunOptions.saveRearrangeCodeState(isRearrangeCode()); if (myEnableOnlyVCSChangedTextCb) { - PropertiesComponent.getInstance().setValue(LayoutCodeConstants.PROCESS_CHANGED_TEXT_KEY, Boolean.toString(myCbOnlyVcsChangedRegions.isSelected())); + myLastRunOptions.saveProcessVcsChangedTextState(getTextRangeType() == TextRangeType.VCS_CHANGED_TEXT); } } @@ -200,10 +202,6 @@ public class LayoutProjectCodeDialog extends DialogWrapper implements ReformatFi return myCbOptimizeImports.isSelected(); } - public boolean isProcessOnlyChangedText() { - return myCbOnlyVcsChangedRegions.isEnabled() && myCbOnlyVcsChangedRegions.isSelected(); - } - @Nullable public String getFileTypeMask() { if (myEnableFileNameFilterCb.isSelected()) { @@ -232,4 +230,10 @@ public class LayoutProjectCodeDialog extends DialogWrapper implements ReformatFi return false; } + @Override + public TextRangeType getTextRangeType() { + return myCbOnlyVcsChangedRegions.isEnabled() && myCbOnlyVcsChangedRegions.isSelected() + ? TextRangeType.VCS_CHANGED_TEXT + : TextRangeType.WHOLE_FILE; + } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/actions/OptimizeImportsAction.java b/platform/lang-impl/src/com/intellij/codeInsight/actions/OptimizeImportsAction.java index 0295fee17973..3f17fc67a5ec 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/actions/OptimizeImportsAction.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/OptimizeImportsAction.java @@ -17,11 +17,11 @@ package com.intellij.codeInsight.actions; import com.intellij.codeInsight.CodeInsightBundle; +import com.intellij.ide.util.PropertiesComponent; import com.intellij.lang.LanguageImportStatements; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.ex.EditorSettingsExternalizable; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; @@ -72,13 +72,16 @@ public class OptimizeImportsAction extends AnAction { if (projectContext != null || moduleContext != null) { final String text; + final boolean hasChanges; if (moduleContext != null) { text = CodeInsightBundle.message("process.scope.module", moduleContext.getName()); + hasChanges = FormatChangedTextUtil.hasChanges(moduleContext); } else { text = CodeInsightBundle.message("process.scope.project", projectContext.getPresentableUrl()); + hasChanges = FormatChangedTextUtil.hasChanges(projectContext); } - DialogWrapper dialog = new OptimizeOnModuleDialog(project, text); + DialogWrapper dialog = new OptimizeImportsDialog(project, text, hasChanges); if (!dialog.showAndGet()) { return; } @@ -106,29 +109,21 @@ public class OptimizeImportsAction extends AnAction { } } - boolean processDirectory; - boolean includeSubdirectories; - - if (ApplicationManager.getApplication().isUnitTestMode()) { - includeSubdirectories = processDirectory = false; - } - else if (!EditorSettingsExternalizable.getInstance().getOptions().SHOW_OPIMIZE_IMPORTS_DIALOG && file != null) { - includeSubdirectories = processDirectory = false; - } - else { - final LayoutCodeDialog dialog = - new LayoutCodeDialog(project, CodeInsightBundle.message("process.optimize.imports"), file, dir, null, HELP_ID); - if (!dialog.showAndGet()) { + boolean processDirectory = false; + boolean processOnlyVcsChangedFiles = false; + if (!ApplicationManager.getApplication().isUnitTestMode() && file == null && dir != null) { + String message = CodeInsightBundle.message("process.scope.directory", dir.getName()); + OptimizeImportsDialog dialog = new OptimizeImportsDialog(project, message, FormatChangedTextUtil.hasChanges(dir)); + dialog.show(); + if (!dialog.isOK()) { return; } - EditorSettingsExternalizable.getInstance().getOptions().SHOW_OPIMIZE_IMPORTS_DIALOG = !dialog.isDoNotAskMe(); - ReformatCodeAction.updateShowDialogSetting(dialog, "\"Optimize Imports\" dialog disabled"); - processDirectory = dialog.isProcessDirectory(); - includeSubdirectories = dialog.isIncludeSubdirectories(); + processDirectory = true; + processOnlyVcsChangedFiles = dialog.isProcessOnlyVcsChangedFiles(); } if (processDirectory){ - new OptimizeImportsProcessor(project, dir, includeSubdirectories).run(); + new OptimizeImportsProcessor(project, dir, true, processOnlyVcsChangedFiles).run(); } else{ new OptimizeImportsProcessor(project, file).run(); @@ -204,21 +199,43 @@ public class OptimizeImportsAction extends AnAction { return !LanguageImportStatements.INSTANCE.forFile(file).isEmpty(); } - private static class OptimizeOnModuleDialog extends DialogWrapper { - private final String myText; + private static class OptimizeImportsDialog extends DialogWrapper { + private final boolean myContextHasChanges; - OptimizeOnModuleDialog(Project project, String text) { + private final String myText; + private JCheckBox myOnlyVcsCheckBox; + private final LastRunReformatCodeOptionsProvider myLastRunOptions; + + OptimizeImportsDialog(Project project, String text, boolean hasChanges) { super(project, false); myText = text; + myContextHasChanges = hasChanges; + myLastRunOptions = new LastRunReformatCodeOptionsProvider(PropertiesComponent.getInstance()); setOKButtonText(CodeInsightBundle.message("reformat.code.accept.button.text")); setTitle(CodeInsightBundle.message("process.optimize.imports")); init(); } + public boolean isProcessOnlyVcsChangedFiles() { + return myOnlyVcsCheckBox.isSelected(); + } + @Nullable @Override protected JComponent createCenterPanel() { - return new JLabel(myText); + JPanel panel = new JPanel(); + BoxLayout layout = new BoxLayout(panel, BoxLayout.Y_AXIS); + panel.setLayout(layout); + + panel.add(new JLabel(myText)); + myOnlyVcsCheckBox = new JCheckBox(CodeInsightBundle.message("process.scope.changed.files")); + boolean lastRunVcsChangedTextEnabled = myLastRunOptions.getLastTextRangeType() == TextRangeType.VCS_CHANGED_TEXT; + + myOnlyVcsCheckBox.setEnabled(myContextHasChanges); + myOnlyVcsCheckBox.setSelected(myContextHasChanges && lastRunVcsChangedTextEnabled); + + panel.add(myOnlyVcsCheckBox); + return panel; } } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/actions/OptimizeImportsProcessor.java b/platform/lang-impl/src/com/intellij/codeInsight/actions/OptimizeImportsProcessor.java index 6cdb063c0434..1428605281ff 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/actions/OptimizeImportsProcessor.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/OptimizeImportsProcessor.java @@ -25,16 +25,21 @@ import com.intellij.openapi.util.EmptyRunnable; import com.intellij.psi.PsiDirectory; import com.intellij.psi.PsiFile; import com.intellij.psi.impl.source.codeStyle.CodeStyleManagerImpl; +import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.concurrent.FutureTask; +import static com.intellij.codeInsight.actions.OptimizeImportsProcessor.NotificationInfo.*; + public class OptimizeImportsProcessor extends AbstractLayoutCodeProcessor { private static final String PROGRESS_TEXT = CodeInsightBundle.message("progress.text.optimizing.imports"); public static final String COMMAND_NAME = CodeInsightBundle.message("process.optimize.imports"); + private List myOptimizerNotifications = ContainerUtil.newSmartList(); public OptimizeImportsProcessor(Project project) { super(project, COMMAND_NAME, PROGRESS_TEXT, false); @@ -48,6 +53,10 @@ public class OptimizeImportsProcessor extends AbstractLayoutCodeProcessor { super(project, directory, includeSubdirs, PROGRESS_TEXT, COMMAND_NAME, false); } + public OptimizeImportsProcessor(Project project, PsiDirectory directory, boolean includeSubdirs, boolean processOnlyVcsChangedFiles) { + super(project, directory, includeSubdirs, PROGRESS_TEXT, COMMAND_NAME, processOnlyVcsChangedFiles); + } + public OptimizeImportsProcessor(Project project, PsiFile file) { super(project, file, PROGRESS_TEXT, COMMAND_NAME, false); } @@ -77,20 +86,79 @@ public class OptimizeImportsProcessor extends AbstractLayoutCodeProcessor { } } } - Runnable runnable = runnables.isEmpty() ? EmptyRunnable.getInstance() : new Runnable() { + + Runnable runnable = !runnables.isEmpty() ? new Runnable() { @Override public void run() { CodeStyleManagerImpl.setSequentialProcessingAllowed(false); try { for (Runnable runnable : runnables) { runnable.run(); + retrieveAndStoreNotificationInfo(runnable); } + putNotificationInfoIntoCollector(); } finally { CodeStyleManagerImpl.setSequentialProcessingAllowed(true); } } - }; + } : EmptyRunnable.getInstance(); return new FutureTask(runnable, true); } + + private void retrieveAndStoreNotificationInfo(@NotNull Runnable runnable) { + if (runnable instanceof ImportOptimizer.CollectingInfoRunnable) { + String optimizerMessage = ((ImportOptimizer.CollectingInfoRunnable)runnable).getUserNotificationInfo(); + myOptimizerNotifications.add(optimizerMessage != null ? new NotificationInfo(optimizerMessage) : NOTHING_CHANGED_NOTIFICATION); + } + else if (runnable == EmptyRunnable.getInstance()) { + myOptimizerNotifications.add(NOTHING_CHANGED_NOTIFICATION); + } + else { + myOptimizerNotifications.add(SOMETHING_CHANGED_WITHOUT_MESSAGE_NOTIFICATION); + } + } + + private void putNotificationInfoIntoCollector() { + LayoutCodeInfoCollector collector = getInfoCollector(); + if (collector == null) { + return; + } + + boolean atLeastOneOptimizerChangedSomething = false; + for (NotificationInfo info : myOptimizerNotifications) { + atLeastOneOptimizerChangedSomething |= info.isSomethingChanged(); + if (info.getMessage() != null) { + collector.setOptimizeImportsNotification(info.getMessage()); + return; + } + } + + collector.setOptimizeImportsNotification(atLeastOneOptimizerChangedSomething ? "imports optimized" : null); + } + + static class NotificationInfo { + public static final NotificationInfo NOTHING_CHANGED_NOTIFICATION = new NotificationInfo(false, null); + public static final NotificationInfo SOMETHING_CHANGED_WITHOUT_MESSAGE_NOTIFICATION = new NotificationInfo(true, null); + + private final boolean mySomethingChanged; + private final String myMessage; + + NotificationInfo(@NotNull String message) { + this(true, message); + } + + public boolean isSomethingChanged() { + return mySomethingChanged; + } + + public String getMessage() { + return myMessage; + } + + private NotificationInfo(boolean isSomethingChanged, @Nullable String message) { + mySomethingChanged = isSomethingChanged; + myMessage = message; + } + } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutCodeConstants.java b/platform/lang-impl/src/com/intellij/codeInsight/actions/OptionalReformatActions.java similarity index 54% rename from platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutCodeConstants.java rename to platform/lang-impl/src/com/intellij/codeInsight/actions/OptionalReformatActions.java index 84de47d7f457..e71f5c116ad5 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutCodeConstants.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/OptionalReformatActions.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 JetBrains s.r.o. + * Copyright 2000-2014 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. @@ -15,18 +15,10 @@ */ package com.intellij.codeInsight.actions; -import org.jetbrains.annotations.NonNls; +public interface OptionalReformatActions { -/** - * @author Denis Zhdanov - * @since 12/16/11 6:10 PM - */ -public class LayoutCodeConstants { + boolean isOptimizeImports(); - @NonNls public static final String OPTIMIZE_IMPORTS_KEY = "LayoutCode.optimizeImports"; - @NonNls public static final String REARRANGE_ENTRIES_KEY = "LayoutCode.rearrangeEntries"; - @NonNls public static final String PROCESS_CHANGED_TEXT_KEY = "LayoutCode.processChangedText"; + boolean isRearrangeCode(); - private LayoutCodeConstants() { - } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/actions/RearrangeCodeProcessor.java b/platform/lang-impl/src/com/intellij/codeInsight/actions/RearrangeCodeProcessor.java index 4867d85abbd7..fc264cdb61cc 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/actions/RearrangeCodeProcessor.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/RearrangeCodeProcessor.java @@ -15,8 +15,10 @@ */ package com.intellij.codeInsight.actions; +import com.intellij.codeInsight.CodeInsightBundle; import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.components.ServiceManager; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.SelectionModel; import com.intellij.openapi.project.Project; @@ -25,21 +27,22 @@ import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiFile; import com.intellij.psi.codeStyle.arrangement.Rearranger; import com.intellij.psi.codeStyle.arrangement.engine.ArrangementEngine; -import com.intellij.util.SmartList; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.diff.FilesTooBigForDiffException; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Collection; -import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; public class RearrangeCodeProcessor extends AbstractLayoutCodeProcessor { public static final String COMMAND_NAME = "Rearrange code"; - public static final String PROGRESS_TEXT = "Rearranging code..."; + public static final String PROGRESS_TEXT = CodeInsightBundle.message("process.rearrange.code"); - @Nullable private SelectionModel mySelectionModel; + private static final Logger LOG = Logger.getInstance(RearrangeCodeProcessor.class); + private SelectionModel mySelectionModel; public RearrangeCodeProcessor(@NotNull AbstractLayoutCodeProcessor previousProcessor) { super(previousProcessor, COMMAND_NAME, PROGRESS_TEXT); @@ -49,14 +52,16 @@ public class RearrangeCodeProcessor extends AbstractLayoutCodeProcessor { super(previousProcessor, COMMAND_NAME, PROGRESS_TEXT); mySelectionModel = selectionModel; } - - public RearrangeCodeProcessor(@NotNull Project project, - @NotNull PsiFile file, - @Nullable SelectionModel selectionModel) { - super(project, file, PROGRESS_TEXT, COMMAND_NAME, false); + + public RearrangeCodeProcessor(@NotNull PsiFile file, @NotNull SelectionModel selectionModel) { + super(file.getProject(), file, PROGRESS_TEXT, COMMAND_NAME, false); mySelectionModel = selectionModel; } + public RearrangeCodeProcessor(@NotNull PsiFile file) { + super(file.getProject(), file, PROGRESS_TEXT, COMMAND_NAME, false); + } + public RearrangeCodeProcessor(@NotNull Project project, @NotNull PsiFile[] files, @NotNull String commandName, @@ -70,70 +75,55 @@ public class RearrangeCodeProcessor extends AbstractLayoutCodeProcessor { return new FutureTask(new Callable() { @Override public Boolean call() throws Exception { - Collection ranges = processChangedTextOnly ? FormatChangedTextUtil.getChangedTextRanges(myProject, file) - : getRangesToFormat(file); + try { + Collection ranges = getRangesToFormat(file, processChangedTextOnly); + Document document = PsiDocumentManager.getInstance(myProject).getDocument(file); - RearrangeCommand rearranger = new RearrangeCommand(myProject, file, COMMAND_NAME, ranges); - if (rearranger.couldRearrange()) { - rearranger.run(); + if (document != null && Rearranger.EXTENSION.forLanguage(file.getLanguage()) != null) { + Runnable command = prepareRearrangeCommand(file, ranges); + PsiDocumentManager.getInstance(myProject).doPostponedOperationsAndUnblockDocument(document); + try { + CommandProcessor.getInstance().executeCommand(myProject, command, COMMAND_NAME, null); + } + finally { + PsiDocumentManager.getInstance(myProject).commitDocument(document); + } + } + + return true; + } + catch (FilesTooBigForDiffException e) { + handleFileTooBigException(LOG, e, file); + return false; } - return true; } }); } - public Collection getRangesToFormat(@NotNull PsiFile file) { - final List ranges = new SmartList(); - if (mySelectionModel != null && mySelectionModel.hasSelection()) { - ranges.add(TextRange.create(mySelectionModel.getSelectionStart(), mySelectionModel.getSelectionEnd())); - } - else { - ranges.add(TextRange.create(0, file.getTextLength())); - } - return ranges; - } -} - - -class RearrangeCommand { - @NotNull private PsiFile myFile; - @NotNull private String myCommandName; - @NotNull private Project myProject; - private Document myDocument; - private Runnable myCommand; - private final Collection myRanges; - - RearrangeCommand(@NotNull Project project, @NotNull PsiFile file, @NotNull String commandName, @NotNull Collection ranges) { - myProject = project; - myFile = file; - myRanges = ranges; - myCommandName = commandName; - myDocument = PsiDocumentManager.getInstance(project).getDocument(file); - } - - boolean couldRearrange() { - return myDocument != null && Rearranger.EXTENSION.forLanguage(myFile.getLanguage()) != null; - } - - void run() { - assert myDocument != null; - prepare(); - try { - CommandProcessor.getInstance().executeCommand(myProject, myCommand, myCommandName, null); - } - finally { - PsiDocumentManager.getInstance(myProject).commitDocument(myDocument); - } - } - - private void prepare() { + @NotNull + private Runnable prepareRearrangeCommand(@NotNull final PsiFile file, @NotNull final Collection ranges) { final ArrangementEngine engine = ServiceManager.getService(myProject, ArrangementEngine.class); - myCommand = new Runnable() { + return new Runnable() { @Override public void run() { - engine.arrange(myFile, myRanges); + engine.arrange(file, ranges); + if (getInfoCollector() != null) { + String info = engine.getUserNotificationInfo(); + getInfoCollector().setRearrangeCodeNotification(info); + } } }; - PsiDocumentManager.getInstance(myProject).doPostponedOperationsAndUnblockDocument(myDocument); + } + + public Collection getRangesToFormat(@NotNull PsiFile file, boolean processChangedTextOnly) throws FilesTooBigForDiffException { + if (mySelectionModel != null) { + return getSelectedRanges(mySelectionModel); + } + + if (processChangedTextOnly) { + return FormatChangedTextUtil.getChangedTextRanges(myProject, file); + } + + return ContainerUtil.newSmartList(file.getTextRange()); } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatCodeAction.java b/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatCodeAction.java index ba3d2d961f00..bdff57d94240 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatCodeAction.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatCodeAction.java @@ -16,32 +16,22 @@ package com.intellij.codeInsight.actions; -import com.intellij.application.options.editor.EditorOptions; import com.intellij.codeInsight.CodeInsightBundle; import com.intellij.find.impl.FindInProjectUtil; import com.intellij.formatting.FormattingModelBuilder; import com.intellij.ide.util.PropertiesComponent; import com.intellij.lang.LanguageFormatting; -import com.intellij.notification.Notification; -import com.intellij.notification.NotificationListener; -import com.intellij.notification.NotificationType; -import com.intellij.notification.Notifications; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.ex.EditorSettingsExternalizable; import com.intellij.openapi.module.Module; -import com.intellij.openapi.options.ShowSettingsUtil; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.TextRange; import com.intellij.openapi.vcs.changes.ChangeListManagerImpl; import com.intellij.openapi.vfs.ReadonlyStatusHandler; import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.wm.IdeFrame; -import com.intellij.openapi.wm.ex.WindowManagerEx; import com.intellij.psi.*; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.LocalSearchScope; @@ -53,8 +43,6 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; -import javax.swing.*; -import javax.swing.event.HyperlinkEvent; import java.util.ArrayList; import java.util.List; import java.util.regex.Pattern; @@ -95,14 +83,14 @@ public class ReformatCodeAction extends AnAction implements DumbAware { if (selectedFlags == null) return; - final boolean processOnlyChangedText = selectedFlags.isProcessOnlyChangedText(); + final boolean processOnlyChangedText = selectedFlags.getTextRangeType() == TextRangeType.VCS_CHANGED_TEXT; final boolean shouldOptimizeImports = selectedFlags.isOptimizeImports() && !DumbService.getInstance(project).isDumb(); AbstractLayoutCodeProcessor processor = new ReformatCodeProcessor(project, convertToPsiFiles(files, project), null, processOnlyChangedText); if (shouldOptimizeImports) { processor = new OptimizeImportsProcessor(processor); } - if (selectedFlags.isRearrangeEntries()) { + if (selectedFlags.isRearrangeCode()) { processor = new RearrangeCodeProcessor(processor); } @@ -134,15 +122,7 @@ public class ReformatCodeAction extends AnAction implements DumbAware { } } - boolean optimizeImports = ReformatFilesDialog.isOptmizeImportsOptionOn(); - boolean processWholeFile = false; - boolean processChangedTextOnly = PropertiesComponent.getInstance().getBoolean(LayoutCodeConstants.PROCESS_CHANGED_TEXT_KEY, false); - boolean rearrangeEntries = getLastSavedRearrangeCbState(project, file); - - final boolean showDialog = EditorSettingsExternalizable.getInstance().getOptions().SHOW_REFORMAT_DIALOG; - - if (file == null && dir == null) return; - if (file == null) { + if (file == null && dir != null) { DirectoryFormattingOptions options = getDirectoryFormattingOptions(project, dir); if (options != null) { reformatDirectory(project, dir, options); @@ -150,57 +130,29 @@ public class ReformatCodeAction extends AnAction implements DumbAware { return; } - if (showDialog) { - LayoutCodeOptions selectedFlags = getLayoutCodeOptions(project, file, dir, hasSelection); - if (selectedFlags == null) - return; + if (file == null || editor == null) return; - optimizeImports = selectedFlags.isOptimizeImports(); - rearrangeEntries = selectedFlags.isRearrangeEntries(); - processWholeFile = selectedFlags.isProcessWholeFile(); - processChangedTextOnly = selectedFlags.isProcessOnlyChangedText(); + LastRunReformatCodeOptionsProvider provider = new LastRunReformatCodeOptionsProvider(PropertiesComponent.getInstance()); + ReformatCodeRunOptions currentRunOptions = provider.getLastRunOptions(file); - if (selectedFlags.isProcessDirectory()) { - assert dir != null : "File = " + file + ", Element = " + CommonDataKeys.PSI_ELEMENT.getData(dataContext); - reformatDirectory(project, dir, selectedFlags); - return; + TextRangeType processingScope = currentRunOptions.getTextRangeType(); + if (hasSelection) { + processingScope = TextRangeType.SELECTED_TEXT; + } + else if (processingScope == TextRangeType.VCS_CHANGED_TEXT) { + if (isChangeNotTrackedForFile(project, file)) { + processingScope = TextRangeType.WHOLE_FILE; } } - - if (!showDialog && processChangedTextOnly && isChangeNotTrackedForFile(project, file)) { - processChangedTextOnly = false; - } - - final TextRange range; - final boolean processSelectedText = !processWholeFile && hasSelection; - if (processSelectedText) { - range = TextRange.create(editor.getSelectionModel().getSelectionStart(), editor.getSelectionModel().getSelectionEnd()); - } - else{ - range = null; - } - - AbstractLayoutCodeProcessor processor; - if (optimizeImports && range == null) { - processor = new OptimizeImportsProcessor(project, file); - processor = new ReformatCodeProcessor(processor, processChangedTextOnly); - } else { - processor = new ReformatCodeProcessor(project, file, range, !processSelectedText && processChangedTextOnly); + processingScope = TextRangeType.WHOLE_FILE; } - if (rearrangeEntries) { - if (processSelectedText && editor != null) { - processor = new RearrangeCodeProcessor(processor, editor.getSelectionModel()); - } - else { - processor = new RearrangeCodeProcessor(processor); - } - } - - processor.run(); + currentRunOptions.setProcessingScope(processingScope); + new FileInEditorProcessor(file, editor, currentRunOptions).processCode(); } + private static boolean isChangeNotTrackedForFile(@NotNull Project project, @NotNull PsiFile file) { boolean isUnderVcs = VcsUtil.isFileUnderVcs(project, VcsUtil.getFilePath(file.getVirtualFile())); if (!isUnderVcs) return true; @@ -238,10 +190,7 @@ public class ReformatCodeAction extends AnAction implements DumbAware { @NotNull DirectoryFormattingOptions options) { AbstractLayoutCodeProcessor processor = new ReformatCodeProcessor( - project, - dir, - options.isIncludeSubdirectories(), - options.isProcessOnlyChangedText() + project, dir, options.isIncludeSubdirectories(), options.getTextRangeType() == TextRangeType.VCS_CHANGED_TEXT ); registerScopeFilter(processor, options.getSearchScope()); @@ -250,7 +199,7 @@ public class ReformatCodeAction extends AnAction implements DumbAware { if (options.isOptimizeImports()) { processor = new OptimizeImportsProcessor(processor); } - if (options.isRearrangeEntries()) { + if (options.isRearrangeCode()) { processor = new RearrangeCodeProcessor(processor); } @@ -262,7 +211,7 @@ public class ReformatCodeAction extends AnAction implements DumbAware { @NotNull ReformatFilesOptions selectedFlags) { boolean shouldOptimizeImports = selectedFlags.isOptimizeImports() && !DumbService.getInstance(project).isDumb(); - boolean processOnlyChangedText = selectedFlags.isProcessOnlyChangedText(); + boolean processOnlyChangedText = selectedFlags.getTextRangeType() == TextRangeType.VCS_CHANGED_TEXT; AbstractLayoutCodeProcessor processor; if (moduleContext != null) @@ -277,7 +226,7 @@ public class ReformatCodeAction extends AnAction implements DumbAware { processor = new OptimizeImportsProcessor(processor); } - if (selectedFlags.isRearrangeEntries()) { + if (selectedFlags.isRearrangeCode()) { processor = new RearrangeCodeProcessor(processor); } @@ -329,23 +278,6 @@ public class ReformatCodeAction extends AnAction implements DumbAware { } } - public static void updateShowDialogSetting(LayoutCodeDialog dialog, String title) { - if (dialog.isDoNotAskMe()) { - Notifications.Bus.notify(new Notification("Reformat Code", title, - "You can re-enable the dialog on the IDE Settings -> Editor pane", - NotificationType.INFORMATION, new NotificationListener() { - @Override - public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent e) { - if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { - final ShowSettingsUtil util = ShowSettingsUtil.getInstance(); - IdeFrame ideFrame = WindowManagerEx.getInstanceEx().findFrameFor(null); - util.editConfigurable((JFrame)ideFrame, new EditorOptions()); - } - } - })); - } - } - public static PsiFile[] convertToPsiFiles(final VirtualFile[] files,Project project) { final PsiManager manager = PsiManager.getInstance(project); final ArrayList result = new ArrayList(); @@ -457,31 +389,6 @@ public class ReformatCodeAction extends AnAction implements DumbAware { return dialog; } - @Nullable - private static LayoutCodeOptions getLayoutCodeOptions(@NotNull Project project, - @Nullable PsiFile file, - @Nullable PsiDirectory dir, - boolean hasSelection) { - if (ApplicationManager.getApplication().isUnitTestMode()) { - return (LayoutCodeOptions)myTestOptions; - } - LayoutCodeDialog dialog = new LayoutCodeDialog(project, CodeInsightBundle.message("process.reformat.code"), - file, dir, hasSelection ? Boolean.TRUE : Boolean.FALSE, HELP_ID); - if (!dialog.showAndGet()) { - return null; - } - EditorSettingsExternalizable.getInstance().getOptions().SHOW_REFORMAT_DIALOG = !dialog.isDoNotAskMe(); - updateShowDialogSetting(dialog, "\"Reformat Code\" dialog disabled"); - return dialog; - } - - public static boolean getLastSavedRearrangeCbState(@NotNull Project project, @Nullable PsiFile file) { - if (file != null) { - return LayoutCodeSettingsStorage.getLastSavedRearrangeEntriesCbStateFor(project, file.getLanguage()); - } - return LayoutCodeSettingsStorage.getLastSavedRearrangeEntriesCbStateFor(project); - } - @TestOnly protected static void setTestOptions(ReformatFilesOptions options) { myTestOptions = options; diff --git a/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatCodeProcessor.java b/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatCodeProcessor.java index 4ebe9274c1ca..e9f80bd23fad 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatCodeProcessor.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatCodeProcessor.java @@ -18,15 +18,14 @@ package com.intellij.codeInsight.actions; import com.intellij.codeInsight.CodeInsightBundle; import com.intellij.formatting.FormattingProgressTask; -import com.intellij.notification.Notification; -import com.intellij.notification.NotificationType; -import com.intellij.openapi.application.ApplicationBundle; -import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.SelectionModel; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiDirectory; +import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiFile; import com.intellij.psi.codeStyle.CodeStyleManager; import com.intellij.util.IncorrectOperationException; @@ -46,13 +45,24 @@ public class ReformatCodeProcessor extends AbstractLayoutCodeProcessor { private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.actions.ReformatCodeProcessor"); - private final Collection myRanges = new ArrayList(); private static final String PROGRESS_TEXT = CodeInsightBundle.message("reformat.progress.common.text"); + private final Collection myRanges = new ArrayList(); + private SelectionModel mySelectionModel; public ReformatCodeProcessor(Project project, boolean processChangedTextOnly) { super(project, COMMAND_NAME, PROGRESS_TEXT, processChangedTextOnly); } + public ReformatCodeProcessor(@NotNull PsiFile file, @NotNull SelectionModel selectionModel) { + super(file.getProject(), file, COMMAND_NAME, PROGRESS_TEXT, false); + mySelectionModel = selectionModel; + } + + public ReformatCodeProcessor(AbstractLayoutCodeProcessor processor, @NotNull SelectionModel selectionModel) { + super(processor, COMMAND_NAME, PROGRESS_TEXT); + mySelectionModel = selectionModel; + } + public ReformatCodeProcessor(AbstractLayoutCodeProcessor processor, boolean processChangedTextOnly) { super(processor, COMMAND_NAME, PROGRESS_TEXT); setProcessChangedTextOnly(processChangedTextOnly); @@ -73,6 +83,10 @@ public class ReformatCodeProcessor extends AbstractLayoutCodeProcessor { } } + public ReformatCodeProcessor(@NotNull PsiFile file, boolean processChangedTextOnly) { + super(file.getProject(), file, PROGRESS_TEXT, COMMAND_NAME, processChangedTextOnly); + } + public ReformatCodeProcessor(Project project, PsiFile[] files, @Nullable Runnable postRunnable, boolean processChangedTextOnly) { this(project, files, COMMAND_NAME, postRunnable, processChangedTextOnly); } @@ -92,23 +106,31 @@ public class ReformatCodeProcessor extends AbstractLayoutCodeProcessor { throws IncorrectOperationException { return new FutureTask(new Callable() { + private Document myDocument; + @Override public Boolean call() throws Exception { FormattingProgressTask.FORMATTING_CANCELLED_FLAG.set(false); try { Collection ranges = getRangesToFormat(processChangedTextOnly, file); + + CharSequence before = null; + if (getInfoCollector() != null) { + myDocument = PsiDocumentManager.getInstance(myProject).getDocument(file); + LOG.assertTrue(myDocument != null); + before = myDocument.getImmutableCharSequence(); + } + CodeStyleManager.getInstance(myProject).reformatText(file, ranges); + + if (before != null) { + prepareUserNotificationMessage(myDocument, before); + } + return !FormattingProgressTask.FORMATTING_CANCELLED_FLAG.get(); } catch (FilesTooBigForDiffException e) { - LOG.info("Error while calculating changed ranges for: " + file.getVirtualFile(), e); - if (!ApplicationManager.getApplication().isUnitTestMode()) { - Notification notification = new Notification(ApplicationBundle.message("reformat.changed.text.file.too.big.notification.groupId"), - ApplicationBundle.message("reformat.changed.text.file.too.big.notification.title"), - ApplicationBundle.message("reformat.changed.text.file.too.big.notification.text", file.getName()), - NotificationType.INFORMATION); - notification.notify(file.getProject()); - } + handleFileTooBigException(LOG, e, file); return false; } catch (IncorrectOperationException e) { @@ -122,8 +144,21 @@ public class ReformatCodeProcessor extends AbstractLayoutCodeProcessor { }); } + private void prepareUserNotificationMessage(@NotNull Document document, @NotNull CharSequence before) { + LOG.assertTrue(getInfoCollector() != null); + int number = FormatChangedTextUtil.calculateChangedLinesNumber(document, before); + if (number > 0) { + String message = "formatted " + number + " line" + (number > 1 ? "s" : ""); + getInfoCollector().setReformatCodeNotification(message); + } + } + @NotNull private Collection getRangesToFormat(boolean processChangedTextOnly, PsiFile file) throws FilesTooBigForDiffException { + if (mySelectionModel != null) { + return getSelectedRanges(mySelectionModel); + } + if (processChangedTextOnly) { return FormatChangedTextUtil.getChangedTextRanges(myProject, file); } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatCodeRunOptions.java b/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatCodeRunOptions.java new file mode 100644 index 000000000000..67d20e28b735 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatCodeRunOptions.java @@ -0,0 +1,58 @@ +/* + * Copyright 2000-2014 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.codeInsight.actions; + +public class ReformatCodeRunOptions implements LayoutCodeOptions { + + private boolean myRearrangeCode; + private boolean myOptimizeImports; + private TextRangeType myProcessingScope; + + public ReformatCodeRunOptions(TextRangeType processingScope) { + myProcessingScope = processingScope; + } + + public void setProcessingScope(TextRangeType processingScope) { + myProcessingScope = processingScope; + } + + @Override + public boolean isOptimizeImports() { + return myOptimizeImports; + } + + @Override + public boolean isRearrangeCode() { + return myRearrangeCode; + } + + public ReformatCodeRunOptions setRearrangeCode(boolean rearrangeCode) { + myRearrangeCode = rearrangeCode; + return this; + } + + public ReformatCodeRunOptions setOptimizeImports(boolean optimizeImports) { + myOptimizeImports = optimizeImports; + return this; + } + + @Override + public TextRangeType getTextRangeType() { + return myProcessingScope; + } + +} + diff --git a/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatFilesDialog.java b/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatFilesDialog.java index d364857352e6..a7651cd34cb5 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatFilesDialog.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatFilesDialog.java @@ -27,25 +27,27 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; +import static com.intellij.codeInsight.actions.TextRangeType.*; + public class ReformatFilesDialog extends DialogWrapper implements ReformatFilesOptions { - @NotNull private Project myProject; private JPanel myPanel; private JCheckBox myOptimizeImports; private JCheckBox myOnlyChangedText; private JCheckBox myRearrangeEntriesCb; + private final LastRunReformatCodeOptionsProvider myLastRunSettings; + public ReformatFilesDialog(@NotNull Project project, @NotNull VirtualFile[] files) { super(project, true); - myProject = project; - setTitle(CodeInsightBundle.message("dialog.reformat.files.title")); - myOptimizeImports.setSelected(isOptmizeImportsOptionOn()); + myLastRunSettings = new LastRunReformatCodeOptionsProvider(PropertiesComponent.getInstance()); + boolean canTargetVcsChanges = FormatChangedTextUtil.hasChanges(files, project); myOnlyChangedText.setEnabled(canTargetVcsChanges); - myOnlyChangedText.setSelected( - canTargetVcsChanges && PropertiesComponent.getInstance().getBoolean(LayoutCodeConstants.PROCESS_CHANGED_TEXT_KEY, false) - ); - myOptimizeImports.setSelected(isOptmizeImportsOptionOn()); - myRearrangeEntriesCb.setSelected(LayoutCodeSettingsStorage.getLastSavedRearrangeEntriesCbStateFor(myProject)); + myOnlyChangedText.setSelected(canTargetVcsChanges && myLastRunSettings.getLastTextRangeType() == VCS_CHANGED_TEXT); + myOptimizeImports.setSelected(myLastRunSettings.getLastOptimizeImports()); + myRearrangeEntriesCb.setSelected(myLastRunSettings.getLastRearrangeCode()); + + setTitle(CodeInsightBundle.message("dialog.reformat.files.title")); init(); } @@ -60,25 +62,25 @@ public class ReformatFilesDialog extends DialogWrapper implements ReformatFilesO } @Override - public boolean isProcessOnlyChangedText() { - return myOnlyChangedText.isEnabled() && myOnlyChangedText.isSelected(); + public TextRangeType getTextRangeType() { + return myOnlyChangedText.isEnabled() && myOnlyChangedText.isSelected() + ? VCS_CHANGED_TEXT + : WHOLE_FILE; } @Override - public boolean isRearrangeEntries() { + public boolean isRearrangeCode() { return myRearrangeEntriesCb.isSelected(); } @Override protected void doOKAction() { super.doOKAction(); - PropertiesComponent.getInstance().setValue(LayoutCodeConstants.OPTIMIZE_IMPORTS_KEY, Boolean.toString(myOptimizeImports.isSelected())); - PropertiesComponent.getInstance().setValue(LayoutCodeConstants.PROCESS_CHANGED_TEXT_KEY, Boolean.toString(myOnlyChangedText.isSelected())); - LayoutCodeSettingsStorage.saveRearrangeEntriesOptionFor(myProject, isRearrangeEntries()); - } - - static boolean isOptmizeImportsOptionOn() { - return PropertiesComponent.getInstance().getBoolean(LayoutCodeConstants.OPTIMIZE_IMPORTS_KEY, false); + myLastRunSettings.saveOptimizeImportsState(isOptimizeImports()); + myLastRunSettings.saveRearrangeCodeState(isRearrangeCode()); + if (myOnlyChangedText.isEnabled()) { + myLastRunSettings.saveProcessVcsChangedTextState(getTextRangeType() == VCS_CHANGED_TEXT); + } } @Nullable diff --git a/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatFilesOptions.java b/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatFilesOptions.java index bb958e77b2fe..f10757940324 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatFilesOptions.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatFilesOptions.java @@ -18,13 +18,7 @@ package com.intellij.codeInsight.actions; import com.intellij.psi.search.SearchScope; import org.jetbrains.annotations.Nullable; -public interface ReformatFilesOptions { - - boolean isOptimizeImports(); - - boolean isProcessOnlyChangedText(); - - boolean isRearrangeEntries(); +public interface ReformatFilesOptions extends LayoutCodeOptions { @Nullable String getFileTypeMask(); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/actions/ShowReformatFileDialog.java b/platform/lang-impl/src/com/intellij/codeInsight/actions/ShowReformatFileDialog.java new file mode 100644 index 000000000000..b564b939ea1e --- /dev/null +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/ShowReformatFileDialog.java @@ -0,0 +1,77 @@ +/* + * Copyright 2000-2014 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.codeInsight.actions; + +import com.intellij.lang.LanguageFormatting; +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiFile; +import org.jetbrains.annotations.NonNls; + +public class ShowReformatFileDialog extends AnAction implements DumbAware { + private static final @NonNls String HELP_ID = "editing.codeReformatting"; + + @Override + public void update(AnActionEvent event) { + Presentation presentation = event.getPresentation(); + DataContext dataContext = event.getDataContext(); + Project project = CommonDataKeys.PROJECT.getData(dataContext); + Editor editor = CommonDataKeys.EDITOR.getData(dataContext); + if (project == null || editor == null) { + presentation.setEnabled(false); + return; + } + + PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument()); + if (file == null || file.getVirtualFile() == null) { + presentation.setEnabled(false); + return; + } + + if (LanguageFormatting.INSTANCE.forContext(file) != null) { + presentation.setEnabled(true); + } + } + + @Override + public void actionPerformed(AnActionEvent event) { + Presentation presentation = event.getPresentation(); + DataContext dataContext = event.getDataContext(); + Project project = CommonDataKeys.PROJECT.getData(dataContext); + Editor editor = CommonDataKeys.EDITOR.getData(dataContext); + if (project == null || editor == null) { + presentation.setEnabled(false); + return; + } + + PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument()); + if (file == null || file.getVirtualFile() == null) { + presentation.setEnabled(false); + return; + } + + boolean hasSelection = editor.getSelectionModel().hasSelection(); + LayoutCodeDialog dialog = new LayoutCodeDialog(project, file, hasSelection, HELP_ID); + dialog.show(); + + if (dialog.isOK()) { + new FileInEditorProcessor(file, editor, dialog.getRunOptions()).processCode(); + } + } +} diff --git a/platform/lang-impl/src/com/intellij/psi/codeStyle/arrangement/engine/ArrangementEngine.java b/platform/lang-impl/src/com/intellij/psi/codeStyle/arrangement/engine/ArrangementEngine.java index 21d21d91e48f..b3d39daede51 100644 --- a/platform/lang-impl/src/com/intellij/psi/codeStyle/arrangement/engine/ArrangementEngine.java +++ b/platform/lang-impl/src/com/intellij/psi/codeStyle/arrangement/engine/ArrangementEngine.java @@ -57,6 +57,15 @@ import static com.intellij.psi.codeStyle.arrangement.std.StdArrangementTokens.Se * @since 7/20/12 1:56 PM */ public class ArrangementEngine { + private boolean myCodeChanged; + + @Nullable + public String getUserNotificationInfo() { + if (myCodeChanged) { + return "rearranged code"; + } + return null; + } /** * Arranges given PSI root contents that belong to the given ranges. @@ -87,8 +96,9 @@ public class ArrangementEngine { * @param file target PSI root * @param ranges target ranges to use within the given root */ - @SuppressWarnings("MethodMayBeStatic") public void arrange(@NotNull PsiFile file, @NotNull Collection ranges, @Nullable final ArrangementCallback callback) { + myCodeChanged = false; + final Document document = PsiDocumentManager.getInstance(file.getProject()).getDocument(file); if (document == null) { return; @@ -143,7 +153,7 @@ public class ArrangementEngine { } @SuppressWarnings("unchecked") - private static void doArrange(Context context) { + private void doArrange(Context context) { // The general idea is to process entries bottom-up where every processed group belongs to the same parent. We may not bother // with entries text ranges then. We use a list and a stack for achieving that than. // @@ -363,8 +373,8 @@ public class ArrangementEngine { } @SuppressWarnings("unchecked") - private static void doArrange(@NotNull List> wrappers, - @NotNull Context context) { + private void doArrange(@NotNull List> wrappers, + @NotNull Context context) { if (wrappers.isEmpty()) { return; } @@ -408,7 +418,9 @@ public class ArrangementEngine { if (previous != null && previous.equals(previousInitial) || previous == null && previousInitial == null) { final int beforeOffset = arrangedWrapper.getStartOffset(); final int afterOffset = arrangedWrapper.getEndOffset(); - context.changer.insertSection(context, arranged.get(i), newSectionsInfo, parentWrapper, beforeOffset, afterOffset); + + boolean isInserted = context.changer.insertSection(context, arranged.get(i), newSectionsInfo, parentWrapper, beforeOffset, afterOffset); + myCodeChanged = isInserted || myCodeChanged; continue; } } @@ -416,6 +428,7 @@ public class ArrangementEngine { ArrangementEntryWrapper next = i < arranged.size() - 1 ? map.get(arranged.get(i + 1)) : null; context.changer.replace(arrangedWrapper, initialWrapper, previous, next, context); context.changer.insertSection(context, arranged.get(i), newSectionsInfo, arrangedWrapper, initialWrapper, parentWrapper); + myCodeChanged = true; } } @@ -600,7 +613,7 @@ public class ArrangementEngine { @NotNull ArrangementEntryWrapper initial, @Nullable ArrangementEntryWrapper parent); - protected abstract void insertSection(@NotNull Context context, + protected abstract boolean insertSection(@NotNull Context context, @NotNull E entry, @NotNull NewSectionInfo newSectionsInfo, @Nullable ArrangementEntryWrapper parent, int beforeOffset, int afterOffset); @@ -746,18 +759,22 @@ public class ArrangementEngine { } @Override - protected void insertSection(@NotNull Context context, + protected boolean insertSection(@NotNull Context context, @NotNull E entry, @NotNull NewSectionInfo newSectionsInfo, ArrangementEntryWrapper parent, int beforeOffset, int afterOffset) { + boolean isInserted = false; final String afterComment = newSectionsInfo.getEndComment(entry); if (afterComment != null) { insert(context, afterOffset, "\n" + afterComment); + isInserted = true; } final String beforeComment = newSectionsInfo.getStartComment(entry); if (beforeComment != null) { insert(context, beforeOffset, beforeComment + "\n"); + isInserted = true; } + return isInserted; } } @@ -886,24 +903,29 @@ public class ArrangementEngine { } @Override - protected void insertSection(@NotNull Context context, + protected boolean insertSection(@NotNull Context context, @NotNull E entry, @NotNull NewSectionInfo newSectionsInfo, @Nullable ArrangementEntryWrapper parent, int beforeOffset, int afterOffset) { + boolean isInserted = false; int diff = 0; final String afterComment = newSectionsInfo.getEndComment(entry); if (afterComment != null) { insert(context, afterOffset, "\n" + afterComment); diff += afterComment.length() + 1; + isInserted = true; } final String beforeComment = newSectionsInfo.getStartComment(entry); if (beforeComment != null) { insert(context, beforeOffset, beforeComment + "\n"); diff += beforeComment.length() + 1; + isInserted = true; } updateAllWrapperRanges(parent, diff); + + return isInserted; } /** diff --git a/platform/platform-resources-en/src/messages/ActionsBundle.properties b/platform/platform-resources-en/src/messages/ActionsBundle.properties index df4aa168e8fa..727d96663ff5 100644 --- a/platform/platform-resources-en/src/messages/ActionsBundle.properties +++ b/platform/platform-resources-en/src/messages/ActionsBundle.properties @@ -548,6 +548,7 @@ action.OptimizeImports.text=Optimi_ze Imports... action.OptimizeImports.description=Remove unused imports and reorder/reorganize imports action.RearrangeCode.text=Rearrange Code action.RearrangeCode.description=Rearrange code +action.ShowReformatFileDialog.text=Show Reformat File Dialog action.MoveStatementDown.text=Move Statement Do_wn action.MoveStatementDown.description=Move selected statements one line down action.MoveStatementUp.text=Move Statement _Up diff --git a/platform/platform-resources-en/src/messages/CodeInsightBundle.properties b/platform/platform-resources-en/src/messages/CodeInsightBundle.properties index 4c4a14eb79b4..7cc0f894fe06 100644 --- a/platform/platform-resources-en/src/messages/CodeInsightBundle.properties +++ b/platform/platform-resources-en/src/messages/CodeInsightBundle.properties @@ -7,6 +7,7 @@ reformat.directory.dialog.filters=Filters process.scope.directory=Directory ''{0}'' process.scope.project=Project ''{0}'' process.scope.module=Module ''{0}'' +process.scope.changed.files=only VCS changed files reformat.code.accept.button.text=Run process.scope.file=&File {0} reformat.option.selected.text=&Selected text @@ -25,6 +26,7 @@ progress.reformat.and.optimize.common.command.text=Reformat and Optimize Imports progress.reformat.stage.wrapping.blocks=Preparing... progress.reformat.stage.processing.blocks=Calculating changes... progress.reformat.stage.applying.changes=Storing changes... +process.rearrange.code=Rearranging code... process.reformat.code=Reformat Code process.reformat.code.before.commit=Reformat Code Before Commit process.rearrange.code.before.commit=Rearrange Code Before Commit diff --git a/platform/platform-resources/src/idea/Keymap_Default.xml b/platform/platform-resources/src/idea/Keymap_Default.xml index 9f51364fa5a0..5ce4479f0491 100644 --- a/platform/platform-resources/src/idea/Keymap_Default.xml +++ b/platform/platform-resources/src/idea/Keymap_Default.xml @@ -87,6 +87,9 @@ + + + diff --git a/platform/platform-resources/src/idea/LangActions.xml b/platform/platform-resources/src/idea/LangActions.xml index 9db13bc456b1..cc37df8bfb68 100644 --- a/platform/platform-resources/src/idea/LangActions.xml +++ b/platform/platform-resources/src/idea/LangActions.xml @@ -938,5 +938,7 @@ + + diff --git a/platform/platform-tests/testData/codeStyle/formatter/addedLines.java b/platform/platform-tests/testData/codeStyle/formatter/addedLines.java new file mode 100644 index 000000000000..909aa3180175 --- /dev/null +++ b/platform/platform-tests/testData/codeStyle/formatter/addedLines.java @@ -0,0 +1,23 @@ +public class Add { + + public void run() { + int a = 1; + a = 2; + a = 3; + a = 4; + a = 5; + a = 6; + a = 7; + a = 8; + a = 9; + a = 10; + a = 11; + a = 12; + a = 13; + a = 14; + a = 15; + a = 16; + a = 17; + } + +} \ No newline at end of file diff --git a/platform/platform-tests/testData/codeStyle/formatter/addedLines_revision.java b/platform/platform-tests/testData/codeStyle/formatter/addedLines_revision.java new file mode 100644 index 000000000000..282dbcfeab31 --- /dev/null +++ b/platform/platform-tests/testData/codeStyle/formatter/addedLines_revision.java @@ -0,0 +1,7 @@ +public class Add { + + public void run() { + + } + +} \ No newline at end of file diff --git a/platform/platform-tests/testData/codeStyle/formatter/changedAndDeleted.java b/platform/platform-tests/testData/codeStyle/formatter/changedAndDeleted.java new file mode 100644 index 000000000000..6c39c1530e29 --- /dev/null +++ b/platform/platform-tests/testData/codeStyle/formatter/changedAndDeleted.java @@ -0,0 +1,9 @@ +public class ChangedAndDeleted { + + public void run() { + String a = "12"; + int f = 31; + int g = 33; + String b = "3"; + } +} \ No newline at end of file diff --git a/platform/platform-tests/testData/codeStyle/formatter/changedAndDeleted_revision.java b/platform/platform-tests/testData/codeStyle/formatter/changedAndDeleted_revision.java new file mode 100644 index 000000000000..ea11b6b07260 --- /dev/null +++ b/platform/platform-tests/testData/codeStyle/formatter/changedAndDeleted_revision.java @@ -0,0 +1,12 @@ +public class ChangedAndDeleted { + + public void run() { + int a = 2; + int b = 3; + int c = 4; + int d = 12; + int e = 212; + int f = 31; + int g = 33; + } +} \ No newline at end of file diff --git a/platform/platform-tests/testData/codeStyle/formatter/changedSingleLine.java b/platform/platform-tests/testData/codeStyle/formatter/changedSingleLine.java new file mode 100644 index 000000000000..5433fda2e122 --- /dev/null +++ b/platform/platform-tests/testData/codeStyle/formatter/changedSingleLine.java @@ -0,0 +1,6 @@ +public class SingleLine { + + public void run() { + String a = "1"; + } +} \ No newline at end of file diff --git a/platform/platform-tests/testData/codeStyle/formatter/changedSingleLine_revision.java b/platform/platform-tests/testData/codeStyle/formatter/changedSingleLine_revision.java new file mode 100644 index 000000000000..3fab16a816e7 --- /dev/null +++ b/platform/platform-tests/testData/codeStyle/formatter/changedSingleLine_revision.java @@ -0,0 +1,6 @@ +public class SingleLine { + + public void run() { + int a = 2; + } +} \ No newline at end of file diff --git a/platform/platform-tests/testData/codeStyle/formatter/deletedLines.java b/platform/platform-tests/testData/codeStyle/formatter/deletedLines.java new file mode 100644 index 000000000000..4e04525f61ee --- /dev/null +++ b/platform/platform-tests/testData/codeStyle/formatter/deletedLines.java @@ -0,0 +1,7 @@ +public class DeletedLines { + + public void run() { + int a = 1; + int e = 5; + } +} \ No newline at end of file diff --git a/platform/platform-tests/testData/codeStyle/formatter/deletedLines_revision.java b/platform/platform-tests/testData/codeStyle/formatter/deletedLines_revision.java new file mode 100644 index 000000000000..31f696d10c23 --- /dev/null +++ b/platform/platform-tests/testData/codeStyle/formatter/deletedLines_revision.java @@ -0,0 +1,10 @@ +public class DeletedLines { + + public void run() { + int a = 1; + int b = 2; + int c = 3; + int d = 4; + int e = 5; + } +} \ No newline at end of file diff --git a/platform/platform-tests/testData/codeStyle/formatter/insert.java b/platform/platform-tests/testData/codeStyle/formatter/insert.java new file mode 100644 index 000000000000..e34fddabbbc8 --- /dev/null +++ b/platform/platform-tests/testData/codeStyle/formatter/insert.java @@ -0,0 +1,10 @@ +public class Insert { + + public void run() { + int a = 1; + int b = 3; + int c = 4; + String a = "aa"; + String b = "bb"; + } +} \ No newline at end of file diff --git a/platform/platform-tests/testData/codeStyle/formatter/insert_revision.java b/platform/platform-tests/testData/codeStyle/formatter/insert_revision.java new file mode 100644 index 000000000000..34959832ab08 --- /dev/null +++ b/platform/platform-tests/testData/codeStyle/formatter/insert_revision.java @@ -0,0 +1,8 @@ +public class Insert { + + public void run() { + int a = 1; + int b = 3; + int c = 4; + } +} \ No newline at end of file diff --git a/platform/platform-tests/testData/codeStyle/formatter/lotsWhiteSpaces.java b/platform/platform-tests/testData/codeStyle/formatter/lotsWhiteSpaces.java new file mode 100644 index 000000000000..e08516436124 --- /dev/null +++ b/platform/platform-tests/testData/codeStyle/formatter/lotsWhiteSpaces.java @@ -0,0 +1,6 @@ +public class WhiteSpaces { + + public void run() { + + } +} \ No newline at end of file diff --git a/platform/platform-tests/testData/codeStyle/formatter/lotsWhiteSpaces_revision.java b/platform/platform-tests/testData/codeStyle/formatter/lotsWhiteSpaces_revision.java new file mode 100644 index 000000000000..30257da33a66 --- /dev/null +++ b/platform/platform-tests/testData/codeStyle/formatter/lotsWhiteSpaces_revision.java @@ -0,0 +1,25 @@ +public class WhiteSpaces { + + + + + + + + + + + + + + + + + + + + + public void run() { + + } +} \ No newline at end of file diff --git a/platform/platform-tests/testData/codeStyle/formatter/modification.java b/platform/platform-tests/testData/codeStyle/formatter/modification.java new file mode 100644 index 000000000000..f7ebb94eb68b --- /dev/null +++ b/platform/platform-tests/testData/codeStyle/formatter/modification.java @@ -0,0 +1,8 @@ +public class Modify { + + public void run() { + int a = 1; + String ad = "s"; + int d = 6; + } +} \ No newline at end of file diff --git a/platform/platform-tests/testData/codeStyle/formatter/modification_revision.java b/platform/platform-tests/testData/codeStyle/formatter/modification_revision.java new file mode 100644 index 000000000000..647ce258cf1e --- /dev/null +++ b/platform/platform-tests/testData/codeStyle/formatter/modification_revision.java @@ -0,0 +1,10 @@ +public class Modify { + + public void run() { + int a = 1; + int b = 3; + int c = 4; + int f = 5; + int d = 6; + } +} \ No newline at end of file diff --git a/platform/platform-tests/testData/codeStyle/formatter/modifiedLines.java b/platform/platform-tests/testData/codeStyle/formatter/modifiedLines.java new file mode 100644 index 000000000000..70fc21e037c1 --- /dev/null +++ b/platform/platform-tests/testData/codeStyle/formatter/modifiedLines.java @@ -0,0 +1,11 @@ +public class Modified { + + public void run() { + String a = "1"; + String b = "2"; + String c = "3"; + String d = "4"; + String e = "5"; + String f = "6"; + } +} \ No newline at end of file diff --git a/platform/platform-tests/testData/codeStyle/formatter/modifiedLines_revision.java b/platform/platform-tests/testData/codeStyle/formatter/modifiedLines_revision.java new file mode 100644 index 000000000000..a4b60339bfdf --- /dev/null +++ b/platform/platform-tests/testData/codeStyle/formatter/modifiedLines_revision.java @@ -0,0 +1,8 @@ +public class Modified { + + public void run() { + int a = 1; + int b = 2; + int c = 3; + } +} \ No newline at end of file diff --git a/platform/platform-tests/testSrc/com/intellij/codeInsight/actions/ChangedLinesCounterTest.java b/platform/platform-tests/testSrc/com/intellij/codeInsight/actions/ChangedLinesCounterTest.java new file mode 100644 index 000000000000..724e1e1d502d --- /dev/null +++ b/platform/platform-tests/testSrc/com/intellij/codeInsight/actions/ChangedLinesCounterTest.java @@ -0,0 +1,91 @@ +/* + * Copyright 2000-2015 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.codeInsight.actions; + +import com.intellij.openapi.editor.Document; +import com.intellij.psi.PsiFile; +import com.intellij.testFramework.PlatformTestCase; +import com.intellij.testFramework.PlatformTestUtil; +import com.intellij.testFramework.fixtures.LightPlatformCodeInsightFixtureTestCase; +import org.junit.Assert; + +import java.io.File; + +public class ChangedLinesCounterTest extends LightPlatformCodeInsightFixtureTestCase { + + static { + PlatformTestCase.initPlatformLangPrefix(); + } + + @Override + protected String getTestDataPath() { + return PlatformTestUtil.getCommunityPath().replace(File.separatorChar, '/') + + "/platform/platform-tests/testData/" + + "codeStyle/formatter/"; + } + + @Override + public void tearDown() throws Exception { + myFixture.getFile().putUserData(FormatChangedTextUtil.TEST_REVISION_CONTENT, null); + super.tearDown(); + } + + public void doTest(int expectedLinesChanged) { + myFixture.configureByFile(getTestName(true) + "_revision.java"); + PsiFile file = myFixture.getFile(); + CharSequence revisionContent = myFixture.getDocument(file).getCharsSequence(); + + myFixture.configureByFile(getTestName(true) + ".java"); + file = myFixture.getFile(); + Document document = myFixture.getDocument(file); + + int linesChanged = FormatChangedTextUtil.calculateChangedLinesNumber(document, revisionContent); + Assert.assertTrue(linesChanged > 0); + Assert.assertEquals(expectedLinesChanged, linesChanged); + } + + public void testAddedLines() { + doTest(17); + } + + public void testModifiedLines() { + doTest(6); + } + + public void testDeletedLines() { + doTest(3); + } + + public void testChangedSingleLine() { + doTest(1); + } + + public void testChangedAndDeleted() { + doTest(6); + } + + public void testModification() { + doTest(3); + } + + public void testInsert() { + doTest(2); + } + + public void testLotsWhiteSpaces() { + doTest(19); + } +}