@@ -114,7 +114,7 @@ public Token[] ScriptTokens
114114 }
115115
116116 /// <summary>
117- /// Gets the array of filepaths dot sourced in this ScriptFile
117+ /// Gets the array of filepaths dot sourced in this ScriptFile
118118 /// </summary>
119119 public string [ ] ReferencedFiles
120120 {
@@ -227,7 +227,7 @@ public static IList<string> GetLines(string text)
227227 }
228228
229229 /// <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"
231231 /// which has not been saved to file.
232232 /// </summary>
233233 /// <param name="path">The path to check.</param>
@@ -311,12 +311,38 @@ public void ValidatePosition(BufferPosition bufferPosition)
311311 /// <param name="column">The 1-based column to be validated.</param>
312312 public void ValidatePosition ( int line , int column )
313313 {
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 ;
315331 if ( line < 1 || line > maxLine )
316332 {
317333 throw new ArgumentOutOfRangeException ( $ "Position { line } :{ column } is outside of the line range of 1 to { maxLine } .") ;
318334 }
319335
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+
320346 // The maximum column is either **one past** the length of the string
321347 // or 1 if the string is empty.
322348 string lineString = this . FileLines [ line - 1 ] ;
@@ -347,51 +373,65 @@ public void ApplyChange(FileChange fileChange)
347373 }
348374 else
349375 {
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 ) ;
370378
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 )
374383 {
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 )
381385 {
382- // Append the first line fragment
383- finalLine = firstLineFragment + finalLine ;
386+ string finalLine = addedLine . TrimEnd ( ' \r ' ) ;
387+ this . FileLines . Add ( finalLine ) ;
384388 }
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 ++ )
386407 {
387- // Append the last line fragment
388- finalLine = finalLine + lastLineFragment ;
408+ this . FileLines . RemoveAt ( fileChange . Line - 1 ) ;
389409 }
390410
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 ' ) ;
394418
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+ }
395435 }
396436
397437 // Parse the script again to be up-to-date
0 commit comments