diff --git a/src/PowerShellEditorServices/Workspace/ScriptFile.cs b/src/PowerShellEditorServices/Workspace/ScriptFile.cs index b34985476..e8cb7aac1 100644 --- a/src/PowerShellEditorServices/Workspace/ScriptFile.cs +++ b/src/PowerShellEditorServices/Workspace/ScriptFile.cs @@ -90,7 +90,7 @@ public ScriptFileMarker[] SyntaxMarkers /// /// Gets the list of strings for each line of the file. /// - internal IList FileLines + internal List FileLines { get; private set; @@ -197,7 +197,18 @@ public ScriptFile( /// /// Input string to be split up into lines. /// The lines in the string. + [Obsolete("This method is not designed for public exposure and will be retired in later versions of EditorServices")] public static IList GetLines(string text) + { + return GetLinesInternal(text); + } + + /// + /// Get the lines in a string. + /// + /// Input string to be split up into lines. + /// The lines in the string. + internal static List GetLinesInternal(string text) { if (text == null) { @@ -311,38 +322,12 @@ public void ValidatePosition(BufferPosition bufferPosition) /// The 1-based column to be validated. public void ValidatePosition(int line, int column) { - ValidatePosition(line, column, isInsertion: false); - } - - /// - /// Throws ArgumentOutOfRangeException if the given position is outside - /// of the file's buffer extents. If the position is for an insertion (an applied change) - /// the index may be 1 past the end of the file, which is just appended. - /// - /// The 1-based line to be validated. - /// The 1-based column to be validated. - /// If true, the position to validate is for an applied change. - public void ValidatePosition(int line, int column, bool isInsertion) - { - // If new content is being added, VSCode sometimes likes to add it at (FileLines.Count + 1), - // which used to crash EditorServices. Now we append it on to the end of the file. - // See https://github.com/PowerShell/vscode-powershell/issues/1283 - int maxLine = isInsertion ? this.FileLines.Count + 1 : this.FileLines.Count; + int maxLine = this.FileLines.Count; if (line < 1 || line > maxLine) { throw new ArgumentOutOfRangeException($"Position {line}:{column} is outside of the line range of 1 to {maxLine}."); } - // If we are inserting at the end of the file, the column should be 1 - if (isInsertion && line == maxLine) - { - if (column != 1) - { - throw new ArgumentOutOfRangeException($"Insertion at the end of a file must occur at column 1"); - } - return; - } - // The maximum column is either **one past** the length of the string // or 1 if the string is empty. string lineString = this.FileLines[line - 1]; @@ -354,6 +339,19 @@ public void ValidatePosition(int line, int column, bool isInsertion) } } + + /// + /// Defunct ValidatePosition method call. The isInsertion parameter is ignored. + /// + /// + /// + /// + [Obsolete("Use ValidatePosition(int, int) instead")] + public void ValidatePosition(int line, int column, bool isInsertion) + { + ValidatePosition(line, column); + } + /// /// Applies the provided FileChange to the file's contents /// @@ -373,9 +371,6 @@ public void ApplyChange(FileChange fileChange) } else { - this.ValidatePosition(fileChange.Line, fileChange.Offset, isInsertion: true); - this.ValidatePosition(fileChange.EndLine, fileChange.EndOffset, isInsertion: true); - // VSCode sometimes likes to give the change start line as (FileLines.Count + 1). // This used to crash EditorServices, but we now treat it as an append. // See https://github.com/PowerShell/vscode-powershell/issues/1283 @@ -387,9 +382,19 @@ public void ApplyChange(FileChange fileChange) this.FileLines.Add(finalLine); } } + // Similarly, when lines are deleted from the end of the file, + // VSCode likes to give the end line as (FileLines.Count + 1). + else if (fileChange.EndLine == this.FileLines.Count + 1 && String.Empty.Equals(fileChange.InsertString)) + { + int lineIndex = fileChange.Line - 1; + this.FileLines.RemoveRange(lineIndex, this.FileLines.Count - lineIndex); + } // Otherwise, the change needs to go between existing content else { + this.ValidatePosition(fileChange.Line, fileChange.Offset); + this.ValidatePosition(fileChange.EndLine, fileChange.EndOffset); + // Get the first fragment of the first line string firstLineFragment = this.FileLines[fileChange.Line - 1] @@ -576,7 +581,7 @@ private void SetFileContents(string fileContents) { // Split the file contents into lines and trim // any carriage returns from the strings. - this.FileLines = GetLines(fileContents); + this.FileLines = GetLinesInternal(fileContents); // Parse the contents to get syntax tree and errors this.ParseFileContents(); diff --git a/test/PowerShellEditorServices.Test/Session/ScriptFileTests.cs b/test/PowerShellEditorServices.Test/Session/ScriptFileTests.cs index 620454bb1..4d8ec3f80 100644 --- a/test/PowerShellEditorServices.Test/Session/ScriptFileTests.cs +++ b/test/PowerShellEditorServices.Test/Session/ScriptFileTests.cs @@ -143,6 +143,23 @@ public void CanApplyEditsToEndOfFile() }); } + [Fact] + public void CanAppendToEndOfFile() + { + this.AssertFileChange( + "line1\r\nline2\r\nline3", + "line1\r\nline2\r\nline3\r\nline4\r\nline5", + new FileChange + { + Line = 4, + EndLine = 5, + Offset = 1, + EndOffset = 1, + InsertString = "line4\r\nline5" + } + ); + } + [Fact] public void FindsDotSourcedFiles() { @@ -181,7 +198,7 @@ public void ThrowsExceptionWithEditOutsideOfRange() new FileChange { Line = 3, - EndLine = 7, + EndLine = 8, Offset = 1, EndOffset = 1, InsertString = "" @@ -189,6 +206,23 @@ public void ThrowsExceptionWithEditOutsideOfRange() }); } + [Fact] + public void CanDeleteFromEndOfFile() + { + this.AssertFileChange( + "line1\r\nline2\r\nline3\r\nline4", + "line1\r\nline2", + new FileChange + { + Line = 3, + EndLine = 5, + Offset = 1, + EndOffset = 1, + InsertString = "" + } + ); + } + internal static ScriptFile CreateScriptFile(string initialString) { using (StringReader stringReader = new StringReader(initialString))