@@ -53,14 +53,9 @@ import {
53
53
} from "./_namespaces/ts.server" ;
54
54
import * as protocol from "./protocol" ;
55
55
56
- export interface ScriptInfoVersion {
57
- svc : number ;
58
- text : number ;
59
- }
60
-
61
56
/** @internal */
62
57
export class TextStorage {
63
- version : ScriptInfoVersion ;
58
+ version : number ;
64
59
65
60
/**
66
61
* Generated only on demand (based on edits, or information requested)
@@ -74,6 +69,7 @@ export class TextStorage {
74
69
* Only on edits to the script version cache, the text will be set to undefined
75
70
*/
76
71
private text : string | undefined ;
72
+ private textSnapshot : IScriptSnapshot | undefined ;
77
73
/**
78
74
* Line map for the text when there is no script version cache present
79
75
*/
@@ -100,24 +96,20 @@ export class TextStorage {
100
96
*/
101
97
private pendingReloadFromDisk = false ;
102
98
103
- constructor ( private readonly host : ServerHost , private readonly info : ScriptInfo , initialVersion ?: ScriptInfoVersion ) {
104
- this . version = initialVersion || { svc : 0 , text : 0 } ;
99
+ constructor ( private readonly host : ServerHost , private readonly info : ScriptInfo , initialVersion ?: number ) {
100
+ this . version = initialVersion || 0 ;
105
101
}
106
102
107
103
public getVersion ( ) {
108
104
return this . svc
109
- ? `SVC-${ this . version . svc } -${ this . svc . getSnapshotVersion ( ) } `
110
- : `Text-${ this . version . text } ` ;
105
+ ? `SVC-${ this . version } -${ this . svc . getSnapshotVersion ( ) } `
106
+ : `Text-${ this . version } ` ;
111
107
}
112
108
113
109
public hasScriptVersionCache_TestOnly ( ) {
114
110
return this . svc !== undefined ;
115
111
}
116
112
117
- public useScriptVersionCache_TestOnly ( ) {
118
- this . switchToScriptVersionCache ( ) ;
119
- }
120
-
121
113
private resetSourceMapInfo ( ) {
122
114
this . info . sourceFileLike = undefined ;
123
115
this . info . closeSourceMapFileWatcher ( ) ;
@@ -131,16 +123,18 @@ export class TextStorage {
131
123
public useText ( newText : string ) {
132
124
this . svc = undefined ;
133
125
this . text = newText ;
126
+ this . textSnapshot = undefined ;
134
127
this . lineMap = undefined ;
135
128
this . fileSize = undefined ;
136
129
this . resetSourceMapInfo ( ) ;
137
- this . version . text ++ ;
130
+ this . version ++ ;
138
131
}
139
132
140
133
public edit ( start : number , end : number , newText : string ) {
141
134
this . switchToScriptVersionCache ( ) . edit ( start , end - start , newText ) ;
142
135
this . ownFileText = false ;
143
136
this . text = undefined ;
137
+ this . textSnapshot = undefined ;
144
138
this . lineMap = undefined ;
145
139
this . fileSize = undefined ;
146
140
this . resetSourceMapInfo ( ) ;
@@ -161,7 +155,12 @@ export class TextStorage {
161
155
// we are switching back to text.
162
156
// The change to version cache will happen when needed
163
157
// Thus avoiding the computation if there are no changes
158
+ if ( ! this . text && this . svc ) {
159
+ // Ensure we have text representing current state
160
+ this . text = getSnapshotText ( this . svc . getSnapshot ( ) ) ;
161
+ }
164
162
if ( this . text !== newText ) {
163
+ // Update the text
165
164
this . useText ( newText ) ;
166
165
// We cant guarantee new text is own file text
167
166
this . ownFileText = false ;
@@ -176,7 +175,9 @@ export class TextStorage {
176
175
* returns true if text changed
177
176
*/
178
177
public reloadWithFileText ( tempFileName ?: string ) {
179
- const { text : newText , fileSize } = this . getFileTextAndSize ( tempFileName ) ;
178
+ const { text : newText , fileSize } = tempFileName || ! this . info . isDynamicOrHasMixedContent ( ) ?
179
+ this . getFileTextAndSize ( tempFileName ) :
180
+ { text : "" , fileSize : undefined } ;
180
181
const reloaded = this . reload ( newText ) ;
181
182
this . fileSize = fileSize ; // NB: after reload since reload clears it
182
183
this . ownFileText = ! tempFileName || tempFileName === this . info . fileName ;
@@ -216,46 +217,52 @@ export class TextStorage {
216
217
}
217
218
218
219
public getSnapshot ( ) : IScriptSnapshot {
219
- return this . useScriptVersionCacheIfValidOrOpen ( )
220
- ? this . svc ! . getSnapshot ( )
221
- : ScriptSnapshot . fromString ( this . getOrLoadText ( ) ) ;
220
+ return this . tryUseScriptVersionCache ( ) ?. getSnapshot ( ) ||
221
+ ( this . textSnapshot ??= ScriptSnapshot . fromString ( Debug . checkDefined ( this . text ) ) ) ;
222
222
}
223
223
224
- public getAbsolutePositionAndLineText ( line : number ) : AbsolutePositionAndLineText {
225
- return this . switchToScriptVersionCache ( ) . getAbsolutePositionAndLineText ( line ) ;
224
+ public getAbsolutePositionAndLineText ( oneBasedLine : number ) : AbsolutePositionAndLineText {
225
+ const svc = this . tryUseScriptVersionCache ( ) ;
226
+ if ( svc ) svc . getAbsolutePositionAndLineText ( oneBasedLine ) ;
227
+ const lineMap = this . getLineMap ( ) ;
228
+ return oneBasedLine <= lineMap . length ?
229
+ {
230
+ absolutePosition : lineMap [ oneBasedLine - 1 ] ,
231
+ lineText : this . text ! . substring ( lineMap [ oneBasedLine - 1 ] , lineMap [ oneBasedLine ] ) ,
232
+ } :
233
+ {
234
+ absolutePosition : this . text ! . length ,
235
+ lineText : undefined ,
236
+ } ;
226
237
}
227
238
/**
228
239
* @param line 0 based index
229
240
*/
230
241
lineToTextSpan ( line : number ) : TextSpan {
231
- if ( ! this . useScriptVersionCacheIfValidOrOpen ( ) ) {
232
- const lineMap = this . getLineMap ( ) ;
233
- const start = lineMap [ line ] ; // -1 since line is 1-based
234
- const end = line + 1 < lineMap . length ? lineMap [ line + 1 ] : this . text ! . length ;
235
- return createTextSpanFromBounds ( start , end ) ;
236
- }
237
- return this . svc ! . lineToTextSpan ( line ) ;
242
+ const svc = this . tryUseScriptVersionCache ( ) ;
243
+ if ( svc ) return svc . lineToTextSpan ( line ) ;
244
+ const lineMap = this . getLineMap ( ) ;
245
+ const start = lineMap [ line ] ; // -1 since line is 1-based
246
+ const end = line + 1 < lineMap . length ? lineMap [ line + 1 ] : this . text ! . length ;
247
+ return createTextSpanFromBounds ( start , end ) ;
238
248
}
239
249
240
250
/**
241
251
* @param line 1 based index
242
252
* @param offset 1 based index
243
253
*/
244
254
lineOffsetToPosition ( line : number , offset : number , allowEdits ?: true ) : number {
245
- if ( ! this . useScriptVersionCacheIfValidOrOpen ( ) ) {
246
- return computePositionOfLineAndCharacter ( this . getLineMap ( ) , line - 1 , offset - 1 , this . text , allowEdits ) ;
247
- }
248
-
249
- // TODO: assert this offset is actually on the line
250
- return this . svc ! . lineOffsetToPosition ( line , offset ) ;
255
+ const svc = this . tryUseScriptVersionCache ( ) ;
256
+ return svc ?
257
+ svc . lineOffsetToPosition ( line , offset ) :
258
+ computePositionOfLineAndCharacter ( this . getLineMap ( ) , line - 1 , offset - 1 , this . text , allowEdits ) ;
251
259
}
252
260
253
261
positionToLineOffset ( position : number ) : protocol . Location {
254
- if ( ! this . useScriptVersionCacheIfValidOrOpen ( ) ) {
255
- const { line, character } = computeLineAndCharacterOfPosition ( this . getLineMap ( ) , position ) ;
256
- return { line : line + 1 , offset : character + 1 } ;
257
- }
258
- return this . svc ! . positionToLineOffset ( position ) ;
262
+ const svc = this . tryUseScriptVersionCache ( ) ;
263
+ if ( svc ) return svc . positionToLineOffset ( position ) ;
264
+ const { line, character } = computeLineAndCharacterOfPosition ( this . getLineMap ( ) , position ) ;
265
+ return { line : line + 1 , offset : character + 1 } ;
259
266
}
260
267
261
268
private getFileTextAndSize ( tempFileName ?: string ) : { text : string , fileSize ?: number } {
@@ -276,23 +283,29 @@ export class TextStorage {
276
283
return { text : getText ( ) } ;
277
284
}
278
285
279
- private switchToScriptVersionCache ( ) : ScriptVersionCache {
286
+ /** @internal */
287
+ switchToScriptVersionCache ( ) : ScriptVersionCache {
280
288
if ( ! this . svc || this . pendingReloadFromDisk ) {
281
289
this . svc = ScriptVersionCache . fromString ( this . getOrLoadText ( ) ) ;
282
- this . version . svc ++ ;
290
+ this . textSnapshot = undefined ;
291
+ this . version ++ ;
283
292
}
284
293
return this . svc ;
285
294
}
286
295
287
- private useScriptVersionCacheIfValidOrOpen ( ) : ScriptVersionCache | undefined {
288
- // If this is open script, use the cache
289
- if ( this . isOpen ) {
290
- return this . switchToScriptVersionCache ( ) ;
296
+ private tryUseScriptVersionCache ( ) : ScriptVersionCache | undefined {
297
+ if ( ! this . svc || this . pendingReloadFromDisk ) {
298
+ // Ensure updated text
299
+ this . getOrLoadText ( ) ;
291
300
}
292
301
293
- // If there is pending reload from the disk then, reload the text
294
- if ( this . pendingReloadFromDisk ) {
295
- this . reloadWithFileText ( ) ;
302
+ // If this is open script, use the cache
303
+ if ( this . isOpen ) {
304
+ if ( ! this . svc && ! this . textSnapshot ) {
305
+ this . svc = ScriptVersionCache . fromString ( Debug . checkDefined ( this . text ) ) ;
306
+ this . textSnapshot = undefined ;
307
+ }
308
+ return this . svc ;
296
309
}
297
310
298
311
// At this point if svc is present it's valid
@@ -309,14 +322,15 @@ export class TextStorage {
309
322
310
323
private getLineMap ( ) {
311
324
Debug . assert ( ! this . svc , "ScriptVersionCache should not be set" ) ;
312
- return this . lineMap || ( this . lineMap = computeLineStarts ( this . getOrLoadText ( ) ) ) ;
325
+ return this . lineMap || ( this . lineMap = computeLineStarts ( Debug . checkDefined ( this . text ) ) ) ;
313
326
}
314
327
315
328
getLineInfo ( ) : LineInfo {
316
- if ( this . svc ) {
329
+ const svc = this . tryUseScriptVersionCache ( ) ;
330
+ if ( svc ) {
317
331
return {
318
- getLineCount : ( ) => this . svc ! . getLineCount ( ) ,
319
- getLineText : line => this . svc ! . getAbsolutePositionAndLineText ( line + 1 ) . lineText !
332
+ getLineCount : ( ) => svc . getLineCount ( ) ,
333
+ getLineText : line => svc . getAbsolutePositionAndLineText ( line + 1 ) . lineText !
320
334
} ;
321
335
}
322
336
const lineMap = this . getLineMap ( ) ;
@@ -353,7 +367,8 @@ export class ScriptInfo {
353
367
354
368
/** @internal */
355
369
fileWatcher : FileWatcher | undefined ;
356
- private textStorage : TextStorage ;
370
+ /** @internal */
371
+ readonly textStorage : TextStorage ;
357
372
358
373
/** @internal */
359
374
readonly isDynamic : boolean ;
@@ -391,29 +406,18 @@ export class ScriptInfo {
391
406
readonly scriptKind : ScriptKind ,
392
407
public readonly hasMixedContent : boolean ,
393
408
readonly path : Path ,
394
- initialVersion ?: ScriptInfoVersion ) {
409
+ initialVersion ?: number ) {
395
410
this . isDynamic = isDynamicFileName ( fileName ) ;
396
411
397
412
this . textStorage = new TextStorage ( host , this , initialVersion ) ;
398
413
if ( hasMixedContent || this . isDynamic ) {
399
- this . textStorage . reload ( "" ) ;
400
414
this . realpath = this . path ;
401
415
}
402
416
this . scriptKind = scriptKind
403
417
? scriptKind
404
418
: getScriptKindFromFileName ( fileName ) ;
405
419
}
406
420
407
- /** @internal */
408
- getVersion ( ) {
409
- return this . textStorage . version ;
410
- }
411
-
412
- /** @internal */
413
- getTelemetryFileSize ( ) {
414
- return this . textStorage . getTelemetryFileSize ( ) ;
415
- }
416
-
417
421
/** @internal */
418
422
public isDynamicOrHasMixedContent ( ) {
419
423
return this . hasMixedContent || this . isDynamic ;
@@ -423,7 +427,7 @@ export class ScriptInfo {
423
427
return this . textStorage . isOpen ;
424
428
}
425
429
426
- public open ( newText : string ) {
430
+ public open ( newText : string | undefined ) {
427
431
this . textStorage . isOpen = true ;
428
432
if ( newText !== undefined &&
429
433
this . textStorage . reload ( newText ) ) {
@@ -434,12 +438,7 @@ export class ScriptInfo {
434
438
435
439
public close ( fileExists = true ) {
436
440
this . textStorage . isOpen = false ;
437
- if ( this . isDynamicOrHasMixedContent ( ) || ! fileExists ) {
438
- if ( this . textStorage . reload ( "" ) ) {
439
- this . markContainingProjectsAsDirty ( ) ;
440
- }
441
- }
442
- else if ( this . textStorage . reloadFromDisk ( ) ) {
441
+ if ( fileExists && this . textStorage . reloadFromDisk ( ) ) {
443
442
this . markContainingProjectsAsDirty ( ) ;
444
443
}
445
444
}
@@ -644,25 +643,13 @@ export class ScriptInfo {
644
643
}
645
644
646
645
reloadFromFile ( tempFileName ?: NormalizedPath ) {
647
- if ( this . isDynamicOrHasMixedContent ( ) ) {
648
- this . textStorage . reload ( "" ) ;
646
+ if ( this . textStorage . reloadWithFileText ( tempFileName ) ) {
649
647
this . markContainingProjectsAsDirty ( ) ;
650
648
return true ;
651
649
}
652
- else {
653
- if ( this . textStorage . reloadWithFileText ( tempFileName ) ) {
654
- this . markContainingProjectsAsDirty ( ) ;
655
- return true ;
656
- }
657
- }
658
650
return false ;
659
651
}
660
652
661
- /** @internal */
662
- getAbsolutePositionAndLineText ( line : number ) : AbsolutePositionAndLineText {
663
- return this . textStorage . getAbsolutePositionAndLineText ( line ) ;
664
- }
665
-
666
653
editContent ( start : number , end : number , newText : string ) : void {
667
654
this . textStorage . edit ( start , end , newText ) ;
668
655
this . markContainingProjectsAsDirty ( ) ;
@@ -714,11 +701,6 @@ export class ScriptInfo {
714
701
return this . scriptKind === ScriptKind . JS || this . scriptKind === ScriptKind . JSX ;
715
702
}
716
703
717
- /** @internal */
718
- getLineInfo ( ) : LineInfo {
719
- return this . textStorage . getLineInfo ( ) ;
720
- }
721
-
722
704
/** @internal */
723
705
closeSourceMapFileWatcher ( ) {
724
706
if ( this . sourceMapFilePath && ! isString ( this . sourceMapFilePath ) ) {
0 commit comments