The Document Snapshot API is primarily a consolidation and extension of several low-level features which previously existed in the NetBeans source code but were not usable as a whole to third-party extensions. Most of the central features of the API draw from the NetBeans Editor Library 2 and Parsing API.
The core interfaces in the Document Snapshot API are
DocumentSnapshot, which are most similar to the
Snapshot classes (respectively) in the Parsing API. Here are some particular similarities.
- Each can represent a file on disk or document currently opened for editing.
- Each can provide a "snapshot" of the document contents.
- Each represents an immutable view of document contents at some point in time.
At this point, the APIs quickly diverge. The NetBeans Parsing API extends on the above to provide specific parsing features such as access to the
TokenHierarchy and the ability to represent
Embeddings. The Document Snapshot API extends on the above to address synchronization issues which arise when asynchronous operations are occurring while documents are being edited. Since the Parsing API is already well documented, from here on I'll focus on the specifics of the Document Snapshot API.
Document versions are a way of expressing the relative points in time for which multiple snapshots apply. If the user types text in a document while an asynchronous operation is processing a
DocumentSnapshot, the operation's snapshot represents an earlier version of the text, and the text after the user's edits is the most recent or current version of the document. The
VersionedDocument.getCurrentSnapshot() method provides a
DocumentSnapshot for the current version of the document.
DocumentVersion interface provides specific information about document versions. The
getVersionNumber() method returns an integer, where higher values represent later versions of a document. The
getNext() method returns the following version (or
null if called on the most recent version), and the
getChanges() method returns a collection of changes which were applied to the current version to get the next version.
Positions and regions within a document
An offset is a single location represented by an integer. The offset n represents the location before the _n_th character (0-based). In the Document Snapshot API, offsets have the primitive type
A position is an offset within the context of a specific document. In the Document Snapshot API, the
SnapshotPosition class is used to represent an offset within a specific
getOffset() methods return the snapshot and offset for the position.
A region is a pair of offsets. Regions are expressed either by their start offset and length or by their start and end (exclusive) offsets. In the Document Snapshot API, a region is represented by the
A position region is a pair of positions, also described as a region within the context of a specific document. In the Document Snapshot API, a position region has the type
getRegion() methods return the snapshot and
OffsetRegion for the position region. The methods
getEnd() return the start and end (exclusive) positions of the region.
Tracking positions and regions between document versions
The Document Snapshot API really shows its strength in the ability to create a position or position region in one snapshot, and then map the value to a new snapshot. The snapshot and mapping methods are thread-safe, so they may be used by asynchronous operations or called on the foreground thread with the current document version to consistently mark locations found during an asynchronous operation (eg. marking errors from a background parse).
TrackingPositionRegion interfaces represent positions and position regions which can be translated from one document version to another. The tracking operations can be expressed with a bias for specific handling of document changes at the position (or endpoints for a position region).
TrackingPosition.Bias enumeration provides
Backward, which results in the position moving towards the end or beginning of a document (respectively) in response to text insertions at the position.
TrackingPositionRegion.Bias enumeration provides values for each combination of
TrackingPosition.Bias for the start and end points of the position region. The values
Backward map both the start and end positions towards the end or beginning of a document (respectively) in response to text insertions at the start or end positions of the region. The value
Exclusive maps the start position
Forward and the end position
Backward. The value
Inclusive maps the start position
Backward and the end position
To allow substantial optimization in the implementation, tracking positions and position regions are also expressed with a tracking fidelity (with the
TrackingFidelity enumeration). By default the fidelity is
Forward, which means operations only support translating a position or position region from an older document version to a newer document version. This value is appropriate for almost all mapping scenarios encountered during asynchronous operations in the IDE.
Similar concepts in other APIs
The above concepts are certainly not new. Advanced processing for new features in IDEs demands asynchronous operations, and expressing locations and versions of documents has become necessary but not always clean.
Documentinterface expresses a document, but operations on it are not thread-safe and it doesn't support the concepts of snapshots and versions.
Positioninterface expresses a position, but without snapshots the object is not thread safe and the implementation eagerly updates the offset as the document changes, up until the garbage collector collects the position.
Position.Biasenumeration expresses a position bias, but there are no methods provided by Swing interfaces for actually creating a position with a specified bias.
PositionRegionclass holds a pair of
Positionobjects, but suffers from the same drawbacks of
BaseDocumentinterface provides a
createPositionmethod which takes a specified bias as an argument. The created positions suffer from the same drawbacks listed above.
- The NetBeans implementation of
BaseDocumentincrements an internal version number when the document is edited, but the number is not publicly exposed so code operating on documents must implement their own versioning in response to
Visual Studio SDK
ITextVersioninterfaces provide features similar to
SnapshotSpanstructures provide features similar to
Lines and columns
Methods for mapping between document offsets and line/column numbers are provided by the
DocumentSnapshot interface and
SnapshotPosition class. Line and column numbers are 0-based, and column numbers treat all characters (including "hard" tab characters) as taking up exactly one column. The
DocumentSnapshotLine interface provides access to information about a line including the line number, text, and position region. An instance of this interface is returned by
Lazily tracked positions and regions