@@ -15,27 +15,46 @@ import LanguageServerProtocol
15
15
import LSPLogging
16
16
import SKSupport
17
17
18
- public struct DocumentSnapshot {
19
- public var document : Document
20
- public var version : Int
21
- public var lineTable : LineTable
22
- /// Syntax highlighting tokens for the document. Note that
23
- /// `uri` + `latestVersion` only uniquely identifies a snapshot's content,
24
- /// the tokens are updated independently and only used internally.
25
- public var tokens : DocumentTokens
18
+ /// An immutable snapshot of a document at a given time.
19
+ ///
20
+ /// ``DocumentSnapshot`` is always derived from a ``Document``. That is, the
21
+ /// data structure that is stored internally by the ``DocumentManager`` is a
22
+ /// ``Document``. The purpose of a ``DocumentSnapshot`` is to be able to work
23
+ /// with one version of a document without having to think about it changing.
24
+ public struct DocumentSnapshot : Identifiable {
25
+ /// An ID that uniquely identifies the version of the document stored in this
26
+ /// snapshot.
27
+ public struct ID : Hashable , Comparable {
28
+ public let uri : DocumentURI
29
+ public let version : Int
30
+
31
+ /// Returns `true` if the snapshots reference the same document but rhs has a
32
+ /// later version than `lhs`.
33
+ ///
34
+ /// Snapshot IDs of different documents are not comparable to each other and
35
+ /// will always return `false`.
36
+ public static func < ( lhs: DocumentSnapshot . ID , rhs: DocumentSnapshot . ID ) -> Bool {
37
+ return lhs. uri == rhs. uri && lhs. version < rhs. version
38
+ }
39
+ }
40
+
41
+ public let id : ID
42
+ public let language : Language
43
+ public let lineTable : LineTable
26
44
45
+ public var uri : DocumentURI { id. uri }
46
+ public var version : Int { id. version }
27
47
public var text : String { lineTable. content }
28
48
29
49
public init (
30
- document: Document ,
50
+ uri: DocumentURI ,
51
+ language: Language ,
31
52
version: Int ,
32
- lineTable: LineTable ,
33
- tokens: DocumentTokens
53
+ lineTable: LineTable
34
54
) {
35
- self . document = document
36
- self . version = version
55
+ self . id = ID ( uri : uri , version : version )
56
+ self . language = language
37
57
self . lineTable = lineTable
38
- self . tokens = tokens
39
58
}
40
59
41
60
func index( of pos: Position ) -> String . Index ? {
@@ -48,23 +67,21 @@ public final class Document {
48
67
public let language : Language
49
68
var latestVersion : Int
50
69
var latestLineTable : LineTable
51
- var latestTokens : DocumentTokens
52
70
53
71
init ( uri: DocumentURI , language: Language , version: Int , text: String ) {
54
72
self . uri = uri
55
73
self . language = language
56
74
self . latestVersion = version
57
75
self . latestLineTable = LineTable ( text)
58
- self . latestTokens = DocumentTokens ( )
59
76
}
60
77
61
78
/// **Not thread safe!** Use `DocumentManager.latestSnapshot` instead.
62
79
fileprivate var latestSnapshot : DocumentSnapshot {
63
80
DocumentSnapshot (
64
- document: self ,
81
+ uri: self . uri,
82
+ language: self . language,
65
83
version: latestVersion,
66
- lineTable: latestLineTable,
67
- tokens: latestTokens
84
+ lineTable: latestLineTable
68
85
)
69
86
}
70
87
}
@@ -118,28 +135,31 @@ public final class DocumentManager {
118
135
119
136
/// Applies the given edits to the document.
120
137
///
121
- /// - parameter willEditDocument: Optional closure to call before each edit.
122
- /// - parameter updateDocumentTokens: Optional closure to call after each edit.
123
- /// - parameter before: The document contents *before* the edit is applied.
124
- /// - parameter after: The document contents *after* the edit is applied.
125
- /// - returns: The contents of the file after all the edits are applied.
126
- /// - throws: Error.missingDocument if the document is not open.
138
+ /// - Parameters:
139
+ /// - uri: The URI of the document to update
140
+ /// - newVersion: The new version of the document. Must be greater than the
141
+ /// latest version of the document.
142
+ /// - edits: The edits to apply to the document
143
+ /// - willEditDocument: Optional closure to call before each edit. Will be
144
+ /// called multiple times if there are multiple edits.
145
+ /// - Returns: The snapshot of the document before the edit and the snapshot
146
+ /// of the document after the edit.
127
147
@discardableResult
128
148
public func edit(
129
149
_ uri: DocumentURI ,
130
150
newVersion: Int ,
131
151
edits: [ TextDocumentContentChangeEvent ] ,
132
- willEditDocument: ( ( _ before: DocumentSnapshot , TextDocumentContentChangeEvent ) -> Void ) ? = nil ,
133
- updateDocumentTokens: ( ( _ after: DocumentSnapshot ) -> DocumentTokens ) ? = nil
134
- ) throws -> DocumentSnapshot {
152
+ willEditDocument: ( ( _ before: LineTable , TextDocumentContentChangeEvent ) -> Void ) ? = nil
153
+ ) throws -> ( preEditSnapshot: DocumentSnapshot , postEditSnapshot: DocumentSnapshot ) {
135
154
return try queue. sync {
136
155
guard let document = documents [ uri] else {
137
156
throw Error . missingDocument ( uri)
138
157
}
158
+ let preEditSnapshot = document. latestSnapshot
139
159
140
160
for edit in edits {
141
- if let f = willEditDocument {
142
- f ( document. latestSnapshot , edit)
161
+ if let willEditDocument {
162
+ willEditDocument ( document. latestLineTable , edit)
143
163
}
144
164
145
165
if let range = edit. range {
@@ -149,49 +169,17 @@ public final class DocumentManager {
149
169
toLine: range. upperBound. line,
150
170
utf16Offset: range. upperBound. utf16index,
151
171
with: edit. text)
152
-
153
- // Remove all tokens in the updated range and shift later ones.
154
- let rangeAdjuster = RangeAdjuster ( edit: edit) !
155
-
156
- document. latestTokens. semantic = document. latestTokens. semantic. compactMap {
157
- var token = $0
158
- if let adjustedRange = rangeAdjuster. adjust ( token. range) {
159
- token. range = adjustedRange
160
- return token
161
- } else {
162
- return nil
163
- }
164
- }
165
172
} else {
166
173
// Full text replacement.
167
174
document. latestLineTable = LineTable ( edit. text)
168
- document. latestTokens = DocumentTokens ( )
169
- }
170
-
171
- if let f = updateDocumentTokens {
172
- document. latestTokens = f ( document. latestSnapshot)
173
175
}
174
176
}
175
177
176
- document. latestVersion = newVersion
177
- return document. latestSnapshot
178
- }
179
- }
180
-
181
- /// Updates the tokens in a document.
182
- ///
183
- /// - parameter uri: The URI of the document to be updated
184
- /// - parameter tokens: The new tokens for the document
185
- @discardableResult
186
- public func updateTokens( _ uri: DocumentURI , tokens: DocumentTokens ) throws -> DocumentSnapshot {
187
- return try queue. sync {
188
- guard let document = documents [ uri] else {
189
- throw Error . missingDocument ( uri)
178
+ if newVersion <= document. latestVersion {
179
+ log ( " Document version did not increase on edit from \( document. latestVersion) to \( newVersion) " , level: . error)
190
180
}
191
-
192
- document. latestTokens = tokens
193
-
194
- return document. latestSnapshot
181
+ document. latestVersion = newVersion
182
+ return ( preEditSnapshot, document. latestSnapshot)
195
183
}
196
184
}
197
185
@@ -230,16 +218,14 @@ extension DocumentManager {
230
218
@discardableResult
231
219
func edit(
232
220
_ note: DidChangeTextDocumentNotification ,
233
- willEditDocument: ( ( _ before: DocumentSnapshot , TextDocumentContentChangeEvent ) -> Void ) ? = nil ,
234
- updateDocumentTokens: ( ( _ after: DocumentSnapshot ) -> DocumentTokens ) ? = nil
235
- ) -> DocumentSnapshot ? {
221
+ willEditDocument: ( ( _ before: LineTable , TextDocumentContentChangeEvent ) -> Void ) ? = nil
222
+ ) -> ( preEditSnapshot: DocumentSnapshot , postEditSnapshot: DocumentSnapshot ) ? {
236
223
return orLog ( " failed to edit document " , level: . error) {
237
- try edit (
224
+ return try edit (
238
225
note. textDocument. uri,
239
226
newVersion: note. textDocument. version,
240
227
edits: note. contentChanges,
241
- willEditDocument: willEditDocument,
242
- updateDocumentTokens: updateDocumentTokens
228
+ willEditDocument: willEditDocument
243
229
)
244
230
}
245
231
}
0 commit comments