EditorFormattingSettings

Editor Formatting Settings

With an introduction of per-project formatting settings the whole situation with formatting settings, their use, where they are stored, how this storage should be accessed and so on, has become quite confusing. This page tries to clarify things.


Basic Concepts

The formatting settings are key-value pairs that define how a document edited in the IDE should be formatted and rendered on screen. There are several areas that use formatting settings. The obvious ones are basic editing actions such as pressing TAB or ENTER keys, the 'Format' action, but the settings are also needed when drawing text on screen (eg. tab-size is needed to know how wide 'space' should be drawn when rendering a tab character, or right-margin is needed for drawing the red vertical line), the formatting settings are also needed when generating code by various refactoring features, etc.

The formatting settings can be divided in two groups - basic settings defined by the editor infrastructure and advanced settings defined by language support modules for fine-grained formatting. The majority of languages in the IDE do not define any advanced settings, but the few big languages such as java, ruby, C/C++ have a lot of advanced settings.

Since Netbeans 6.5 the formatting settings can be defined either globally in the IDE or in a project. It is up to the user to decide if a particular project should use global IDE-wide settings or if it should use its own customized copy. In the latter case the per-project formatting settings will be applied to all files owned by the project (ie. all files in the project). The fact that formatting settings can be stored in a project has several implications. By far the most important one is that in order to access formatting settings you need a document or at least a file!

This is a big difference from previous Netbeans versions where the only thing that you needed for accessing formatting settings was a mime type.


Formatting Settings Storage

There are two main usecase when an access to the formatting settings is needed. The majority of code will only ever need to read the settings without bothering where the settings came from. The other usecase are the settings customizers in Tools-Options and Project Properties dialogs.

We could arguably leave this on the modules, because they own both the formatters/indenters and the settings customizers. So they could store the formatting settings wherever they want (eg. in NbPreferences) and make sure that all their code use the same storage. In fact this used to be the case in Netbeans versions prior 6.5. Unfortunately, the situation is not that simple. As we mentioned earlier some of the basic settings are used by the infrastructure for things like text rendering, etc. If modules used their own proprietary storage the infrastructure would not know about it and we would need to devise a way for injecting those settings in the infrastructure.

An introduction of pre-project settings makes the use of a proprietary storage even less viable. Obviously the per-project settings have to be stored in the project. And so, if the per-project formatting settings are stored in a single common place, why should not be the IDE-wide version of these settings. The MimeLookup and its java.util.prefs.Preferences implementation is a logical place.

The recommended places for storing the formatting settings are MimeLookup for IDE-wide settings and a project for per-project settings. The settings themselves are provided in form of java.util.prefs.Preferences no matter where they are stored. Both MimeLookup and project storages use the same rules/semantics. Here is the basic overview.

  1. The settings are grouped by mime-types. Which means that there are for example formatting settings for java files stored under 'text/x-java' mime type or settings for Ruby files stored under 'text/x-ruby'.
  2. There are also 'all languages' settings which are stored under an empty mime type.
  3. The 'all languages' settings apply to all mime types unless a mime type overrides them. This inheritance is invisible for clients accessing the settings and is applied on setting-by-setting basis.

As explained earlier it is neccessary to have a Document or FileObject instance in order to access the formatting settings that apply for that document or file. The Editor Indentation API provides a unified access to the formatting settings. See the example below.

javax.swing.text.Document doc = ...
java.util.prefs.Preferences prefs = CodeStylePreferences.get(doc).getPreferences();

// The prefs instance contains formatting settings applicable to the doc document
int tab-size = prefs.getInt(SimpleValueNames.TAB_SIZE, 4);
...

UI Customizers

The formatting settings customizers are UI panels that allow users to control the formatting settings and preview their changes on a code snippet. The customizers are accessible either from the Tools-Options dialog (Editor -> Formatting) or from the Project Properties dialog (Formatting). In both cases the editor infrastructure provides a generic part of the UI for selecting a language and a particular customizer from the customizers available for the language.

This infrastructure and the generic UI that it provides are in the options.editor module (Editor Options). This module also provides friend API for implementing and registering customizers. The important thing is that each customizer is given a Preferences instance with settings, which it should customize. The customizer simply reads and writes settings from the Preferences instance according to the actions done by a user in the UI. The infrastructure then commits or drops the changes depending on whether the user confirms or cancels the dialog. Below is an example of a customizer.

public class Customizer implements PreferencesCustomizer {

  private final JComponent c;

  public Customizer(Preferences prefs) {
    this.c = new CustomizerPanel(prefs);
  }

  public String getId() {
    return "customizer-unique-id";
  }
  
  public String getDisplayName() {
    return NbBundle.getMessage(Customizer.class, "customizer-human-readable-name");
  }
  
  public HelpCtx getHelpCtx() {
    return new HelpCtx("customizer-help-id");
  }
  
  public JComponent getComponent() {
    return this.c;
  }
}

The PreferencesCustomizer implementations can be registered in a module layer under the OptionsDialog/Editor/Formatting/<mime-type> folder. Since there can be multiple customizers for the same mime-type needed (each in a different project) it is not possible to register the customizers directly, but we have to use a factory interface. Here is an example.

public class Factory implements PreferencesCustomizer.Factory {

  public PreferencesCustomizer createCustomizer(Preferences prefs) {
    return new Customizer(prefs);
  }
}

There is always at least one customizer for 'all languages' and it is provided by the infrastructure. This customizer allows controlling the basic formatting settings such as expand-tabs, tab-size, etc. As explained in the rules for the formatting settings storage the basic settings are inherited from 'all languages' to each particular mime type unless the mime type overrides them. This means that the languages providing their own customizers are likely to need to provide a customizer for the basic settings as well. The infrastructure simplifies this and allows reusing its own basic settings customizer by automatically adding it to any customizer with the PreferencesCustomizer.TABS_AND_INDENTS_ID id.


Preview panel

A useful feature of settings customizers in general is an ability to preview changes done by users. The formatting settings customizers can do that by implementing the PreviewProvider interface and supplying JComponent that shows what happens when the changed settings are applied on a piece of code. Here is an example.

public class Customizer implements PreferencesCustomizer, PreviewProvider {

  private JEditorPane jep;
  
  public JComponent getPreviewComponent() {
    if (jep == null) {
      jep = new JEditorPane();
      
      // Accessibility
      jep.getAccessibleContext().setAccessibleName(NbBundle.getMessage(Customizer.class, "AN_Preview")); //NOI18N
      jep.getAccessibleContext().setAccessibleDescription(NbBundle.getMessage(Customizer.class, "AD_Preview")); //NOI18N
      
      // turn off all the highlights, but syntax highlighting
      jep.putClientProperty("HighlightsLayerIncludes", "^org<br>.netbeans<br>.modules<br>.editor<br>.lib2<br>.highlighting<br>.SyntaxHighlighting$"); //NOI18N
      
      // set the correct editor kit, text/xml in this example
      jep.setEditorKit(CloneableEditorSupport.getEditorKit("text/xml")); //NOI18N
      
      // the preview is not editable
      jep.setEditable(false);
    }
    return jep;
  }
  
  public void refreshPreview() {
    // This is module specific and depends on how the formatters are implemented,
    // but basically you should create a formatter and initialize it from the
    // customizer's Preferences, then set preview text to the editor pane created in
    // getPreviewComponent() and reformat it with the formatter.
    
    // Or if your formatter uses CodeStylePreferences.get(Document) you can use
    // the simplified version below. The customizer's Preferences will automatically
    // be recognized by CodeStylePreferences and passed to your formatter.
    
    JEditorPane pane = (JEditorPane) getPreviewComponent();
    pane.setText(previewText);
            
    BaseDocument doc = (BaseDocument) pane.getDocument();
    Reformat reformat = Reformat.get(doc);
    reformat.lock();
    try {
      doc.atomicLock();
      try {
        reformat.reformat(0, doc.getLength());
      } catch (BadLocationException ble) {
        // log the exception
      } finally {
        doc.atomicUnlock();
      }
    } finally {
      reformat.unlock();
    }
  }
  
}


Legacy customizers

In order to support legacy customizers the infrastructure recognizes OptionsPanelCustomizer implementations registered in OptionsDialog/Editor/Formatting/<mime-type> (ie. the same place as for PreferencesCustomizer.Factory). However, these customizers are only used in the Tools-Options dialog and not in Project Properties, because they were originally designed to support only IDE-wide (global) formatting settings.

Project properties

Project types are responsible for adding the 'Formatting' node to their properties dialog if they wish to provide per-project formatting settings. They should reuse the implementation of the 'Formatting' node in the editor.options module. The example below shows how the PHP project type adds the 'Formatting' node to its project properties dialog.

<folder name="Projects">
  <folder name="org-netbeans-modules-php-project">
    <folder name="Customizer">
      <file name="Formatting.instance">
        <attr name="instanceOf" stringvalue="org.netbeans.spi.project.ui.support.ProjectCustomizer$CompositeCategoryProvider"/>
        <attr name="instanceCreate" methodvalue="org.netbeans.modules.options.indentation.FormattingCustomizerPanel.createCategoryProvider"/>
        <attr name="allowedMimeTypes" stringvalue="text/x-php5"/>
        <attr name="position" intvalue="1000"/>
      </file>
    </folder>
  </folder>
</folder>

Please note the allowedMimeTypes attribute which can be used to supply a comma separated list of mime types that should be offered in the 'Languages' combobox on the 'Formatting' panel. Obviously there have to be some formatting settings customizers registered for those mime types in order to have them listed in the combobox. If this attribute is not specified then all languages that have a formatting settings customizer registered will be listed. On the other hand if this attribute is specified, but contains an empty list the combobox will only contain the 'All Languages' customizer.

Limitations

  1. The Preferences implementation available in MimeLookup does not support child nodes (ie. you can't call Preferences.node() on the Preferences objects found in MimeLookup). This is limiting for C/C++ language support, because it is using child nodes to implement formatting profiles. [Issue 144579]
  2. Mostly due to #1 the C/C++ language support keeps storing their formatting settings in NbPreferences in the way which is not compatible with 6.5 and per-project settings.
  3. (Fixed in Issue 144987) In Netbeans versions prior 6.5 other modules (java, ruby) also used NbPreferences for storing their (IDE-wide) formatting settings. These modules now use MimeLookup. No attempt has been made to import the old settings and transfer them from NbPreferences to MimeLookup.
Not logged in. Log in, Register

By use of this website, you agree to the NetBeans Policies and Terms of Use. © 2012, Oracle Corporation and/or its affiliates. Sponsored by Oracle logo