@@ -114,7 +114,7 @@ public Token[] ScriptTokens
114
114
}
115
115
116
116
/// <summary>
117
- /// Gets the array of filepaths dot sourced in this ScriptFile
117
+ /// Gets the array of filepaths dot sourced in this ScriptFile
118
118
/// </summary>
119
119
public string [ ] ReferencedFiles
120
120
{
@@ -227,7 +227,7 @@ public static IList<string> GetLines(string text)
227
227
}
228
228
229
229
/// <summary>
230
- /// Deterines whether the supplied path indicates the file is an "untitled:Unitled-X"
230
+ /// Deterines whether the supplied path indicates the file is an "untitled:Unitled-X"
231
231
/// which has not been saved to file.
232
232
/// </summary>
233
233
/// <param name="path">The path to check.</param>
@@ -311,12 +311,38 @@ public void ValidatePosition(BufferPosition bufferPosition)
311
311
/// <param name="column">The 1-based column to be validated.</param>
312
312
public void ValidatePosition ( int line , int column )
313
313
{
314
- int maxLine = this . FileLines . Count ;
314
+ ValidatePosition ( line , column , isInsertion : false ) ;
315
+ }
316
+
317
+ /// <summary>
318
+ /// Throws ArgumentOutOfRangeException if the given position is outside
319
+ /// of the file's buffer extents. If the position is for an insertion (an applied change)
320
+ /// the index may be 1 past the end of the file, which is just appended.
321
+ /// </summary>
322
+ /// <param name="line">The 1-based line to be validated.</param>
323
+ /// <param name="column">The 1-based column to be validated.</param>
324
+ /// <param name="isInsertion">If true, the position to validate is for an applied change.</param>
325
+ public void ValidatePosition ( int line , int column , bool isInsertion )
326
+ {
327
+ // If new content is being added, VSCode sometimes likes to add it at (FileLines.Count + 1),
328
+ // which used to crash EditorServices. Now we append it on to the end of the file.
329
+ // See https://github.com/PowerShell/vscode-powershell/issues/1283
330
+ int maxLine = isInsertion ? this . FileLines . Count + 1 : this . FileLines . Count ;
315
331
if ( line < 1 || line > maxLine )
316
332
{
317
333
throw new ArgumentOutOfRangeException ( $ "Position { line } :{ column } is outside of the line range of 1 to { maxLine } .") ;
318
334
}
319
335
336
+ // If we are inserting at the end of the file, the column should be 1
337
+ if ( isInsertion && line == maxLine )
338
+ {
339
+ if ( column != 1 )
340
+ {
341
+ throw new ArgumentOutOfRangeException ( $ "Insertion at the end of a file must occur at column 1") ;
342
+ }
343
+ return ;
344
+ }
345
+
320
346
// The maximum column is either **one past** the length of the string
321
347
// or 1 if the string is empty.
322
348
string lineString = this . FileLines [ line - 1 ] ;
@@ -347,51 +373,65 @@ public void ApplyChange(FileChange fileChange)
347
373
}
348
374
else
349
375
{
350
- this . ValidatePosition ( fileChange . Line , fileChange . Offset ) ;
351
- this . ValidatePosition ( fileChange . EndLine , fileChange . EndOffset ) ;
352
-
353
- // Get the first fragment of the first line
354
- string firstLineFragment =
355
- this . FileLines [ fileChange . Line - 1 ]
356
- . Substring ( 0 , fileChange . Offset - 1 ) ;
357
-
358
- // Get the last fragment of the last line
359
- string endLine = this . FileLines [ fileChange . EndLine - 1 ] ;
360
- string lastLineFragment =
361
- endLine . Substring (
362
- fileChange . EndOffset - 1 ,
363
- ( this . FileLines [ fileChange . EndLine - 1 ] . Length - fileChange . EndOffset ) + 1 ) ;
364
-
365
- // Remove the old lines
366
- for ( int i = 0 ; i <= fileChange . EndLine - fileChange . Line ; i ++ )
367
- {
368
- this . FileLines . RemoveAt ( fileChange . Line - 1 ) ;
369
- }
376
+ this . ValidatePosition ( fileChange . Line , fileChange . Offset , isInsertion : true ) ;
377
+ this . ValidatePosition ( fileChange . EndLine , fileChange . EndOffset , isInsertion : true ) ;
370
378
371
- // Build and insert the new lines
372
- int currentLineNumber = fileChange . Line ;
373
- for ( int changeIndex = 0 ; changeIndex < changeLines . Length ; changeIndex ++ )
379
+ // VSCode sometimes likes to give the change start line as (FileLines.Count + 1).
380
+ // This used to crash EditorServices, but we now treat it as an append.
381
+ // See https://github.com/PowerShell/vscode-powershell/issues/1283
382
+ if ( fileChange . Line == this . FileLines . Count + 1 )
374
383
{
375
- // Since we split the lines above using \n, make sure to
376
- // trim the ending \r's off as well.
377
- string finalLine = changeLines [ changeIndex ] . TrimEnd ( '\r ' ) ;
378
-
379
- // Should we add first or last line fragments?
380
- if ( changeIndex == 0 )
384
+ foreach ( string addedLine in changeLines )
381
385
{
382
- // Append the first line fragment
383
- finalLine = firstLineFragment + finalLine ;
386
+ string finalLine = addedLine . TrimEnd ( ' \r ' ) ;
387
+ this . FileLines . Add ( finalLine ) ;
384
388
}
385
- if ( changeIndex == changeLines . Length - 1 )
389
+ }
390
+ // Otherwise, the change needs to go between existing content
391
+ else
392
+ {
393
+ // Get the first fragment of the first line
394
+ string firstLineFragment =
395
+ this . FileLines [ fileChange . Line - 1 ]
396
+ . Substring ( 0 , fileChange . Offset - 1 ) ;
397
+
398
+ // Get the last fragment of the last line
399
+ string endLine = this . FileLines [ fileChange . EndLine - 1 ] ;
400
+ string lastLineFragment =
401
+ endLine . Substring (
402
+ fileChange . EndOffset - 1 ,
403
+ ( this . FileLines [ fileChange . EndLine - 1 ] . Length - fileChange . EndOffset ) + 1 ) ;
404
+
405
+ // Remove the old lines
406
+ for ( int i = 0 ; i <= fileChange . EndLine - fileChange . Line ; i ++ )
386
407
{
387
- // Append the last line fragment
388
- finalLine = finalLine + lastLineFragment ;
408
+ this . FileLines . RemoveAt ( fileChange . Line - 1 ) ;
389
409
}
390
410
391
- this . FileLines . Insert ( currentLineNumber - 1 , finalLine ) ;
392
- currentLineNumber ++ ;
393
- }
411
+ // Build and insert the new lines
412
+ int currentLineNumber = fileChange . Line ;
413
+ for ( int changeIndex = 0 ; changeIndex < changeLines . Length ; changeIndex ++ )
414
+ {
415
+ // Since we split the lines above using \n, make sure to
416
+ // trim the ending \r's off as well.
417
+ string finalLine = changeLines [ changeIndex ] . TrimEnd ( '\r ' ) ;
394
418
419
+ // Should we add first or last line fragments?
420
+ if ( changeIndex == 0 )
421
+ {
422
+ // Append the first line fragment
423
+ finalLine = firstLineFragment + finalLine ;
424
+ }
425
+ if ( changeIndex == changeLines . Length - 1 )
426
+ {
427
+ // Append the last line fragment
428
+ finalLine = finalLine + lastLineFragment ;
429
+ }
430
+
431
+ this . FileLines . Insert ( currentLineNumber - 1 , finalLine ) ;
432
+ currentLineNumber ++ ;
433
+ }
434
+ }
395
435
}
396
436
397
437
// Parse the script again to be up-to-date
0 commit comments