#157561: adding option to control how trailing whitespace is removed when saving files diff --git a/editor.lib/nbproject/project.xml b/editor.lib/nbproject/project.xml --- a/editor.lib/nbproject/project.xml +++ b/editor.lib/nbproject/project.xml @@ -90,7 +90,7 @@ 1 - 1.20 + 1.33 diff --git a/editor.lib/src/org/netbeans/modules/editor/lib/TrailingWhitespaceRemove.java b/editor.lib/src/org/netbeans/modules/editor/lib/TrailingWhitespaceRemove.java --- a/editor.lib/src/org/netbeans/modules/editor/lib/TrailingWhitespaceRemove.java +++ b/editor.lib/src/org/netbeans/modules/editor/lib/TrailingWhitespaceRemove.java @@ -46,6 +46,7 @@ import java.util.logging.Level; import java.util.logging.Logger; +import java.util.prefs.Preferences; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.text.BadLocationException; @@ -57,6 +58,8 @@ import javax.swing.undo.CannotUndoException; import javax.swing.undo.CompoundEdit; import org.netbeans.api.editor.EditorRegistry; +import org.netbeans.api.editor.mimelookup.MimeLookup; +import org.netbeans.api.editor.settings.SimpleValueNames; import org.netbeans.editor.BaseDocument; import org.netbeans.lib.editor.util.ArrayUtilities; import org.netbeans.lib.editor.util.GapList; @@ -98,6 +101,7 @@ private boolean inWhitespaceRemove; + @SuppressWarnings("LeakingThisInConstructor") private TrailingWhitespaceRemove(BaseDocument doc) { this.doc = doc; this.docText = DocumentUtilities.getText(doc); // Persists for doc's lifetime @@ -105,15 +109,21 @@ doc.addUpdateDocumentListener(this); } + @Override public synchronized void run(CompoundEdit compoundEdit) { - inWhitespaceRemove = true; - try { - new ModsProcessor().removeWhitespace(); - NewModRegionsEdit edit = new NewModRegionsEdit(); - compoundEdit.addEdit(edit); - edit.run(); - } finally { - inWhitespaceRemove = false; + Preferences prefs = MimeLookup.getLookup(DocumentUtilities.getMimeType(doc)).lookup(Preferences.class); + String policy = prefs.get(SimpleValueNames.ON_SAVE_REMOVE_TRAILING_WHITESPACE, "never"); //NOI18N + if (!"never".equals(policy)) { //NOI18N + inWhitespaceRemove = true; + try { + // XXX: somehow update the processor to support removing all lines vs modified lines only + new ModsProcessor().removeWhitespace(); + NewModRegionsEdit edit = new NewModRegionsEdit(); + compoundEdit.addEdit(edit); + edit.run(); + } finally { + inWhitespaceRemove = false; + } } } @@ -125,6 +135,7 @@ return new GapList(3); } + @Override public void insertUpdate(DocumentEvent evt) { CompoundEdit compoundEdit = (CompoundEdit) evt; int offset = evt.getOffset(); @@ -146,6 +157,7 @@ } } + @Override public void removeUpdate(DocumentEvent evt) { // Currently do not handle in any special way but // Since there's a mod on the line there will be a diff @@ -155,6 +167,7 @@ } } + @Override public void changedUpdate(DocumentEvent evt) { } diff --git a/editor.settings/manifest.mf b/editor.settings/manifest.mf --- a/editor.settings/manifest.mf +++ b/editor.settings/manifest.mf @@ -1,5 +1,5 @@ Manifest-Version: 1.0 OpenIDE-Module: org.netbeans.modules.editor.settings/1 OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/editor/settings/Bundle.properties -OpenIDE-Module-Specification-Version: 1.32 +OpenIDE-Module-Specification-Version: 1.33 OpenIDE-Module-Needs: org.netbeans.api.editor.settings.implementation diff --git a/editor.settings/src/org/netbeans/api/editor/settings/SimpleValueNames.java b/editor.settings/src/org/netbeans/api/editor/settings/SimpleValueNames.java --- a/editor.settings/src/org/netbeans/api/editor/settings/SimpleValueNames.java +++ b/editor.settings/src/org/netbeans/api/editor/settings/SimpleValueNames.java @@ -424,7 +424,19 @@ * @since 1.30 */ public static final String NON_PRINTABLE_CHARACTERS_VISIBLE = "non-printable-characters-visible"; //NOI18N - + + /** + * Determines whether to remove trailing whitespace when saving files and how exactly to do that. + * Values: java.lang.String instances + *
    + *
  • never + *
  • always + *
  • modified-lines + *
+ * @since 1.33 + */ + public static final String ON_SAVE_REMOVE_TRAILING_WHITESPACE = "on-save-remove-trailing-whitespace"; //NOI18N + @PatchedPublic private SimpleValueNames() { // to prevent instantialization diff --git a/options.editor/nbproject/project.xml b/options.editor/nbproject/project.xml --- a/options.editor/nbproject/project.xml +++ b/options.editor/nbproject/project.xml @@ -109,7 +109,7 @@ 1 - 1.29 + 1.33
diff --git a/options.editor/src/org/netbeans/modules/options/generaleditor/Bundle.properties b/options.editor/src/org/netbeans/modules/options/generaleditor/Bundle.properties --- a/options.editor/src/org/netbeans/modules/options/generaleditor/Bundle.properties +++ b/options.editor/src/org/netbeans/modules/options/generaleditor/Bundle.properties @@ -105,4 +105,12 @@ CTL_Camel_Case_Behavior_Example=Example: Caret stops at S, T, N in "SomeTypeName" when using next/previous word actions AN_Camel_Case_Behavior_Example=Example: Caret stops at S, T, N in "SomeTypeName" when using next/previous word actions AD_Camel_Case_Behavior_Example=Example: Caret stops at S, T, N in "SomeTypeName" when using next/previous word actions - +CTL_When_Saving_Files=When Saving Files +AN_When_Saving_Files=When Saving Files +AD_When_Saving_Files=When Saving Files +CTL_Remove_Trailing_Whitespace=Remove Trailing Whitespace +AN_Remove_Trailing_Whitespace=Remove Trailing Whitespace +AD_Remove_Trailing_Whitespace=Remove Trailing Whitespace +RTW_never=Never +RTW_always=Always +RTW_modified-lines=From Modified Lines Only diff --git a/options.editor/src/org/netbeans/modules/options/generaleditor/GeneralEditorPanel.form b/options.editor/src/org/netbeans/modules/options/generaleditor/GeneralEditorPanel.form --- a/options.editor/src/org/netbeans/modules/options/generaleditor/GeneralEditorPanel.form +++ b/options.editor/src/org/netbeans/modules/options/generaleditor/GeneralEditorPanel.form @@ -1,4 +1,4 @@ - +
@@ -21,17 +21,7 @@ - - - - - - - - - - - + @@ -39,21 +29,67 @@ + + + + + - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -91,10 +127,22 @@ - + + + + + + + + + + - - + + + + + @@ -169,5 +217,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/options.editor/src/org/netbeans/modules/options/generaleditor/GeneralEditorPanel.java b/options.editor/src/org/netbeans/modules/options/generaleditor/GeneralEditorPanel.java --- a/options.editor/src/org/netbeans/modules/options/generaleditor/GeneralEditorPanel.java +++ b/options.editor/src/org/netbeans/modules/options/generaleditor/GeneralEditorPanel.java @@ -48,8 +48,11 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.AbstractButton; +import javax.swing.DefaultComboBoxModel; import javax.swing.JLabel; +import javax.swing.JList; import javax.swing.JPanel; +import javax.swing.ListCellRenderer; import org.openide.awt.Mnemonics; import org.openide.util.NbBundle; @@ -84,8 +87,14 @@ loc (lCamelCaseBehavior, "Camel_Case_Behavior"); loc (cbCamelCaseBehavior, "Enable_Camel_Case_In_Java"); loc (lCamelCaseBehaviorExample, "Camel_Case_Behavior_Example"); + + loc (lWhenSavingFiles, "When_Saving_Files"); + loc (lRemoveTrailingWhitespace, "Remove_Trailing_Whitespace"); + loc (cboRemoveTrailingWhitespace, "Remove_Trailing_Whitespace"); + cbUseCodeFolding.setMnemonic(NbBundle.getMessage (GeneralEditorPanel.class, "MNEMONIC_Use_Folding").charAt(0)); - + cboRemoveTrailingWhitespace.setRenderer(new RemoveTrailingWhitespaceRenderer(cboRemoveTrailingWhitespace.getRenderer())); + cboRemoveTrailingWhitespace.setModel(new DefaultComboBoxModel(new Object [] { "never", "always", "modified-lines" })); //NOI18N } /** This method is called from within the constructor to @@ -111,6 +120,10 @@ jSeparator3 = new javax.swing.JSeparator(); cbCamelCaseBehavior = new javax.swing.JCheckBox(); lCamelCaseBehaviorExample = new javax.swing.JLabel(); + jSeparator4 = new javax.swing.JSeparator(); + lWhenSavingFiles = new javax.swing.JLabel(); + lRemoveTrailingWhitespace = new javax.swing.JLabel(); + cboRemoveTrailingWhitespace = new javax.swing.JComboBox(); setForeground(new java.awt.Color(99, 130, 191)); @@ -139,37 +152,72 @@ lCamelCaseBehaviorExample.setText("Example: Caret stops at J, T, N in \"JavaTypeName\" when using next/previous word acctions"); + lWhenSavingFiles.setText("When Saving Files"); + + lRemoveTrailingWhitespace.setLabelFor(cboRemoveTrailingWhitespace); + lRemoveTrailingWhitespace.setText("Remove Trailing Whitespace:"); + + cboRemoveTrailingWhitespace.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "Item 1", "Item 2", "Item 3", "Item 4" })); + org.jdesktop.layout.GroupLayout layout = new org.jdesktop.layout.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) .add(layout.createSequentialGroup() - .add(lCodeFolding) - .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) - .add(jSeparator1, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 671, Short.MAX_VALUE)) - .add(layout.createSequentialGroup() - .add(lCamelCaseBehavior) - .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) - .add(jSeparator3, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 612, Short.MAX_VALUE)) - .add(layout.createSequentialGroup() .addContainerGap() .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) .add(lUseCodeFolding) .add(lCollapseByDefault)) .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) - .add(lCamelCaseBehaviorExample) - .add(cbFoldInitialComments) - .add(cbFoldJavadocComments) - .add(cbUseCodeFolding) .add(layout.createSequentialGroup() - .add(cbFoldMethods) - .add(18, 18, 18) - .add(cbFoldTags)) - .add(cbFoldInnerClasses) - .add(cbFoldImports) - .add(cbCamelCaseBehavior)) + .add(lRemoveTrailingWhitespace) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .add(cboRemoveTrailingWhitespace, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)) + .add(lCamelCaseBehaviorExample)) .addContainerGap(40, Short.MAX_VALUE)) + .add(layout.createSequentialGroup() + .add(155, 155, 155) + .add(cbCamelCaseBehavior) + .addContainerGap(391, Short.MAX_VALUE)) + .add(layout.createSequentialGroup() + .add(155, 155, 155) + .add(cbFoldImports) + .addContainerGap(461, Short.MAX_VALUE)) + .add(layout.createSequentialGroup() + .add(155, 155, 155) + .add(cbFoldInnerClasses) + .addContainerGap(461, Short.MAX_VALUE)) + .add(layout.createSequentialGroup() + .add(155, 155, 155) + .add(cbFoldMethods) + .add(18, 18, 18) + .add(cbFoldTags) + .addContainerGap(234, Short.MAX_VALUE)) + .add(layout.createSequentialGroup() + .add(155, 155, 155) + .add(cbUseCodeFolding) + .addContainerGap(585, Short.MAX_VALUE)) + .add(layout.createSequentialGroup() + .add(155, 155, 155) + .add(cbFoldJavadocComments) + .addContainerGap(461, Short.MAX_VALUE)) + .add(layout.createSequentialGroup() + .add(155, 155, 155) + .add(cbFoldInitialComments) + .addContainerGap(461, Short.MAX_VALUE)) + .add(layout.createSequentialGroup() + .add(lWhenSavingFiles) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .add(jSeparator4, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 644, Short.MAX_VALUE)) + .add(layout.createSequentialGroup() + .add(lCamelCaseBehavior) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .add(jSeparator3, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 622, Short.MAX_VALUE)) + .add(layout.createSequentialGroup() + .add(lCodeFolding) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .add(jSeparator1, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 673, Short.MAX_VALUE)) ); layout.linkSize(new java.awt.Component[] {cbFoldImports, cbFoldInitialComments, cbFoldInnerClasses, cbFoldJavadocComments, cbFoldMethods}, org.jdesktop.layout.GroupLayout.HORIZONTAL); @@ -204,10 +252,19 @@ .add(jSeparator3, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 10, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)) .add(lCamelCaseBehavior)) .add(2, 2, 2) - .add(cbCamelCaseBehavior) + .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.TRAILING) + .add(layout.createSequentialGroup() + .add(cbCamelCaseBehavior) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .add(lCamelCaseBehaviorExample) + .add(18, 18, 18) + .add(jSeparator4, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 10, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)) + .add(lWhenSavingFiles)) .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) - .add(lCamelCaseBehaviorExample) - .addContainerGap(59, Short.MAX_VALUE)) + .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE) + .add(lRemoveTrailingWhitespace) + .add(cboRemoveTrailingWhitespace, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)) + .addContainerGap(67, Short.MAX_VALUE)) ); }// //GEN-END:initComponents @@ -221,13 +278,17 @@ private javax.swing.JCheckBox cbFoldMethods; private javax.swing.JCheckBox cbFoldTags; private javax.swing.JCheckBox cbUseCodeFolding; + private javax.swing.JComboBox cboRemoveTrailingWhitespace; private javax.swing.JSeparator jSeparator1; private javax.swing.JSeparator jSeparator3; + private javax.swing.JSeparator jSeparator4; private javax.swing.JLabel lCamelCaseBehavior; private javax.swing.JLabel lCamelCaseBehaviorExample; private javax.swing.JLabel lCodeFolding; private javax.swing.JLabel lCollapseByDefault; + private javax.swing.JLabel lRemoveTrailingWhitespace; private javax.swing.JLabel lUseCodeFolding; + private javax.swing.JLabel lWhenSavingFiles; // End of variables declaration//GEN-END:variables @@ -245,7 +306,7 @@ (AbstractButton) c, loc ("CTL_" + key) ); - } else { + } else if (c instanceof JLabel) { Mnemonics.setLocalizedText ( (JLabel) c, loc ("CTL_" + key) @@ -267,6 +328,7 @@ cbFoldInitialComments.addActionListener (this); cbCamelCaseBehavior.addActionListener (this); cbFoldTags.addActionListener (this); + cboRemoveTrailingWhitespace.addActionListener(this); } // init code folding @@ -288,7 +350,10 @@ cbCamelCaseBehavior.setEnabled(true); cbCamelCaseBehavior.setSelected(ccJava); } - + + // when saving files section + cboRemoveTrailingWhitespace.setSelectedItem(model.getRemoveTrailingWhitespace()); + updateEnabledState (); listen = true; @@ -312,6 +377,9 @@ // java camel case navigation model.setCamelCaseNavigation(cbCamelCaseBehavior.isSelected()); + // when saving files section + model.setRemoveTrailingWhitespace((String)cboRemoveTrailingWhitespace.getSelectedItem()); + changed = false; } @@ -327,10 +395,12 @@ return changed; } + @Override public void actionPerformed (ActionEvent e) { if (!listen) return; - if (e.getSource () == cbUseCodeFolding) + if (e.getSource () == cbUseCodeFolding) { updateEnabledState (); + } changed = true; } @@ -346,4 +416,24 @@ cbFoldMethods.setEnabled (useCodeFolding); cbFoldTags.setEnabled(useCodeFolding); } + + private static final class RemoveTrailingWhitespaceRenderer implements ListCellRenderer { + + private final ListCellRenderer defaultRenderer; + + public RemoveTrailingWhitespaceRenderer(ListCellRenderer defaultRenderer) { + this.defaultRenderer = defaultRenderer; + } + + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + return defaultRenderer.getListCellRendererComponent( + list, + NbBundle.getMessage(GeneralEditorPanel.class, "RTW_" + value), //NOI18N + index, + isSelected, + cellHasFocus); + } + + } // End of RemoveTrailingWhitespaceRendererRenderer class } diff --git a/options.editor/src/org/netbeans/modules/options/generaleditor/Model.java b/options.editor/src/org/netbeans/modules/options/generaleditor/Model.java --- a/options.editor/src/org/netbeans/modules/options/generaleditor/Model.java +++ b/options.editor/src/org/netbeans/modules/options/generaleditor/Model.java @@ -50,6 +50,7 @@ import java.util.Set; import java.util.prefs.Preferences; import org.netbeans.api.editor.mimelookup.MimeLookup; +import org.netbeans.api.editor.mimelookup.MimePath; import org.netbeans.api.editor.settings.SimpleValueNames; import org.netbeans.modules.editor.settings.storage.api.EditorSettings; import org.openide.util.Lookup; @@ -122,7 +123,17 @@ void setCamelCaseNavigation(boolean value) { NbPreferences.root ().putBoolean("useCamelCaseStyleNavigation", value); // NOI18N } - + + String getRemoveTrailingWhitespace() { + Preferences prefs = MimeLookup.getLookup(MimePath.EMPTY).lookup(Preferences.class); + return prefs.get(SimpleValueNames.ON_SAVE_REMOVE_TRAILING_WHITESPACE, "never"); + } + + void setRemoveTrailingWhitespace(String value) { + Preferences prefs = MimeLookup.getLookup(MimePath.EMPTY).lookup(Preferences.class); + prefs.put(SimpleValueNames.ON_SAVE_REMOVE_TRAILING_WHITESPACE, value); + } + // private helper methods .................................................. private static final List PRIVILEDGED_MIME_TYPES = Arrays.asList(new String [] {