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 VersionedDocument and DocumentSnapshot, which are most similar to the Source and Snapshot classes (respectively) in the Parsing API. Here are some particular similarities.
Similarities between VersionedDocument and Source
- Each can represent a file on disk or document currently opened for editing.
- Each can provide a "snapshot" of the document contents.
Similarities between DocumentSnapshot and Snapshot
- 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 versioning
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.
The 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 int.
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 DocumentSnapshot. The getSnapshot() and 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 OffsetRegion class.
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 SnapshotPositionRegion. The getSnapshot() and getRegion() methods return the snapshot and OffsetRegion for the position region. The methods getStart() and 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).
The TrackingPosition and 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).
The TrackingPosition.Bias enumeration provides Forward and Backward, which results in the position moving towards the end or beginning of a document (respectively) in response to text insertions at the position.
The TrackingPositionRegion.Bias enumeration provides values for each combination of TrackingPosition.Bias for the start and end points of the position region. The values Forward and 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 Forward.
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.
Swing
- The
Documentinterface expresses a document, but operations on it are not thread-safe and it doesn't support the concepts of snapshots and versions. - The
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. - The
Position.Biasenumeration expresses a position bias, but there are no methods provided by Swing interfaces for actually creating a position with a specified bias.
NetBeans
- The
PositionRegionclass holds a pair ofPositionobjects, but suffers from the same drawbacks ofPositionmentioned above. - The
BaseDocumentinterface provides acreatePositionmethod 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 toDocumentListenerevents.
Visual Studio SDK
- The
ITextSnapshotandITextVersioninterfaces provide features similar toDocumentSnapshotandDocumentVersion. - The
SnapshotPointandSnapshotSpanstructures provide features similar toSnapshotPositionandSnapshotPositionRegion.
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 DocumentSnapshot.findLineFromLineNumber, DocumentSnapshot.findLineFromOffset, and SnapshotPosition.getContainingLine.
VersionedDocumentUtilities
The VersionedDocumentUtilities class provides overloaded getVersionedDocument methods which return a VersionedDocument from a Document or FileObject.
Implementation details
TODO
LineTextCache
TODO
Lazily tracked positions and regions
TODO