From 8fc7a2c0919e77aaeb2dd3c8abdcd8ea5b59bf41 Mon Sep 17 00:00:00 2001 From: "Denis.Zhdanov" Date: Mon, 19 Dec 2011 15:33:54 +0400 Subject: [PATCH] IDEA-51747 Ability to reformat changed lines only Added ability to define that only changed text should be reformatted --- .../psi/codeStyle/CodeStyleManager.java | 21 +- .../actions/AbstractLayoutCodeProcessor.java | 59 +++-- .../actions/FormatChangedTextUtil.java | 211 ++++++++++++++++++ .../actions/LayoutCodeConstants.java | 31 +++ .../codeInsight/actions/LayoutCodeDialog.java | 74 ++++-- .../actions/LayoutProjectCodeDialog.java | 62 +++-- .../actions/OptimizeImportsAction.java | 7 +- .../actions/OptimizeImportsProcessor.java | 16 +- .../ReformatAndOptimizeImportsProcessor.java | 42 ++-- .../actions/ReformatCodeAction.java | 38 ++-- .../actions/ReformatCodeProcessor.java | 62 +++-- .../actions/ReformatFilesDialog.form | 30 +-- .../actions/ReformatFilesDialog.java | 31 ++- .../checkin/ReformatBeforeCheckinHandler.java | 4 +- .../codeStyle/CodeStyleManagerImpl.java | 91 +++++--- .../src/messages/CodeInsightBundle.properties | 1 + .../mvc/MvcModuleStructureSynchronizer.java | 5 +- 17 files changed, 607 insertions(+), 178 deletions(-) create mode 100644 platform/lang-impl/src/com/intellij/codeInsight/actions/FormatChangedTextUtil.java create mode 100644 platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutCodeConstants.java diff --git a/platform/core-api/src/com/intellij/psi/codeStyle/CodeStyleManager.java b/platform/core-api/src/com/intellij/psi/codeStyle/CodeStyleManager.java index 19196becf9a2..5bc78abbac36 100644 --- a/platform/core-api/src/com/intellij/psi/codeStyle/CodeStyleManager.java +++ b/platform/core-api/src/com/intellij/psi/codeStyle/CodeStyleManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2011 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,6 +30,8 @@ import com.intellij.util.ThrowableRunnable; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Collection; + /** * Service for reformatting code fragments, getting names for elements * according to the user's code style and working with import statements and full-qualified names. @@ -123,9 +125,7 @@ public abstract class CodeStyleManager { boolean canChangeWhiteSpacesOnly) throws IncorrectOperationException; /** - * Reformats a range of text in the specified file. This method works faster than - * {@link #reformatRange(com.intellij.psi.PsiElement, int, int)} but invalidates the - * PSI structure for the file. + * Delegates to the {@link #reformatText(PsiFile, Collection)} with the single range defined by the given offsets. * * @param element the file to reformat. * @param startOffset the start of the text range to reformat. @@ -135,7 +135,18 @@ public abstract class CodeStyleManager { public abstract void reformatText(@NotNull PsiFile element, int startOffset, int endOffset) throws IncorrectOperationException; /** - * Reformats the specified range of a file, modifying only line indents and leaving + * Re-formats a ranges of text in the specified file. This method works faster than + * {@link #reformatRange(com.intellij.psi.PsiElement, int, int)} but invalidates the + * PSI structure for the file. + * + * @param element the file to reformat + * @param ranges ranges to process + * @throws IncorrectOperationException if the file to reformat is read-only. + */ + public abstract void reformatText(@NotNull PsiFile element, @NotNull Collection ranges) throws IncorrectOperationException; + + /** + * Re-formats the specified range of a file, modifying only line indents and leaving * all other whitespace intact. * * @param file the file to reformat. 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 492abf905aae..1be1f4e83413 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/actions/AbstractLayoutCodeProcessor.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/AbstractLayoutCodeProcessor.java @@ -66,18 +66,18 @@ public abstract class AbstractLayoutCodeProcessor { private final String myProgressText; private final String myCommandName; private final Runnable myPostRunnable; + private final boolean myProcessChangedTextOnly; - protected AbstractLayoutCodeProcessor(Project project, String commandName, String progressText) { - myProject = project; - myModule = null; - myDirectory = null; - myIncludeSubdirs = true; - myCommandName = commandName; - myProgressText = progressText; - myPostRunnable = null; + protected AbstractLayoutCodeProcessor(Project project, String commandName, String progressText, boolean processChangedTextOnly) { + this(project, (Module)null, commandName, progressText, processChangedTextOnly); } - protected AbstractLayoutCodeProcessor(Project project, Module module, String commandName, String progressText) { + protected AbstractLayoutCodeProcessor(Project project, + @Nullable Module module, + String commandName, + String progressText, + boolean processChangedTextOnly) + { myProject = project; myModule = module; myDirectory = null; @@ -85,9 +85,16 @@ public abstract class AbstractLayoutCodeProcessor { myCommandName = commandName; myProgressText = progressText; myPostRunnable = null; + myProcessChangedTextOnly = processChangedTextOnly; } - protected AbstractLayoutCodeProcessor(Project project, PsiDirectory directory, boolean includeSubdirs, String progressText, String commandName) { + protected AbstractLayoutCodeProcessor(Project project, + PsiDirectory directory, + boolean includeSubdirs, + String progressText, + String commandName, + boolean processChangedTextOnly) + { myProject = project; myModule = null; myDirectory = directory; @@ -95,24 +102,38 @@ public abstract class AbstractLayoutCodeProcessor { myProgressText = progressText; myCommandName = commandName; myPostRunnable = null; + myProcessChangedTextOnly = processChangedTextOnly; } - protected AbstractLayoutCodeProcessor(Project project, PsiFile file, String progressText, String commandName) { + protected AbstractLayoutCodeProcessor(Project project, + PsiFile file, + String progressText, + String commandName, + boolean processChangedTextOnly) + { myProject = project; myModule = null; myFile = file; myProgressText = progressText; myCommandName = commandName; myPostRunnable = null; + myProcessChangedTextOnly = processChangedTextOnly; } - protected AbstractLayoutCodeProcessor(Project project, PsiFile[] files, String progressText, String commandName, Runnable postRunnable) { + protected AbstractLayoutCodeProcessor(Project project, + PsiFile[] files, + String progressText, + String commandName, + @Nullable Runnable postRunnable, + boolean processChangedTextOnly) + { myProject = project; myModule = null; myFiles = filterFiles(files); myProgressText = progressText; myCommandName = commandName; myPostRunnable = postRunnable; + myProcessChangedTextOnly = processChangedTextOnly; } private static PsiFile[] filterFiles(PsiFile[] files){ @@ -128,13 +149,15 @@ public abstract class AbstractLayoutCodeProcessor { /** * Ensures that given file is ready to reformatting and prepares it if necessary. * - * @param file file to process + * @param file file to process + * @param processChangedTextOnly flag that defines is only the changed text (in terms of VCS change) should be processed * @return task that triggers formatting of the given file. Returns value of that task indicates whether formatting * is finished correctly or not (exception occurred, user cancelled formatting etc) * @throws IncorrectOperationException if unexpected exception occurred during formatting */ @NotNull - protected abstract FutureTask preprocessFile(PsiFile file) throws IncorrectOperationException; + protected abstract FutureTask preprocessFile(@NotNull PsiFile file, boolean processChangedTextOnly) + throws IncorrectOperationException; public void run() { if (myDirectory != null){ @@ -154,7 +177,7 @@ public abstract class AbstractLayoutCodeProcessor { } } - private void runProcessFile(final PsiFile file) { + private void runProcessFile(@NotNull final PsiFile file) { Document document = PsiDocumentManager.getInstance(myProject).getDocument(file); if (document == null) { @@ -174,7 +197,7 @@ public abstract class AbstractLayoutCodeProcessor { public void run() { if (!checkFileWritable(file)) return; try{ - resultRunnable[0] = preprocessFile(file); + resultRunnable[0] = preprocessFile(file, myProcessChangedTextOnly); } catch(IncorrectOperationException e){ LOG.error(e); @@ -223,7 +246,7 @@ public abstract class AbstractLayoutCodeProcessor { } if (file.isWritable()){ try{ - tasks.add(preprocessFile(file)); + tasks.add(preprocessFile(file, myProcessChangedTextOnly)); } catch(IncorrectOperationException e){ LOG.error(e); @@ -487,7 +510,7 @@ public abstract class AbstractLayoutCodeProcessor { } public void runWithoutProgress() throws IncorrectOperationException { - final Runnable runnable = preprocessFile(myFile); + final Runnable runnable = preprocessFile(myFile, myProcessChangedTextOnly); runnable.run(); } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/actions/FormatChangedTextUtil.java b/platform/lang-impl/src/com/intellij/codeInsight/actions/FormatChangedTextUtil.java new file mode 100644 index 000000000000..6dd5c90dc51d --- /dev/null +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/FormatChangedTextUtil.java @@ -0,0 +1,211 @@ +/* + * Copyright 2000-2011 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.codeInsight.actions; + +import com.intellij.openapi.application.ReadAction; +import com.intellij.openapi.application.Result; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.markup.RangeHighlighter; +import com.intellij.openapi.module.ModifiableModuleModel; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ModuleRootManager; +import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.vcs.changes.Change; +import com.intellij.openapi.vcs.changes.ChangeListManager; +import com.intellij.openapi.vcs.ex.LineStatusTracker; +import com.intellij.openapi.vcs.ex.Range; +import com.intellij.openapi.vcs.impl.LineStatusTrackerManager; +import com.intellij.openapi.vcs.impl.LineStatusTrackerManagerI; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiDirectory; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiFile; +import org.jetbrains.annotations.NotNull; + +import java.util.*; + +/** + * Contains utility methods for 'format only changed text' (in terms of VCS changes). + * + * @author Denis Zhdanov + * @since 12/19/11 10:55 AM + */ +public class FormatChangedTextUtil { + + private FormatChangedTextUtil() { + } + + /** + * Allows to answer if given file has changes in comparison with VCS. + * + * @param file target file + * @return true if given file has changes; false otherwise + */ + public static boolean hasChanges(@NotNull PsiFile file) { + final Project project = file.getProject(); + 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; + } + } + + 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(); + if (ranges == null || ranges.isEmpty()) { + return false; + } + for (Range range : ranges) { + if (range.getType() != Range.DELETED) { + return true; + } + } + return false; + } + + /** + * Allows to answer if any file below the given directory (any level of nesting) has changes in comparison with VCS. + * + * @param directory target directory to check + * @return true if any file below the given directory has changes in comparison with VCS; + * false otherwise + */ + public static boolean hasChanges(@NotNull PsiDirectory directory) { + return hasChanges(directory.getVirtualFile(), directory.getProject()); + } + + /** + * Allows to answer if given file or any file below the given directory (any level of nesting) has changes in comparison with VCS. + * + * @param file target directory to check + * @param project target project + * @return true if given file or any file below the given directory has changes in comparison with VCS; + * false otherwise + */ + public static boolean hasChanges(@NotNull VirtualFile file, @NotNull Project project) { + final Collection changes = ChangeListManager.getInstance(project).getChangesIn(file); + for (Change change : changes) { + if (change.getType() == Change.Type.NEW || change.getType() == Change.Type.MODIFICATION) { + return true; + } + } + return false; + } + + /** + * Allows to answer if any file that belongs to the given module has changes in comparison with VCS. + * + * @param module target module to check + * @return true if any file that belongs to the given module has changes in comparison with VCS + * false otherwise + */ + public static boolean hasChanges(@NotNull Module module) { + final ModuleRootManager rootManager = ModuleRootManager.getInstance(module); + for (VirtualFile root : rootManager.getSourceRoots()) { + if (hasChanges(root, module.getProject())) { + return true; + } + } + return false; + } + + /** + * Allows to answer if any file that belongs to the given project has changes in comparison with VCS. + * + * @param project target project to check + * @return true if any file that belongs to the given project has changes in comparison with VCS + * false otherwise + */ + public static boolean hasChanges(@NotNull final Project project) { + final ModifiableModuleModel moduleModel = new ReadAction() { + protected void run(Result result) throws Throwable { + result.setResult(ModuleManager.getInstance(project).getModifiableModel()); + } + }.execute().getResultObject(); + try { + for (Module module : moduleModel.getModules()) { + if (hasChanges(module)) { + return true; + } + } + return false; + } + finally { + moduleModel.dispose(); + } + } + + /** + * Allows to ask for the changed text of the given file (in comparison with VCS). + * + * @param file target file + * @return collection of changed regions for the given file + */ + @NotNull + public static Collection getChanges(@NotNull PsiFile file) { + final Set defaultResult = Collections.singleton(file.getTextRange()); + final VirtualFile virtualFile = file.getVirtualFile(); + if (virtualFile != null) { + final Change change = ChangeListManager.getInstance(file.getProject()).getChange(virtualFile); + if (change != null && change.getType() == Change.Type.NEW) { + return defaultResult; + } + } + + final LineStatusTrackerManagerI manager = LineStatusTrackerManager.getInstance(file.getProject()); + if (manager == null) { + return defaultResult; + } + final Document document = PsiDocumentManager.getInstance(file.getProject()).getDocument(file); + if (document == null) { + return defaultResult; + } + final LineStatusTracker lineStatusTracker = manager.getLineStatusTracker(document); + if (lineStatusTracker == null) { + return defaultResult; + } + final List ranges = lineStatusTracker.getRanges(); + if (ranges == null || ranges.isEmpty()) { + return defaultResult; + } + + List result = new ArrayList(); + for (Range range : ranges) { + if (range.getType() != Range.DELETED) { + final RangeHighlighter highlighter = range.getHighlighter(); + if (highlighter != null) { + result.add(new TextRange(highlighter.getStartOffset(), highlighter.getEndOffset())); + } + } + } + return result; + } +} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutCodeConstants.java b/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutCodeConstants.java new file mode 100644 index 000000000000..06c5a4b840c2 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutCodeConstants.java @@ -0,0 +1,31 @@ +/* + * Copyright 2000-2011 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.codeInsight.actions; + +import org.jetbrains.annotations.NonNls; + +/** + * @author Denis Zhdanov + * @since 12/16/11 6:10 PM + */ +public class LayoutCodeConstants { + + public static final @NonNls String OPTIMIZE_IMPORTS_KEY = "LayoutCode.optimizeImports"; + public static final @NonNls String PROCESS_CHANGED_TEXT_KEY = "LayoutCode.processChangedText"; + + private LayoutCodeConstants() { + } +} 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 5b23d6f2a2cf..cfbc5907b75d 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutCodeDialog.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutCodeDialog.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2011 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,7 +26,7 @@ import com.intellij.openapi.ui.DialogWrapper; import com.intellij.psi.PsiDirectory; import com.intellij.psi.PsiFile; import com.intellij.util.ui.OptionsDialog; -import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; @@ -37,23 +37,23 @@ import java.awt.event.ItemEvent; import java.awt.event.ItemListener; public class LayoutCodeDialog extends DialogWrapper { - private final PsiFile myFile; + private final PsiFile myFile; @Nullable private final PsiDirectory myDirectory; - private final Boolean myTextSelected; + private final Boolean myTextSelected; private JRadioButton myRbFile; private JRadioButton myRbSelectedText; private JRadioButton myRbDirectory; - private JCheckBox myCbIncludeSubdirs; - private JCheckBox myCbOptimizeImports; - private JCheckBox myDoNotAskMeCheckBox; + private JCheckBox myCbIncludeSubdirs; + private JCheckBox myCbOptimizeImports; + private JCheckBox myCbOnlyVcsChangedRegions; + private JCheckBox myDoNotAskMeCheckBox; - public static final @NonNls String OPTIMIZE_IMPORTS_KEY = "LayoutCode.optimizeImports"; private final String myHelpId; - public LayoutCodeDialog(Project project, - String title, - PsiFile file, + public LayoutCodeDialog(@NotNull Project project, + @NotNull String title, + @Nullable PsiFile file, @Nullable PsiDirectory directory, Boolean isTextSelected, final String helpId) { @@ -84,7 +84,7 @@ public class LayoutCodeDialog extends DialogWrapper { } myCbIncludeSubdirs.setSelected(true); - myCbOptimizeImports.setSelected(isOptmizeImportsOptionOn()); + myCbOptimizeImports.setSelected(PropertiesComponent.getInstance().getBoolean(LayoutCodeConstants.OPTIMIZE_IMPORTS_KEY, false)); ItemListener listener = new ItemListener() { public void itemStateChanged(ItemEvent e) { @@ -99,18 +99,17 @@ public class LayoutCodeDialog extends DialogWrapper { updateState(); } - private static boolean isOptmizeImportsOptionOn() { - return Boolean.toString(true).equals(PropertiesComponent.getInstance().getValue(OPTIMIZE_IMPORTS_KEY)); - } - - private static void setOptimizeImportsOption(boolean state) { - PropertiesComponent.getInstance().setValue(OPTIMIZE_IMPORTS_KEY, Boolean.toString(state)); - } - private void updateState() { myCbIncludeSubdirs.setEnabled(myRbDirectory.isSelected()); myCbOptimizeImports.setEnabled( - !myRbSelectedText.isSelected() && !(myFile != null && LanguageImportStatements.INSTANCE.forFile(myFile) == null && myRbFile.isSelected())); + !myRbSelectedText.isSelected() && + !(myFile != null && LanguageImportStatements.INSTANCE.forFile(myFile) == null && myRbFile.isSelected())); + + final boolean canTargetVcsChanges = canTargetVcsRegions(); + myCbOnlyVcsChangedRegions.setEnabled(canTargetVcsChanges); + myCbOnlyVcsChangedRegions.setSelected( + canTargetVcsChanges && PropertiesComponent.getInstance().getBoolean(LayoutCodeConstants.PROCESS_CHANGED_TEXT_KEY, false) + ); myDoNotAskMeCheckBox.setEnabled(!myRbDirectory.isSelected()); myRbDirectory.setEnabled(!myDoNotAskMeCheckBox.isSelected()); @@ -163,6 +162,10 @@ public class LayoutCodeDialog extends DialogWrapper { panel.add(myCbOptimizeImports, 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); @@ -201,7 +204,7 @@ public class LayoutCodeDialog extends DialogWrapper { public boolean isProcessWholeFile() { return myRbFile.isSelected(); } - + public boolean isProcessDirectory() { return myRbDirectory.isSelected(); } @@ -214,12 +217,35 @@ public class LayoutCodeDialog extends DialogWrapper { return myCbOptimizeImports.isSelected(); } + public boolean isProcessOnlyChangedText() { + return myCbOnlyVcsChangedRegions.isEnabled() && myCbOnlyVcsChangedRegions.isSelected(); + } + boolean isDoNotAskMe() { - return myDoNotAskMeCheckBox.isSelected(); + return myDoNotAskMeCheckBox.isSelected(); } protected void doOKAction() { super.doOKAction(); - setOptimizeImportsOption(isOptimizeImports()); + PropertiesComponent.getInstance().setValue(LayoutCodeConstants.OPTIMIZE_IMPORTS_KEY, Boolean.toString(isOptimizeImports())); + PropertiesComponent.getInstance().setValue(LayoutCodeConstants.PROCESS_CHANGED_TEXT_KEY, Boolean.toString(isProcessOnlyChangedText())); + } + + private boolean canTargetVcsRegions() { + if (isProcessSelectedText()) { + return false; + } + + if (isProcessWholeFile()) { + return FormatChangedTextUtil.hasChanges(myFile); + } + + if (isProcessDirectory()) { + if (myDirectory == null) { + return false; + } + return FormatChangedTextUtil.hasChanges(myDirectory); + } + return false; } } 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 f4242a821fc2..0cdc67c67574 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutProjectCodeDialog.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutProjectCodeDialog.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2011 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,9 +19,12 @@ package com.intellij.codeInsight.actions; import com.intellij.codeInsight.CodeInsightBundle; import com.intellij.ide.util.PropertiesComponent; import com.intellij.openapi.help.HelpManager; +import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; @@ -30,36 +33,51 @@ import java.awt.*; * @author max */ public class LayoutProjectCodeDialog extends DialogWrapper { - private static @NonNls final String OPTIMIZE_IMPORTS_KEY = "LayoutCode.optimizeImports"; private static @NonNls final String HELP_ID = "editing.codeReformatting"; - private final String myText; - private final boolean mySuggestOptmizeImports; + private final String myText; + private final boolean mySuggestOptimizeImports; + private final Project myProject; + private final Module myModule; + private JCheckBox myCbOptimizeImports; + private JCheckBox myCbOnlyVcsChangedRegions; - public LayoutProjectCodeDialog(Project project, String title, String text, boolean suggestOptmizeImports) { + public LayoutProjectCodeDialog(@NotNull Project project, + @Nullable Module module, + String title, + String text, + boolean suggestOptimizeImports) + { super(project, false); myText = text; - mySuggestOptmizeImports = suggestOptmizeImports; + mySuggestOptimizeImports = suggestOptimizeImports; + myProject = project; + myModule = module; + setOKButtonText(CodeInsightBundle.message("reformat.code.accept.button.text")); setTitle(title); init(); } protected JComponent createCenterPanel() { - if (!mySuggestOptmizeImports) return new JLabel(myText); - JPanel panel = new JPanel(new GridLayout(2, 1)); + if (!mySuggestOptimizeImports) return new JLabel(myText); + JPanel panel = new JPanel(new GridLayout(3, 1)); panel.add(new JLabel(myText)); myCbOptimizeImports = new JCheckBox(CodeInsightBundle.message("reformat.option.optimize.imports")); panel.add(myCbOptimizeImports); - myCbOptimizeImports.setSelected(Boolean.toString(true).equals(PropertiesComponent.getInstance().getValue(OPTIMIZE_IMPORTS_KEY))); + myCbOptimizeImports.setSelected(PropertiesComponent.getInstance().getBoolean(LayoutCodeConstants.OPTIMIZE_IMPORTS_KEY, false)); + + myCbOnlyVcsChangedRegions = new JCheckBox(CodeInsightBundle.message("reformat.option.vcs.changed.region")); + panel.add(myCbOnlyVcsChangedRegions); + final boolean canTargetVcsRegions = canTargetVcsRegions(); + myCbOnlyVcsChangedRegions.setEnabled(canTargetVcsRegions); + myCbOnlyVcsChangedRegions.setSelected( + canTargetVcsRegions && PropertiesComponent.getInstance().getBoolean(LayoutCodeConstants.PROCESS_CHANGED_TEXT_KEY, false) + ); return panel; } - - private void setOptimizeImportsOption(boolean state) { - PropertiesComponent.getInstance().setValue(OPTIMIZE_IMPORTS_KEY, Boolean.toString(state)); - } - + protected Action[] createActions() { return new Action[]{getOKAction(), getCancelAction(), getHelpAction()}; } @@ -70,12 +88,24 @@ public class LayoutProjectCodeDialog extends DialogWrapper { protected void doOKAction() { super.doOKAction(); - if (mySuggestOptmizeImports) { - setOptimizeImportsOption(isOptimizeImports()); + if (mySuggestOptimizeImports) { + PropertiesComponent.getInstance().setValue(LayoutCodeConstants.OPTIMIZE_IMPORTS_KEY, Boolean.toString(isOptimizeImports())); } } public boolean isOptimizeImports() { return myCbOptimizeImports.isSelected(); } + + public boolean isProcessOnlyChangedText() { + return myCbOnlyVcsChangedRegions.isEnabled() && myCbOnlyVcsChangedRegions.isSelected(); + } + + private boolean canTargetVcsRegions() { + if (myModule != null) { + return FormatChangedTextUtil.hasChanges(myModule); + } + + return FormatChangedTextUtil.hasChanges(myProject); + } } 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 59c18bc50d72..951dbecd7c3c 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/actions/OptimizeImportsAction.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/OptimizeImportsAction.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2011 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -54,7 +54,7 @@ public class OptimizeImportsAction extends AnAction { if (file == null) return; dir = file.getContainingDirectory(); } - else if (ReformatCodeAction.areFiles(files)) { + else if (files != null && ReformatCodeAction.areFiles(files)) { final ReadonlyStatusHandler.OperationStatus operationStatus = ReadonlyStatusHandler.getInstance(project).ensureFilesWritable(files); if (!operationStatus.hasReadonlyFiles()) { new OptimizeImportsProcessor(project, ReformatCodeAction.convertToPsiFiles(files, project), null).run(); @@ -73,7 +73,8 @@ public class OptimizeImportsAction extends AnAction { else { text = CodeInsightBundle.message("process.scope.project", projectContext.getPresentableUrl()); } - LayoutProjectCodeDialog dialog = new LayoutProjectCodeDialog(project, CodeInsightBundle.message("process.optimize.imports"), text, false); + LayoutProjectCodeDialog dialog + = new LayoutProjectCodeDialog(project, null, CodeInsightBundle.message("process.optimize.imports"), text, false); dialog.show(); if (!dialog.isOK()) return; if (moduleContext != null) { 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 34a66346aead..d16b9dcf3b46 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/actions/OptimizeImportsProcessor.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/OptimizeImportsProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2011 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,19 +34,19 @@ public class OptimizeImportsProcessor extends AbstractLayoutCodeProcessor { private static final String COMMAND_NAME = CodeInsightBundle.message("process.optimize.imports"); public OptimizeImportsProcessor(Project project) { - super(project, COMMAND_NAME, PROGRESS_TEXT); + super(project, COMMAND_NAME, PROGRESS_TEXT, false); } public OptimizeImportsProcessor(Project project, Module module) { - super(project, module, COMMAND_NAME, PROGRESS_TEXT); + super(project, module, COMMAND_NAME, PROGRESS_TEXT, false); } public OptimizeImportsProcessor(Project project, PsiDirectory directory, boolean includeSubdirs) { - super(project, directory, includeSubdirs, PROGRESS_TEXT, COMMAND_NAME); + super(project, directory, includeSubdirs, PROGRESS_TEXT, COMMAND_NAME, false); } public OptimizeImportsProcessor(Project project, PsiFile file) { - super(project, file, PROGRESS_TEXT, COMMAND_NAME); + super(project, file, PROGRESS_TEXT, COMMAND_NAME, false); } public OptimizeImportsProcessor(Project project, PsiFile[] files, Runnable postRunnable) { @@ -54,11 +54,13 @@ public class OptimizeImportsProcessor extends AbstractLayoutCodeProcessor { } public OptimizeImportsProcessor(Project project, PsiFile[] files, String commandName, Runnable postRunnable) { - super(project, files, PROGRESS_TEXT, commandName, postRunnable); + super(project, files, PROGRESS_TEXT, commandName, postRunnable, false); } @NotNull - protected FutureTask preprocessFile(final PsiFile file) throws IncorrectOperationException { + protected FutureTask preprocessFile(@NotNull final PsiFile file, boolean processChangedTextOnly) + throws IncorrectOperationException + { final ImportOptimizer optimizer = LanguageImportStatements.INSTANCE.forFile(file); Runnable runnable = optimizer != null ? optimizer.processFile(file) : EmptyRunnable.getInstance(); return new FutureTask(runnable, true); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatAndOptimizeImportsProcessor.java b/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatAndOptimizeImportsProcessor.java index 41bf48a3e5e6..cfef35956d5b 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatAndOptimizeImportsProcessor.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatAndOptimizeImportsProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2011 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,40 +38,44 @@ public class ReformatAndOptimizeImportsProcessor extends AbstractLayoutCodeProce private final OptimizeImportsProcessor myOptimizeImportsProcessor; private final ReformatCodeProcessor myReformatCodeProcessor; - public ReformatAndOptimizeImportsProcessor(Project project) { - super(project, COMMAND_NAME, PROGRESS_TEXT); + public ReformatAndOptimizeImportsProcessor(Project project, boolean processChangedTextOnly) { + super(project, COMMAND_NAME, PROGRESS_TEXT, processChangedTextOnly); myOptimizeImportsProcessor = new OptimizeImportsProcessor(project); - myReformatCodeProcessor = new ReformatCodeProcessor(project); + myReformatCodeProcessor = new ReformatCodeProcessor(project, processChangedTextOnly); } - public ReformatAndOptimizeImportsProcessor(Project project, Module module) { - super(project, module, COMMAND_NAME, PROGRESS_TEXT); + public ReformatAndOptimizeImportsProcessor(Project project, Module module, boolean processChangedTextOnly) { + super(project, module, COMMAND_NAME, PROGRESS_TEXT, processChangedTextOnly); myOptimizeImportsProcessor = new OptimizeImportsProcessor(project, module); - myReformatCodeProcessor = new ReformatCodeProcessor(project, module); + myReformatCodeProcessor = new ReformatCodeProcessor(project, module, processChangedTextOnly); } - public ReformatAndOptimizeImportsProcessor(Project project, PsiFile[] files) { - super(project, files, PROGRESS_TEXT, COMMAND_NAME, null); + public ReformatAndOptimizeImportsProcessor(Project project, PsiFile[] files, boolean processChangedTextOnly) { + super(project, files, PROGRESS_TEXT, COMMAND_NAME, null, processChangedTextOnly); myOptimizeImportsProcessor = new OptimizeImportsProcessor(project); - myReformatCodeProcessor = new ReformatCodeProcessor(project); + myReformatCodeProcessor = new ReformatCodeProcessor(project, processChangedTextOnly); } - public ReformatAndOptimizeImportsProcessor(Project project, PsiDirectory directory, boolean includeSubdirs) { - super(project, directory, includeSubdirs, PROGRESS_TEXT, COMMAND_NAME); + public ReformatAndOptimizeImportsProcessor(Project project, + PsiDirectory directory, + boolean includeSubdirs, + boolean processChangedTextOnly) + { + super(project, directory, includeSubdirs, PROGRESS_TEXT, COMMAND_NAME, processChangedTextOnly); myOptimizeImportsProcessor = new OptimizeImportsProcessor(project, directory, includeSubdirs); - myReformatCodeProcessor = new ReformatCodeProcessor(project, directory, includeSubdirs); + myReformatCodeProcessor = new ReformatCodeProcessor(project, directory, includeSubdirs, processChangedTextOnly); } - public ReformatAndOptimizeImportsProcessor(Project project, PsiFile file) { - super(project, file, PROGRESS_TEXT, COMMAND_NAME); + public ReformatAndOptimizeImportsProcessor(Project project, PsiFile file, boolean processChangedTextOnly) { + super(project, file, PROGRESS_TEXT, COMMAND_NAME, processChangedTextOnly); myOptimizeImportsProcessor = new OptimizeImportsProcessor(project, file); - myReformatCodeProcessor = new ReformatCodeProcessor(project, file, null); + myReformatCodeProcessor = new ReformatCodeProcessor(project, file, null, processChangedTextOnly); } @NotNull - protected FutureTask preprocessFile(PsiFile file) throws IncorrectOperationException { - final FutureTask reformatTask = myReformatCodeProcessor.preprocessFile(file); - final FutureTask optimizeImportsTask = myOptimizeImportsProcessor.preprocessFile(file); + protected FutureTask preprocessFile(@NotNull PsiFile file, boolean processChangedTextOnly) throws IncorrectOperationException { + final FutureTask reformatTask = myReformatCodeProcessor.preprocessFile(file, processChangedTextOnly); + final FutureTask optimizeImportsTask = myOptimizeImportsProcessor.preprocessFile(file, false); return new FutureTask(new Callable() { @Override public Boolean call() throws Exception { 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 918f2f22451f..dfbc027222fb 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatCodeAction.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatCodeAction.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2011 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -75,20 +75,22 @@ public class ReformatCodeAction extends AnAction implements DumbAware { else if (areFiles(files)) { final ReadonlyStatusHandler.OperationStatus operationStatus = ReadonlyStatusHandler.getInstance(project).ensureFilesWritable(files); if (!operationStatus.hasReadonlyFiles()) { - final ReformatFilesDialog reformatFilesDialog = new ReformatFilesDialog(project); + final ReformatFilesDialog reformatFilesDialog = new ReformatFilesDialog(project, files); reformatFilesDialog.show(); if (!reformatFilesDialog.isOK()) return; if (reformatFilesDialog.optimizeImports() && !DumbService.getInstance(project).isDumb()) { - new ReformatAndOptimizeImportsProcessor(project, convertToPsiFiles(files, project)).run(); + new ReformatAndOptimizeImportsProcessor( + project, convertToPsiFiles(files, project), reformatFilesDialog.isProcessOnlyChangedText() + ).run(); } else { - new ReformatCodeProcessor(project, convertToPsiFiles(files, project), null).run(); + new ReformatCodeProcessor(project, convertToPsiFiles(files, project), null, reformatFilesDialog.isProcessOnlyChangedText()).run(); } } return; } - else{ + else { Project projectContext = PlatformDataKeys.PROJECT_CONTEXT.getData(dataContext); Module moduleContext = LangDataKeys.MODULE_CONTEXT.getData(dataContext); @@ -98,26 +100,27 @@ public class ReformatCodeAction extends AnAction implements DumbAware { text = CodeInsightBundle.message("process.scope.module", moduleContext.getModuleFilePath()); } else { - text = CodeInsightBundle.message("process.scope.project", projectContext.getPresentableUrl()); + text = CodeInsightBundle.message("process.scope.project", project.getPresentableUrl()); } - LayoutProjectCodeDialog dialog = new LayoutProjectCodeDialog(project, CodeInsightBundle.message("process.reformat.code"), text, true); + LayoutProjectCodeDialog dialog + = new LayoutProjectCodeDialog(project, moduleContext, CodeInsightBundle.message("process.reformat.code"), text, true); dialog.show(); if (!dialog.isOK()) return; if (dialog.isOptimizeImports() && !DumbService.getInstance(project).isDumb()) { if (moduleContext != null) { - new ReformatAndOptimizeImportsProcessor(project, moduleContext).run(); + new ReformatAndOptimizeImportsProcessor(project, moduleContext, dialog.isProcessOnlyChangedText()).run(); } else { - new ReformatAndOptimizeImportsProcessor(projectContext).run(); + new ReformatAndOptimizeImportsProcessor(project, dialog.isProcessOnlyChangedText()).run(); } } else { if (moduleContext != null) { - new ReformatCodeProcessor(project, moduleContext).run(); + new ReformatCodeProcessor(project, moduleContext, dialog.isProcessOnlyChangedText()).run(); } else { - new ReformatCodeProcessor(projectContext).run(); + new ReformatCodeProcessor(project, dialog.isProcessOnlyChangedText()).run(); } } return; @@ -140,6 +143,7 @@ public class ReformatCodeAction extends AnAction implements DumbAware { boolean optimizeImports = ReformatFilesDialog.isOptmizeImportsOptionOn(); boolean processWholeFile = false; + boolean processChangedTextOnly = false; if (EditorSettingsExternalizable.getInstance().getOptions().SHOW_REFORMAT_DIALOG || (file == null && dir != null)) { final LayoutCodeDialog dialog = new LayoutCodeDialog(project, CodeInsightBundle.message("process.reformat.code"), file, dir, hasSelection ? Boolean.TRUE : Boolean.FALSE, HELP_ID); @@ -149,12 +153,14 @@ public class ReformatCodeAction extends AnAction implements DumbAware { updateShowDialogSetting(dialog, "\"Reformat Code\" dialog disabled"); optimizeImports = dialog.isOptimizeImports(); processWholeFile = dialog.isProcessWholeFile(); + processChangedTextOnly = dialog.isProcessOnlyChangedText(); + if (dialog.isProcessDirectory()){ if (optimizeImports) { - new ReformatAndOptimizeImportsProcessor(project, dir, dialog.isIncludeSubdirectories()).run(); + new ReformatAndOptimizeImportsProcessor(project, dir, dialog.isIncludeSubdirectories(), processChangedTextOnly).run(); } else { - new ReformatCodeProcessor(project, dir, dialog.isIncludeSubdirectories()).run(); + new ReformatCodeProcessor(project, dir, dialog.isIncludeSubdirectories(), processChangedTextOnly).run(); } return; } @@ -170,14 +176,14 @@ public class ReformatCodeAction extends AnAction implements DumbAware { if (optimizeImports && range == null) { if (file != null || dir == null) { - new ReformatAndOptimizeImportsProcessor(project, file).run(); + new ReformatAndOptimizeImportsProcessor(project, file, processChangedTextOnly).run(); } else { - new ReformatAndOptimizeImportsProcessor(project, dir, true).run(); + new ReformatAndOptimizeImportsProcessor(project, dir, true, processChangedTextOnly).run(); } } else { - new ReformatCodeProcessor(project, file, range).run(); + new ReformatCodeProcessor(project, file, range, processChangedTextOnly).run(); } } 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 4aadcbfdb8f3..f75a4c36d6e2 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatCodeProcessor.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatCodeProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2011 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,7 +27,10 @@ import com.intellij.psi.PsiFile; import com.intellij.psi.codeStyle.CodeStyleManager; import com.intellij.util.IncorrectOperationException; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; +import java.util.Collection; import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; @@ -37,53 +40,66 @@ public class ReformatCodeProcessor extends AbstractLayoutCodeProcessor { private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.actions.ReformatCodeProcessor"); - private final TextRange myRange; + private final Collection myRanges = new ArrayList(); private static final String PROGRESS_TEXT = CodeInsightBundle.message("reformat.progress.common.text"); - public ReformatCodeProcessor(Project project) { - super(project, COMMAND_NAME, PROGRESS_TEXT); - myRange = null; + public ReformatCodeProcessor(Project project, boolean processChangedTextOnly) { + super(project, COMMAND_NAME, PROGRESS_TEXT, processChangedTextOnly); } - public ReformatCodeProcessor(Project project, Module module) { - super(project, module, COMMAND_NAME, PROGRESS_TEXT); - myRange = null; + public ReformatCodeProcessor(Project project, Module module, boolean processChangedTextOnly) { + super(project, module, COMMAND_NAME, PROGRESS_TEXT, processChangedTextOnly); } - public ReformatCodeProcessor(Project project, PsiDirectory directory, boolean includeSubdirs) { - super(project, directory, includeSubdirs, PROGRESS_TEXT, COMMAND_NAME); - myRange = null; + public ReformatCodeProcessor(Project project, PsiDirectory directory, boolean includeSubdirs, boolean processChangedTextOnly) { + super(project, directory, includeSubdirs, PROGRESS_TEXT, COMMAND_NAME, processChangedTextOnly); } - public ReformatCodeProcessor(Project project, PsiFile file, TextRange range) { - super(project, file, PROGRESS_TEXT, COMMAND_NAME); - myRange = range; + public ReformatCodeProcessor(Project project, PsiFile file, @Nullable TextRange range, boolean processChangedTextOnly) { + super(project, file, PROGRESS_TEXT, COMMAND_NAME, processChangedTextOnly); + if (range != null) { + myRanges.add(range); + } } - public ReformatCodeProcessor(Project project, PsiFile[] files, Runnable postRunnable) { - this(project, files, COMMAND_NAME, postRunnable); + public ReformatCodeProcessor(Project project, PsiFile[] files, Runnable postRunnable, boolean processChangedTextOnly) { + this(project, files, COMMAND_NAME, postRunnable, processChangedTextOnly); } - - public ReformatCodeProcessor(Project project, PsiFile[] files, String commandName, Runnable postRunnable) { - super(project, files, PROGRESS_TEXT, commandName, postRunnable); - myRange = null; + + public ReformatCodeProcessor(Project project, + PsiFile[] files, + String commandName, + Runnable postRunnable, + boolean processChangedTextOnly) + { + super(project, files, PROGRESS_TEXT, commandName, postRunnable, processChangedTextOnly); } @NotNull - protected FutureTask preprocessFile(final PsiFile file) throws IncorrectOperationException { + protected FutureTask preprocessFile(@NotNull final PsiFile file, final boolean processChangedTextOnly) + throws IncorrectOperationException + { return new FutureTask(new Callable() { @Override public Boolean call() throws Exception { FormattingProgressTask.FORMATTING_CANCELLED_FLAG.set(false); try { - TextRange range = myRange == null ? file.getTextRange() : myRange; - CodeStyleManager.getInstance(myProject).reformatText(file, range.getStartOffset(), range.getEndOffset()); + if (myRanges.isEmpty() && processChangedTextOnly) { + myRanges.addAll(FormatChangedTextUtil.getChanges(file)); + } + if (myRanges.isEmpty()) { + myRanges.add(file.getTextRange()); + } + CodeStyleManager.getInstance(myProject).reformatText(file, myRanges); return !FormattingProgressTask.FORMATTING_CANCELLED_FLAG.get(); } catch (IncorrectOperationException e) { LOG.error(e); return false; } + finally { + myRanges.clear(); + } } }); } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatFilesDialog.form b/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatFilesDialog.form index 5b069fca7cda..d5f043e108ab 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatFilesDialog.form +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatFilesDialog.form @@ -1,27 +1,24 @@
- + - - + - + - - + - - + @@ -29,8 +26,7 @@ - - + @@ -38,16 +34,22 @@ - - + + + + + + + + + - - + 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 9e22ffa8977b..61af5d4e709b 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatFilesDialog.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/ReformatFilesDialog.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2011 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,17 +20,34 @@ import com.intellij.codeInsight.CodeInsightBundle; import com.intellij.ide.util.PropertiesComponent; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.NotNull; import javax.swing.*; public class ReformatFilesDialog extends DialogWrapper { private JPanel myPanel; private JCheckBox myOptimizeImports; + private JCheckBox myOnlyChangedText; + private final VirtualFile[] myFiles; - public ReformatFilesDialog(Project project) { + public ReformatFilesDialog(@NotNull Project project, @NotNull VirtualFile[] files) { super(project, true); + myFiles = files; setTitle(CodeInsightBundle.message("dialog.reformat.files.title")); myOptimizeImports.setSelected(isOptmizeImportsOptionOn()); + boolean canTargetVcsChanges = false; + for (VirtualFile file : files) { + if (FormatChangedTextUtil.hasChanges(file, project)) { + canTargetVcsChanges = true; + break; + } + } + myOnlyChangedText.setEnabled(canTargetVcsChanges); + myOnlyChangedText.setSelected( + canTargetVcsChanges && PropertiesComponent.getInstance().getBoolean(LayoutCodeConstants.PROCESS_CHANGED_TEXT_KEY, false) + ); + myOptimizeImports.setSelected(isOptmizeImportsOptionOn()); init(); } @@ -42,13 +59,19 @@ public class ReformatFilesDialog extends DialogWrapper { return myOptimizeImports.isSelected(); } + public boolean isProcessOnlyChangedText() { + return myOnlyChangedText.isEnabled() && myOnlyChangedText.isSelected(); + } + protected void doOKAction() { - PropertiesComponent.getInstance().setValue(LayoutCodeDialog.OPTIMIZE_IMPORTS_KEY, Boolean.toString(myOptimizeImports.isSelected())); 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())); } static boolean isOptmizeImportsOptionOn() { - return Boolean.valueOf(PropertiesComponent.getInstance().getValue(LayoutCodeDialog.OPTIMIZE_IMPORTS_KEY)); + return PropertiesComponent.getInstance().getBoolean(LayoutCodeConstants.OPTIMIZE_IMPORTS_KEY, false); } } diff --git a/platform/lang-impl/src/com/intellij/openapi/vcs/checkin/ReformatBeforeCheckinHandler.java b/platform/lang-impl/src/com/intellij/openapi/vcs/checkin/ReformatBeforeCheckinHandler.java index 05578cce4fae..e1a07cef088d 100644 --- a/platform/lang-impl/src/com/intellij/openapi/vcs/checkin/ReformatBeforeCheckinHandler.java +++ b/platform/lang-impl/src/com/intellij/openapi/vcs/checkin/ReformatBeforeCheckinHandler.java @@ -84,7 +84,9 @@ public class ReformatBeforeCheckinHandler extends CheckinHandler implements Chec }; if (reformat(configuration, true)) { - new ReformatCodeProcessor(myProject, BeforeCheckinHandlerUtil.getPsiFiles(myProject, files), COMMAND_NAME, performCheckoutAction).run(); + new ReformatCodeProcessor( + myProject, BeforeCheckinHandlerUtil.getPsiFiles(myProject, files), COMMAND_NAME, performCheckoutAction, false + ).run(); } else { performCheckoutAction.run(); diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/CodeStyleManagerImpl.java b/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/CodeStyleManagerImpl.java index 4426aa2acfbf..c0d69b38c25c 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/CodeStyleManagerImpl.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/CodeStyleManagerImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2011 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -48,6 +48,10 @@ import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; import java.util.concurrent.TimeUnit; public class CodeStyleManagerImpl extends CodeStyleManager { @@ -137,6 +141,14 @@ public class CodeStyleManagerImpl extends CodeStyleManager { @Override public void reformatText(@NotNull PsiFile file, int startOffset, int endOffset) throws IncorrectOperationException { + reformatText(file, Collections.singleton(new TextRange(startOffset, endOffset))); + } + + @Override + public void reformatText(@NotNull PsiFile file, @NotNull Collection ranges) throws IncorrectOperationException { + if (ranges.isEmpty()) { + return; + } ApplicationManager.getApplication().assertWriteAccessAllowed(); PsiDocumentManager.getInstance(getProject()).commitAllDocuments(); @@ -150,17 +162,9 @@ public class CodeStyleManagerImpl extends CodeStyleManager { final CodeFormatterFacade codeFormatter = new CodeFormatterFacade(getSettings()); LOG.assertTrue(file.isValid()); - final PsiElement start = findElementInTreeWithFormatterEnabled(file, startOffset); - final PsiElement end = findElementInTreeWithFormatterEnabled(file, endOffset); - if (start != null && !start.isValid()) { - LOG.error("start=" + start + "; file=" + file); - } - if (end != null && !end.isValid()) { - LOG.error("end=" + start + "; end=" + file); - } Editor editor = PsiUtilBase.findEditor(file); - + // There is a possible case that cursor is located at the end of the line that contains only white spaces. For example: // public void foo() { // @@ -168,7 +172,7 @@ public class CodeStyleManagerImpl extends CodeStyleManager { // Formatter removes such white spaces, i.e. keeps only line feed symbol. But we want to preserve caret position then. // So, we check if it should be preserved and restore it after formatting if necessary int visualColumnToRestore = -1; - + if (editor != null) { Document document = editor.getDocument(); int caretOffset = editor.getCaretModel().getOffset(); @@ -189,23 +193,40 @@ public class CodeStyleManagerImpl extends CodeStyleManager { visualColumnToRestore = editor.getCaretModel().getVisualPosition().column; } } - - - boolean formatFromStart = startOffset == 0; - boolean formatToEnd = endOffset == file.getTextLength(); final SmartPointerManager smartPointerManager = SmartPointerManager.getInstance(getProject()); - final SmartPsiElementPointer startPointer = start == null ? null : smartPointerManager.createSmartPsiElementPointer(start); - - final SmartPsiElementPointer endPointer = end == null ? null : smartPointerManager.createSmartPsiElementPointer(end); - - codeFormatter.processText(file, new FormatTextRanges(new TextRange(startOffset, endOffset), true), true); - final PsiElement startElement = startPointer == null ? null : startPointer.getElement(); - final PsiElement endElement = endPointer == null ? null : endPointer.getElement(); - - if ((startElement != null || formatFromStart) && (endElement != null || formatToEnd)) { - postProcessText(file, new TextRange(formatFromStart ? 0 : startElement.getTextRange().getStartOffset(), - formatToEnd ? file.getTextLength() : endElement.getTextRange().getEndOffset())); + List infos = new ArrayList(); + for (TextRange range : ranges) { + final PsiElement start = findElementInTreeWithFormatterEnabled(file, range.getStartOffset()); + final PsiElement end = findElementInTreeWithFormatterEnabled(file, range.getEndOffset()); + if (start != null && !start.isValid()) { + LOG.error("start=" + start + "; file=" + file); + } + if (end != null && !end.isValid()) { + LOG.error("end=" + start + "; end=" + file); + } + boolean formatFromStart = range.getStartOffset() == 0; + boolean formatToEnd = range.getEndOffset() == file.getTextLength(); + infos.add(new RangeFormatInfo( + start == null ? null : smartPointerManager.createSmartPsiElementPointer(start), + end == null ? null : smartPointerManager.createSmartPsiElementPointer(end), + formatFromStart, + formatToEnd + )); + } + + FormatTextRanges formatRanges = new FormatTextRanges(); + for (TextRange range : ranges) { + formatRanges.add(range, true); + } + codeFormatter.processText(file, formatRanges, true); + for (RangeFormatInfo info : infos) { + final PsiElement startElement = info.startPointer == null ? null : info.startPointer.getElement(); + final PsiElement endElement = info.endPointer == null ? null : info.endPointer.getElement(); + if ((startElement != null || info.fromStart) && (endElement != null || info.toEnd)) { + postProcessText(file, new TextRange(info.fromStart ? 0 : startElement.getTextRange().getStartOffset(), + info.toEnd ? file.getTextLength() : endElement.getTextRange().getEndOffset())); + } } if (visualColumnToRestore < 0) { @@ -758,5 +779,23 @@ public class CodeStyleManagerImpl extends CodeStyleManager { } }); } + + private static class RangeFormatInfo{ + public final SmartPsiElementPointer startPointer; + public final SmartPsiElementPointer endPointer; + public final boolean fromStart; + public final boolean toEnd; + + RangeFormatInfo(@Nullable SmartPsiElementPointer startPointer, + @Nullable SmartPsiElementPointer endPointer, + boolean fromStart, + boolean toEnd) + { + this.startPointer = startPointer; + this.endPointer = endPointer; + this.fromStart = fromStart; + this.toEnd = toEnd; + } + } } diff --git a/platform/platform-resources-en/src/messages/CodeInsightBundle.properties b/platform/platform-resources-en/src/messages/CodeInsightBundle.properties index 649bcf59210d..3fa41abf3020 100644 --- a/platform/platform-resources-en/src/messages/CodeInsightBundle.properties +++ b/platform/platform-resources-en/src/messages/CodeInsightBundle.properties @@ -12,6 +12,7 @@ reformat.option.selected.text=&Selected text reformat.option.all.files.in.directory=&All files in directory {0} reformat.option.include.subdirectories=&Include subdirectories reformat.option.optimize.imports=&Optimize imports +reformat.option.vcs.changed.region=Only text changed to &VCS reformat.progress.file.with.known.name.text=Reformatting {0} reformat.and.optimize.progress.common.text=Preparing imports... reformat.progress.common.text=Reformatting code... diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/mvc/MvcModuleStructureSynchronizer.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/mvc/MvcModuleStructureSynchronizer.java index 72f259a6ec60..2cdb96c9d68a 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/mvc/MvcModuleStructureSynchronizer.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/mvc/MvcModuleStructureSynchronizer.java @@ -1,5 +1,6 @@ /* - * Copyright 2000-2008 JetBrains s.r.o. + * Copyright 2000-2011 JetBrains s.r.o. + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -169,7 +170,7 @@ public class MvcModuleStructureSynchronizer extends AbstractProjectComponent { for (VirtualFile virtualFile : files) { PsiFile psiFile = manager.findFile(virtualFile); if (psiFile != null) { - new ReformatCodeProcessor(myProject, psiFile, null).run(); + new ReformatCodeProcessor(myProject, psiFile, null, false).run(); } } }