From 513eb87edd5828595358d1940a8f370900a3edbf Mon Sep 17 00:00:00 2001 From: Daniel Marcotte Date: Wed, 5 Dec 2012 18:48:26 -0800 Subject: [PATCH] Add right margin guideline to commit message editor Add a right margin to the commit message editor, defaulting it to 72 columns and enabled. Also add a configuration option to "Settings->Version Control" for toggling the margin and setting its width. --- .../ui/EditorTextFieldProviderImpl.java | 15 ++- .../intellij/ui/EditorTextFieldProvider.java | 17 +++ .../src/messages/VcsBundle.properties | 2 + .../openapi/vcs/VcsConfiguration.java | 2 + .../VcsCommitMessageMarginConfigurable.java | 124 ++++++++++++++++++ .../VcsDirectoryConfigurationPanel.java | 12 ++ .../openapi/vcs/ui/CommitMessage.java | 89 +++++++++---- 7 files changed, 233 insertions(+), 28 deletions(-) create mode 100644 platform/vcs-impl/src/com/intellij/openapi/vcs/configurable/VcsCommitMessageMarginConfigurable.java diff --git a/platform/lang-impl/src/com/intellij/ui/EditorTextFieldProviderImpl.java b/platform/lang-impl/src/com/intellij/ui/EditorTextFieldProviderImpl.java index 9b2f16e0c263..78a23cb38248 100644 --- a/platform/lang-impl/src/com/intellij/ui/EditorTextFieldProviderImpl.java +++ b/platform/lang-impl/src/com/intellij/ui/EditorTextFieldProviderImpl.java @@ -99,7 +99,20 @@ public class EditorTextFieldProviderImpl implements EditorTextFieldProvider { } }; } - + + @NotNull + @Override + public EditorTextField getEditorField(@NotNull Language language, + @NotNull Project project, + @NotNull final AdHocEditorCustomizer customization) { + return new MyEditorTextField(language, project) { + @Override + protected void applyFeatures(@NotNull EditorCustomization[] customizations, @NotNull EditorEx editor) { + customization.customize(editor); + } + }; + } + private abstract static class MyEditorTextField extends LanguageTextField { MyEditorTextField(Language language, @NotNull Project project) { diff --git a/platform/platform-impl/src/com/intellij/ui/EditorTextFieldProvider.java b/platform/platform-impl/src/com/intellij/ui/EditorTextFieldProvider.java index c6d669203130..9703326767ad 100644 --- a/platform/platform-impl/src/com/intellij/ui/EditorTextFieldProvider.java +++ b/platform/platform-impl/src/com/intellij/ui/EditorTextFieldProvider.java @@ -44,4 +44,21 @@ public interface EditorTextFieldProvider { EditorTextField getEditorField(@NotNull Language language, @NotNull Project project, @NotNull Iterable enabledFeatures, @NotNull Iterable disabledFeatures); + + /** + * Alternative to {@link #getEditorField(com.intellij.lang.Language, com.intellij.openapi.project.Project, Iterable, Iterable)} which + * allows you to define arbitrary customizations by implementing an {@link AdHocEditorCustomizer}. + * + * @param language target language used by document that will be displayed by returned editor + * @param project target project + * @param customization implementation of {@link AdHocEditorCustomizer} which will be called at editor creation + * + */ + @NotNull + EditorTextField getEditorField(@NotNull Language language, @NotNull Project project, + @NotNull AdHocEditorCustomizer customization); + + interface AdHocEditorCustomizer { + void customize(EditorEx editor); + } } diff --git a/platform/platform-resources-en/src/messages/VcsBundle.properties b/platform/platform-resources-en/src/messages/VcsBundle.properties index 675cba61d026..242922d22154 100644 --- a/platform/platform-resources-en/src/messages/VcsBundle.properties +++ b/platform/platform-resources-en/src/messages/VcsBundle.properties @@ -131,6 +131,8 @@ checkbox.force.non.empty.messages=Force non-&empty checkin comments tooltip.text.action.applicable.to.vcses=Applicable to: {0} version.control.main.configurable.name=Version Control configuration.tab.name.general.settings=General Settings +configuration.commit.message.margin.title=Commit message right margin +configuration.commit.message.margin.prompt=Commit message right margin (columns): button.configure=&Configure VCS... action.name.rollback=Rollback command.name.rollback.change=Rollback Change diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/VcsConfiguration.java b/platform/vcs-api/src/com/intellij/openapi/vcs/VcsConfiguration.java index a402abbf0d25..b50050192397 100644 --- a/platform/vcs-api/src/com/intellij/openapi/vcs/VcsConfiguration.java +++ b/platform/vcs-api/src/com/intellij/openapi/vcs/VcsConfiguration.java @@ -91,6 +91,8 @@ public final class VcsConfiguration implements PersistentStateComponent public boolean LIMIT_HISTORY = true; public int MAXIMUM_HISTORY_ROWS = 1000; public String UPDATE_FILTER_SCOPE_NAME; + public boolean USE_COMMIT_MESSAGE_MARGIN = true; + public int COMMIT_MESSAGE_MARGIN_SIZE = 72; public enum StandardOption { ADD(VcsBundle.message("vcs.command.name.add")), diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/configurable/VcsCommitMessageMarginConfigurable.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/configurable/VcsCommitMessageMarginConfigurable.java new file mode 100644 index 000000000000..0c2a50763003 --- /dev/null +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/configurable/VcsCommitMessageMarginConfigurable.java @@ -0,0 +1,124 @@ +/* + * 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.openapi.vcs.configurable; + +import com.intellij.openapi.options.Configurable; +import com.intellij.openapi.options.ConfigurationException; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.VcsBundle; +import com.intellij.openapi.vcs.VcsConfiguration; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +public class VcsCommitMessageMarginConfigurable implements Configurable { + + private JCheckBox myEnableMarginCheckbox; + private JTextField myRightMarginTextField; + private final VcsConfiguration myConfiguration; + + public VcsCommitMessageMarginConfigurable(Project project) { + myConfiguration = VcsConfiguration.getInstance(project); + } + + @Nls + @Override + public String getDisplayName() { + return VcsBundle.message("configuration.commit.message.margin.title"); + } + + @Nullable + @Override + public String getHelpTopic() { + return null; + } + + @Nullable + @Override + public JComponent createComponent() { + JPanel wrapper = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0)); + myEnableMarginCheckbox = new JCheckBox(VcsBundle.message("configuration.commit.message.margin.prompt"), myConfiguration.USE_COMMIT_MESSAGE_MARGIN); + myRightMarginTextField = new JTextField(Integer.toString(myConfiguration.COMMIT_MESSAGE_MARGIN_SIZE), 5); + myRightMarginTextField.setEnabled(myEnableMarginCheckbox.isSelected()); + wrapper.add(myEnableMarginCheckbox); + wrapper.add(myRightMarginTextField); + + myEnableMarginCheckbox.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + myRightMarginTextField.setEnabled(myEnableMarginCheckbox.isSelected()); + } + }); + + return wrapper; + } + + @Override + public boolean isModified() { + if (myEnableMarginCheckbox.isSelected() != myConfiguration.USE_COMMIT_MESSAGE_MARGIN) { + return true; + } + + if (getValidRightMargin() == null || getValidRightMargin() != myConfiguration.COMMIT_MESSAGE_MARGIN_SIZE) { + return true; + } + + return false; + } + + /** + * If possible, returns a valid user-input right margin (i.e. an int greater than zero). + * Otherwise, returns null. + */ + private Integer getValidRightMargin() { + if (myRightMarginTextField == null) { + return null; + } + + try { + Integer rightMargin = Integer.parseInt(myRightMarginTextField.getText()); + return rightMargin == null || rightMargin < 0 ? null : rightMargin; + } catch (NumberFormatException e) { + return null; + } + } + + @Override + public void apply() throws ConfigurationException { + myConfiguration.USE_COMMIT_MESSAGE_MARGIN = myEnableMarginCheckbox.isSelected(); + Integer rightMargin = getValidRightMargin(); + if (rightMargin == null) { + // invalid right margin provided; revert to previous setting + myRightMarginTextField.setText(Integer.toString(myConfiguration.COMMIT_MESSAGE_MARGIN_SIZE)); + } else { + myConfiguration.COMMIT_MESSAGE_MARGIN_SIZE = rightMargin; + } + } + + @Override + public void reset() { + myEnableMarginCheckbox.setSelected(myConfiguration.USE_COMMIT_MESSAGE_MARGIN); + myRightMarginTextField.setText(Integer.toString(myConfiguration.COMMIT_MESSAGE_MARGIN_SIZE)); + } + + @Override + public void disposeUIResources() { + } +} diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/configurable/VcsDirectoryConfigurationPanel.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/configurable/VcsDirectoryConfigurationPanel.java index e8e3fa85e67c..a24a71c87e1c 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/configurable/VcsDirectoryConfigurationPanel.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/configurable/VcsDirectoryConfigurationPanel.java @@ -79,6 +79,7 @@ public class VcsDirectoryConfigurationPanel extends JPanel implements Configurab private JCheckBox myShowChangedRecursively; private final VcsLimitHistoryConfigurable myLimitHistory; private final VcsUpdateInfoScopeFilterConfigurable myScopeFilterConfig; + private VcsCommitMessageMarginConfigurable myCommitMessageMarginConfigurable; private class MyDirectoryRenderer extends ColoredTableCellRenderer { private final Project myProject; @@ -285,6 +286,7 @@ public class VcsDirectoryConfigurationPanel extends JPanel implements Configurab myScopeFilterConfig.reset(); myBaseRevisionTexts.setSelected(myVcsConfiguration.INCLUDE_TEXT_INTO_SHELF); myShowChangedRecursively.setSelected(myVcsConfiguration.SHOW_DIRTY_RECURSIVELY); + myCommitMessageMarginConfigurable.reset(); } public static DefaultComboBoxModel buildVcsWrappersModel(final Project project) { @@ -367,6 +369,7 @@ public class VcsDirectoryConfigurationPanel extends JPanel implements Configurab panel.add(createShowChangedOption(), gb.nextLine().next()); panel.add(createShowVcsRootErrorNotificationOption(), gb.nextLine().next()); panel.add(myScopeFilterConfig.createComponent(), gb.nextLine().next()); + panel.add(createUseCommitMessageRightMargin(), gb.nextLine().next().fillCellHorizontally()); return panel; } @@ -486,6 +489,11 @@ public class VcsDirectoryConfigurationPanel extends JPanel implements Configurab return myShowVcsRootErrorNotification; } + private JComponent createUseCommitMessageRightMargin() { + myCommitMessageMarginConfigurable = new VcsCommitMessageMarginConfigurable(myProject); + return myCommitMessageMarginConfigurable.createComponent(); + } + private JComponent createShowRecursivelyDirtyOption() { myShowChangedRecursively = new JCheckBox("Show directories with changed descendants", myVcsConfiguration.SHOW_DIRTY_RECURSIVELY); return myShowChangedRecursively; @@ -503,6 +511,7 @@ public class VcsDirectoryConfigurationPanel extends JPanel implements Configurab myVcsConfiguration.INCLUDE_TEXT_INTO_SHELF = myBaseRevisionTexts.isSelected(); myVcsConfiguration.SHOW_VCS_ERROR_NOTIFICATIONS = myShowVcsRootErrorNotification.isSelected(); myVcsConfiguration.SHOW_DIRTY_RECURSIVELY = myShowChangedRecursively.isSelected(); + myCommitMessageMarginConfigurable.apply(); initializeModel(); } @@ -517,6 +526,9 @@ public class VcsDirectoryConfigurationPanel extends JPanel implements Configurab if (myVcsConfiguration.SHOW_DIRTY_RECURSIVELY != myShowChangedRecursively.isSelected()) { return true; } + if (myCommitMessageMarginConfigurable.isModified()) { + return true; + } return !myModel.getItems().equals(myVcsManager.getDirectoryMappings()); } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/ui/CommitMessage.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/ui/CommitMessage.java index e7fe843c56b4..b9c91e66ac35 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/ui/CommitMessage.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/ui/CommitMessage.java @@ -20,6 +20,8 @@ import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.EditorSettings; +import com.intellij.openapi.editor.colors.EditorColorsManager; import com.intellij.openapi.editor.ex.EditorEx; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.fileTypes.FileTypes; @@ -32,8 +34,6 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; -import java.util.EnumSet; -import java.util.Set; public class CommitMessage extends AbstractDataProviderPanel implements Disposable, CommitMessageI { @@ -50,12 +50,7 @@ public class CommitMessage extends AbstractDataProviderPanel implements Disposab public CommitMessage(Project project, final boolean withSeparator) { super(new BorderLayout()); - boolean checkSpelling = true; - VcsConfiguration configuration = VcsConfiguration.getInstance(project); - if (configuration != null) { - checkSpelling = configuration.CHECK_COMMIT_MESSAGE_SPELLING; - } - myEditorField = createEditorField(project, checkSpelling); + myEditorField = createEditorField(project); myProject = project; // Note that we assume here that editor used for commit message processing uses font family implied by LAF (in contrast, @@ -105,24 +100,60 @@ public class CommitMessage extends AbstractDataProviderPanel implements Disposab setText(currentDescription); } - private static EditorTextField createEditorField(final Project project, final boolean checkSpelling) { - EditorTextField editorField = createCommitTextEditor(project, checkSpelling); + private static EditorTextField createEditorField(final Project project) { + EditorTextField editorField = createCommitTextEditor(project, false); editorField.getDocument().putUserData(DATA_CONTEXT_KEY, DataManager.getInstance().getDataContext(editorField.getComponent())); return editorField; } - public static EditorTextField createCommitTextEditor(Project project, boolean checkSpelling) { - EditorTextFieldProvider service = ServiceManager.getService(project, EditorTextFieldProvider.class); - Set enabledFeatures = EnumSet.of(EditorCustomization.Feature.SOFT_WRAP); - Set disabledFeatures = EnumSet.of(EditorCustomization.Feature.ADDITIONAL_PAGE_AT_BOTTOM); - if (checkSpelling) { - enabledFeatures.add(EditorCustomization.Feature.SPELL_CHECK); - } - else { - disabledFeatures.add(EditorCustomization.Feature.SPELL_CHECK); + /** + * Creates a text editor appropriate for creating commit messages. + * + * @param project project this commit message editor is intended for + * @param forceSpellCheckOn if false, {@link com.intellij.openapi.vcs.VcsConfiguration#CHECK_COMMIT_MESSAGE_SPELLING} will control + * whether or not the editor has spell check enabled + * @return a commit message editor + */ + public static EditorTextField createCommitTextEditor(final Project project, boolean forceSpellCheckOn) { + final boolean checkSpelling; + final boolean useCommitMessageMargin; + final int commitMessageMarginSize; + + VcsConfiguration configuration = VcsConfiguration.getInstance(project); + + if (configuration != null) { + checkSpelling = forceSpellCheckOn || configuration.CHECK_COMMIT_MESSAGE_SPELLING; + useCommitMessageMargin = configuration.USE_COMMIT_MESSAGE_MARGIN; + commitMessageMarginSize = configuration.COMMIT_MESSAGE_MARGIN_SIZE; + } else { + checkSpelling = true; + useCommitMessageMargin = false; + commitMessageMarginSize = -1; } - return service.getEditorField(FileTypes.PLAIN_TEXT.getLanguage(), project, enabledFeatures, disabledFeatures); + EditorTextFieldProvider service = ServiceManager.getService(project, EditorTextFieldProvider.class); + return service.getEditorField(FileTypes.PLAIN_TEXT.getLanguage(), + project, + new EditorTextFieldProvider.AdHocEditorCustomizer() { + @Override + public void customize(EditorEx editor) { + editor.getScrollPane().setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); + editor.setHorizontalScrollbarVisible(true); + EditorSettings settings = editor.getSettings(); + settings.setAdditionalColumnsCount(3); + settings.setVirtualSpace(false); + settings.setUseSoftWraps(true); + settings.setAdditionalPageAtBottom(true); + + toggleEditorSpellchecking(project, editor, checkSpelling); + + if (useCommitMessageMargin) { + editor.setColorsScheme(EditorColorsManager.getInstance().getGlobalScheme()); + settings.setRightMarginShown(true); + settings.setRightMargin(commitMessageMarginSize); + } + } + }); } @Nullable @@ -168,20 +199,24 @@ public class CommitMessage extends AbstractDataProviderPanel implements Disposab return; } EditorEx editorEx = (EditorEx)editor; - EditorCustomization[] customizations = Extensions.getExtensions(EditorCustomization.EP_NAME, myProject); - EditorCustomization.Feature feature = EditorCustomization.Feature.SPELL_CHECK; + toggleEditorSpellchecking(myProject, editorEx, check); + } + + private static void toggleEditorSpellchecking(Project project, EditorEx editorEx, boolean spellCheckingEnabled) { + EditorCustomization[] customizations = Extensions.getExtensions(EditorCustomization.EP_NAME, project); + EditorCustomization.Feature spellCheckFeature = EditorCustomization.Feature.SPELL_CHECK; for (EditorCustomization customization : customizations) { - if (customization.getSupportedFeatures().contains(feature)) { - if (check) { - customization.addCustomization(editorEx, feature); + if (customization.getSupportedFeatures().contains(spellCheckFeature)) { + if (spellCheckingEnabled) { + customization.addCustomization(editorEx, spellCheckFeature); } else { - customization.removeCustomization(editorEx, feature); + customization.removeCustomization(editorEx, spellCheckFeature); } } } } - + public void dispose() { }