Skip navigation links
org.netbeans.modules.editor.lib2/1 2.33.0 55

Package org.netbeans.api.editor.caret

The Editor Caret API opens up the editor to give information about its carets and to manipulate them.

See: Description

Package org.netbeans.api.editor.caret Description

The Editor Caret API opens up the editor to give information about its carets and to manipulate them.

Key parts of the API

The whole API is organized around the EditorCaret class, an implementation of the javax.swing.text.Caret managing all the carets in a single document. Information about carets maintained by EditorCaret is stored in the immutable class CaretInfo. Once a caret gets mutated its corresponding caret info becomes obsolete and new caret info instance gets created lazily. EditorCaretListener can be registered to an EditorCaret to be informed about caret addition/removal or movement with an EditorCaretEvent.

The EditorCaret mutates its carets in a single transaction. CaretMoveContext allows clients implementing CaretMoveHandler to manipulate carets. The following code shows how all carets are moved to the end of the word they are currently on.


    editorCaret.moveCarets((CaretMoveContext context) -> {
        for (CaretInfo ci : context.getOriginalCarets()) {
            Position pos = target.getDocument().createPosition(Utilities.getWordEnd(target, ci.getDot()));
            context.setDot(ci, pos);
        }
    });
  
  

Locking and Document changes

The basics of the locking and events model of Swing documents is described in the javadoc of the javax.swing.text.AbstractDocument class. Netbeans documents use the same patterns and so does the Caret API, because of its tight relation to documents. The fundamentals of the Swing documents locking model are that any changes to a document are done under the document's write lock, the document's listeners are notified synchronously on the mutating thread and have full read access to the document, but can't modify it.

The Caret API does not use any special threads and any processing it does is always done on the caller's thread. This means that the above described constraints hardly cause any limitation for practical use.

The Caret API is generally thread-safe meaning that it can be used simultaneously from multiple threads if not stated otherwise. This doesn't change in any way the rule of acquiring a read lock before calling the API. Swing documents generally allow access for multiple readers that can run concurrently.

Reason of Caret Movement

It may be desirable (especially for NavigationListeners, but possibly for EditorCaretListeners too) to determine what was the reason leading to caret movement. The caller of Caret API which intends to position the caret may provide an optional MoveCaretsOrigin instance to Caret API methods to indicate type of action leading to the movement. This description is used to select NavigationFilters which receive and can modify the movement, and is available to all invoked NavigationFilters or EditorCaretListeners contacted by the Caret API operation.

Only one action type is currently defined by the API - DIRECT_NAVIGATION. This type identifies operations actions, whose only task is to reposition the caret (i.e. left/right, page-up, document-begin) from actions, which position caret as a part of larger task (i.e. search, goto type, ...).

Navigation Filters

The Caret API implements and extends the concept of swing NavigationFilters. The Filter gets notified on CaretInfo movement; so if more Carets are present, a NavigationFilter will see all their moves. Navigation Filters are called with a special instance of FilterBypass extending NavigationFilterBypass. The Filter can downcast the FilterBypass to access extended information about the current caret's movement:


    public void setDot(FilterBypass fb, int dot, Position.Bias bias) {
      if (fb instanceof NavigationFilterBypass) {
        NavigationFilterBypass nfb = (NavigationFilterBypass)fb;
   
        // get the Origin object created by the caret-moving operation, can query the details
        MoveCaretsOrigin origin = nfb.getOrigin();
   
        // get the individual caret in multi-caret scenario
        CaretInfo info = nfb.getCaretInfo();
   
        // get the whole EditorCaret
        EditorCaret eCaret = nfb.getEditorCaret();
      }
    }
    

Navigation filters can be also selectively registered for only certain type of actions described by MoveCaretsOrigin instance.


        EditorCaret eCaret = .... ; // obtain EditorCaret
        eCaret.setNavigationFilter(
          new NavigationFilter() {
                 // navigation filter implementation, not important for the example
          }, 
          new MoveCaretsOrigin(MoveCaretsOrigin.DIRECT_NAVIGATION)
        );
    

Such filters are only called if the caller of Caret API provides a suitable MoveCaretsOrigin description of the move operatoion.

          
        // Action perform method
        editorCaret.moveCarets(new CaretMoveHandler() {
             @Override
             public void moveCarets(CaretMoveContext context) {
                 ...
             }
         }, new MoveCaretsOrigin(
                 // The action is a raw movement command
                 MoveCaretsOrigin.DIRECT_NAVIGATION, 
                 // The approximate direction of the movement; can be 0.
                 SwingConstants.NORTH)
         );
        
      
Swing NavigationFilters, specified by TextComponent.setNavigationFilter() are called for all caret movements.

Abstract boilerplate for NavigationFilters is created, CascadingNavigationFilter which allows to chain individual filters. Implementors are encouraged to use it, or otherwise pass the control to previously registered NavigationFilter in case the movement event is not handled by the custom implementation.

Backwards compatibility

As a technical limitation, EditorCaret has to implement Caret to be able to work with Swings Text API. The Caret interface is not aware of multiple carets and a call to setDot(int) will only retain a single caret. For multiple carets a call to moveDot(int) will move the last caret only (but it retains other existing carets).+
editorTextComponent.getCaret() will now always return EditorCaret instance instead of the original caret implementation org.netbeans.editor.BaseCaret. Since clients were expected to only rely on javax.swing.text.Caret returned type (except internal code in editor modules) they should not be affected by the change since EditorCaret implements Caret interface.
With official Caret API the clients can start to cast editorTextComponent.getCaret() to EditorCaret type and use its extended functionality.

Use cases

Use cases are shown in javadoc documentation of particular methods.

Skip navigation links
org.netbeans.modules.editor.lib2/1 2.33.0 55