diff --git a/PSReadLine/BasicEditing.cs b/PSReadLine/BasicEditing.cs
index 6dcdc9f90..a59bdf4ef 100644
--- a/PSReadLine/BasicEditing.cs
+++ b/PSReadLine/BasicEditing.cs
@@ -267,10 +267,6 @@ private bool AcceptLineImpl(bool validate)
_emphasisStart = -1;
_emphasisLength = 0;
- var insertionPoint = _current;
- // Make sure cursor is at the end before writing the line
- _current = _buffer.Length;
-
if (renderNeeded)
{
ForceRender();
@@ -281,6 +277,7 @@ private bool AcceptLineImpl(bool validate)
// can report an error as it normally does.
if (validate && !_statusIsErrorMessage)
{
+ var insertionPoint = _current;
var errorMessage = Validate(_ast);
if (!string.IsNullOrWhiteSpace(errorMessage))
{
@@ -308,8 +305,13 @@ private bool AcceptLineImpl(bool validate)
ClearStatusMessage(render: true);
}
- // Let public API set cursor to end of line incase end of line is end of buffer
- SetCursorPosition(_current);
+ // Make sure cursor is at the end before writing the line.
+ if (_current != _buffer.Length)
+ {
+ // Let public API set cursor to end of line incase end of line is end of buffer.
+ _current = _buffer.Length;
+ SetCursorPosition(_current);
+ }
if (_prediction.ActiveView is PredictionListView listView)
{
diff --git a/PSReadLine/PSReadLineResources.Designer.cs b/PSReadLine/PSReadLineResources.Designer.cs
index bef39e858..d107632a6 100644
--- a/PSReadLine/PSReadLineResources.Designer.cs
+++ b/PSReadLine/PSReadLineResources.Designer.cs
@@ -2191,5 +2191,16 @@ internal static string SelectCommandArgumentDescription
return ResourceManager.GetString("SelectCommandArgumentDescription", resourceCulture);
}
}
+
+ ///
+ /// Looks up a localized string similar to: Cannot locate the offset in the rendered text that was pointed by the original cursor. Initial Coord: ({0}, {1}) Buffer: ({2}, {3}) Cursor: ({4}, {5}).
+ ///
+ internal static string FailedToConvertPointToRenderDataOffset
+ {
+ get
+ {
+ return ResourceManager.GetString("FailedToConvertPointToRenderDataOffset", resourceCulture);
+ }
+ }
}
}
diff --git a/PSReadLine/PSReadLineResources.resx b/PSReadLine/PSReadLineResources.resx
index 61ca0ff41..f57835029 100644
--- a/PSReadLine/PSReadLineResources.resx
+++ b/PSReadLine/PSReadLineResources.resx
@@ -855,4 +855,7 @@ Or not saving history with:
Make visual selection of the command arguments.
+
+ Cannot locate the offset in the rendered text that was pointed by the original cursor. Initial Coord: ({0}, {1}) Buffer: ({2}, {3}) Cursor: ({4}, {5})
+
diff --git a/PSReadLine/ReadLine.cs b/PSReadLine/ReadLine.cs
index 7fd302394..ba1a49247 100644
--- a/PSReadLine/ReadLine.cs
+++ b/PSReadLine/ReadLine.cs
@@ -509,6 +509,10 @@ private string InputLoop()
var moveToLineCommandCount = _moveToLineCommandCount;
var moveToEndOfLineCommandCount = _moveToEndOfLineCommandCount;
+ // We attempt to handle window resizing only once per a keybinding processing, because we assume the
+ // window resizing cannot and shouldn't happen within the processing of a given keybinding.
+ _handlePotentialResizing = true;
+
var key = ReadKey();
ProcessOneKey(key, _dispatchTable, ignoreIfNoAction: false, arg: null);
if (_inputAccepted)
@@ -709,10 +713,6 @@ private void Initialize(Runspace runspace, EngineIntrinsics engineIntrinsics)
_delayedOneTimeInitCompleted = true;
}
- _previousRender = _initialPrevRender;
- _previousRender.bufferWidth = _console.BufferWidth;
- _previousRender.bufferHeight = _console.BufferHeight;
- _previousRender.errorPrompt = false;
_buffer.Clear();
_edits = new List();
_undoEditIndex = 0;
@@ -728,6 +728,9 @@ private void Initialize(Runspace runspace, EngineIntrinsics engineIntrinsics)
_initialY = _console.CursorTop;
_initialForeground = _console.ForegroundColor;
_initialBackground = _console.BackgroundColor;
+ _previousRender = _initialPrevRender;
+ _previousRender.UpdateConsoleInfo(_console);
+ _previousRender.initialY = _initialY;
_statusIsErrorMessage = false;
_initialOutputEncoding = _console.OutputEncoding;
@@ -1033,6 +1036,8 @@ public static void InvokePrompt(ConsoleKeyInfo? key = null, object arg = null)
_singleton._initialX = console.CursorLeft;
_singleton._initialY = console.CursorTop;
_singleton._previousRender = _initialPrevRender;
+ _singleton._previousRender.UpdateConsoleInfo(console);
+ _singleton._previousRender.initialY = _singleton._initialY;
_singleton.Render();
console.CursorVisible = true;
diff --git a/PSReadLine/Render.Helper.cs b/PSReadLine/Render.Helper.cs
index d99313621..9fd67159d 100644
--- a/PSReadLine/Render.Helper.cs
+++ b/PSReadLine/Render.Helper.cs
@@ -45,12 +45,12 @@ private static string Spaces(int cnt)
: new string(' ', cnt);
}
- private static int LengthInBufferCells(string str)
+ internal static int LengthInBufferCells(string str)
{
return LengthInBufferCells(str, 0, str.Length);
}
- private static int LengthInBufferCells(string str, int start, int end)
+ internal static int LengthInBufferCells(string str, int start, int end)
{
var sum = 0;
for (var i = start; i < end; i++)
@@ -70,7 +70,7 @@ private static int LengthInBufferCells(string str, int start, int end)
return sum;
}
- private static int LengthInBufferCells(char c)
+ internal static int LengthInBufferCells(char c)
{
if (c < 256)
{
diff --git a/PSReadLine/Render.cs b/PSReadLine/Render.cs
index 354cd8149..94df96316 100644
--- a/PSReadLine/Render.cs
+++ b/PSReadLine/Render.cs
@@ -12,6 +12,7 @@
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.PowerShell.Internal;
+using Microsoft.PowerShell.PSReadLine;
namespace Microsoft.PowerShell
{
@@ -26,6 +27,115 @@ public override string ToString()
}
}
+ internal class RenderedLineData
+ {
+ public readonly string Line;
+ private readonly bool _isFirstLogicalLine;
+
+ private int _physicalLineCount, _lengthOfLastPhsicalLine;
+ private int _bufferWidth, _initialX;
+
+ public RenderedLineData(string line, bool isFirstLogicalLine)
+ {
+ Line = line;
+ _isFirstLogicalLine = isFirstLogicalLine;
+ }
+
+ public int PhysicalLineCount(int bufferWidth, int initialX, out int lenLastPhysicalLine)
+ {
+ bool useCachedValues = bufferWidth == _bufferWidth && (!_isFirstLogicalLine || initialX == _initialX);
+ if (useCachedValues)
+ {
+ lenLastPhysicalLine = _lengthOfLastPhsicalLine;
+ return _physicalLineCount;
+ }
+
+ _bufferWidth = bufferWidth;
+ _initialX = initialX;
+
+ // The first logical line has the user prompt.
+ int x = _isFirstLogicalLine ? initialX : 0;
+ int y = 1;
+ lenLastPhysicalLine = 0;
+
+ for (int i = 0; i < Line.Length; i++)
+ {
+ var c = Line[i];
+
+ // Simple escape sequence skipping.
+ if (c == 0x1b && (i + 1) < Line.Length && Line[i + 1] == '[')
+ {
+ i += 2;
+ while (i < Line.Length && Line[i] != 'm')
+ {
+ i++;
+ }
+
+ continue;
+ }
+
+ int size = PSConsoleReadLine.LengthInBufferCells(c);
+ if (x == 0 && lenLastPhysicalLine > 0)
+ {
+ y++;
+ lenLastPhysicalLine = 0;
+ }
+
+ x += size;
+ lenLastPhysicalLine += size;
+
+ if (x == bufferWidth)
+ {
+ x = 0;
+ }
+ else if (x > bufferWidth)
+ {
+ // It could wrap to the next line in case of a multi-cell character.
+ // If character didn't fit on current line, it will move entirely to the next line.
+ x = size;
+ y++;
+ lenLastPhysicalLine = size;
+ }
+ }
+
+ _lengthOfLastPhsicalLine = lenLastPhysicalLine;
+ _physicalLineCount = y;
+
+ return y;
+ }
+ }
+
+ internal class RenderData
+ {
+ public int bufferWidth;
+ public int bufferHeight;
+ public int cursorLeft;
+ public int cursorTop;
+ public int initialY;
+ public bool errorPrompt;
+ public RenderedLineData[] lines;
+
+ public void UpdateConsoleInfo(IConsole console)
+ {
+ bufferWidth = console.BufferWidth;
+ bufferHeight = console.BufferHeight;
+ cursorLeft = console.CursorLeft;
+ cursorTop = console.CursorTop;
+ }
+ }
+
+ internal readonly struct RenderDataOffset
+ {
+ public RenderDataOffset(int logicalLineIndex, int visibleCharIndex)
+ {
+ LogicalLineIndex = logicalLineIndex;
+ VisibleCharIndex = visibleCharIndex;
+ }
+
+ public readonly int LogicalLineIndex;
+ public readonly int VisibleCharIndex;
+ }
+
public partial class PSConsoleReadLine
{
struct LineInfoForRendering
@@ -37,31 +147,19 @@ struct LineInfoForRendering
public int PseudoPhysicalLineOffset;
}
- struct RenderedLineData
- {
- public string line;
- public int columns;
- }
-
- class RenderData
- {
- public int bufferWidth;
- public int bufferHeight;
- public bool errorPrompt;
- public RenderedLineData[] lines;
- }
-
private const int COMMON_WIDEST_CONSOLE_WIDTH = 160;
- private readonly List _consoleBufferLines = new List(1) {new StringBuilder(COMMON_WIDEST_CONSOLE_WIDTH)};
+ private readonly List _consoleBufferLines = new(1) {new StringBuilder(COMMON_WIDEST_CONSOLE_WIDTH)};
private static readonly string[] _spaces = new string[80];
private RenderData _previousRender;
- private static readonly RenderData _initialPrevRender = new RenderData
+ private static readonly RenderData _initialPrevRender = new()
{
- lines = new[] { new RenderedLineData{ columns = 0, line = ""}}
+ lines = new[] { new RenderedLineData(line: "", isFirstLogicalLine: true) },
+ errorPrompt = false
};
private int _initialX;
private int _initialY;
private bool _waitingToRender;
+ private bool _handlePotentialResizing;
private ConsoleColor _initialForeground;
private ConsoleColor _initialBackground;
@@ -145,8 +243,7 @@ private void ForceRender()
for (var i = 0; i < logicalLineCount; i++)
{
var line = _consoleBufferLines[i].ToString();
- renderLines[i].line = line;
- renderLines[i].columns = LengthInBufferCells(line);
+ renderLines[i] = new RenderedLineData(line, isFirstLogicalLine: i == 0);
}
// And then do the real work of writing to the screen.
@@ -467,52 +564,6 @@ private bool RenderErrorPrompt(RenderData renderData, string defaultColor)
return true;
}
- ///
- /// Given the length of a logical line, calculate the number of physical lines it takes to render
- /// the logical line on the console.
- ///
- private int PhysicalLineCount(int columns, bool isFirstLogicalLine, out int lenLastPhysicalLine)
- {
- if (columns == 0)
- {
- // This could happen for a new logical line with an empty-string continuation prompt.
- lenLastPhysicalLine = 0;
- return 1;
- }
-
- int cnt = 1;
- int bufferWidth = _console.BufferWidth;
-
- if (isFirstLogicalLine)
- {
- // The first logical line has the user prompt that we don't touch
- // (except where we turn part to red, but we've finished that
- // before getting here.)
- var maxFirstLine = bufferWidth - _initialX;
- if (columns > maxFirstLine)
- {
- cnt += 1;
- columns -= maxFirstLine;
- }
- else
- {
- lenLastPhysicalLine = columns;
- return 1;
- }
- }
-
- lenLastPhysicalLine = columns % bufferWidth;
- if (lenLastPhysicalLine == 0)
- {
- // Handle the last column when the columns is equal to n * bufferWidth
- // where n >= 1 integers
- lenLastPhysicalLine = bufferWidth;
- return cnt - 1 + columns / bufferWidth;
- }
-
- return cnt + columns / bufferWidth;
- }
-
///
/// We avoid re-rendering everything while editing if it's possible.
/// This method attempts to find the first changed logical line and move the cursor to the right position for the subsequent rendering.
@@ -565,9 +616,9 @@ private void CalculateWhereAndWhatToRender(bool cursorMovedToInitialPos, RenderD
for (; logicalLine < minLinesLength; logicalLine++)
{
// Found the first different logical line? Break out the loop.
- if (renderLines[logicalLine].line != previousRenderLines[logicalLine].line) { break; }
+ if (renderLines[logicalLine].Line != previousRenderLines[logicalLine].Line) { break; }
- int count = PhysicalLineCount(renderLines[logicalLine].columns, logicalLine == 0, out _);
+ int count = renderLines[logicalLine].PhysicalLineCount(bufferWidth, _initialX, out _);
physicalLine += count;
if (linesToCheck < 0)
@@ -678,9 +729,7 @@ void UpdateColorsIfNecessary(string newColor)
}
// In case the buffer was resized
- RecomputeInitialCoords();
- renderData.bufferWidth = bufferWidth;
- renderData.bufferHeight = bufferHeight;
+ RecomputeInitialCoords(isTextBufferUnchanged: false);
// Make cursor invisible while we're rendering.
_console.CursorVisible = false;
@@ -689,8 +738,7 @@ void UpdateColorsIfNecessary(string newColor)
bool cursorMovedToInitialPos = RenderErrorPrompt(renderData, defaultColor);
// Calculate what to render and where to start the rendering.
- LineInfoForRendering lineInfoForRendering;
- CalculateWhereAndWhatToRender(cursorMovedToInitialPos, renderData, out lineInfoForRendering);
+ CalculateWhereAndWhatToRender(cursorMovedToInitialPos, renderData, out LineInfoForRendering lineInfoForRendering);
RenderedLineData[] previousRenderLines = _previousRender.lines;
int previousLogicalLine = lineInfoForRendering.PreviousLogicalLineIndex;
@@ -710,9 +758,9 @@ void UpdateColorsIfNecessary(string newColor)
if (logicalLine != logicalLineStartIndex) _console.Write("\n");
var lineData = renderLines[logicalLine];
- _console.Write(lineData.line);
+ _console.Write(lineData.Line);
- physicalLine += PhysicalLineCount(lineData.columns, logicalLine == 0, out int lenLastLine);
+ physicalLine += lineData.PhysicalLineCount(bufferWidth, _initialX, out int lenLastLine);
// Find the previous logical line (if any) that would have rendered
// the current physical line because we may need to clear it.
@@ -722,9 +770,7 @@ void UpdateColorsIfNecessary(string newColor)
while (physicalLine > previousPhysicalLine
&& previousLogicalLine < previousRenderLines.Length)
{
- previousPhysicalLine += PhysicalLineCount(previousRenderLines[previousLogicalLine].columns,
- previousLogicalLine == 0,
- out lenPrevLastLine);
+ previousPhysicalLine += previousRenderLines[previousLogicalLine].PhysicalLineCount(bufferWidth, _initialX, out lenPrevLastLine);
previousLogicalLine += 1;
}
@@ -768,10 +814,13 @@ void UpdateColorsIfNecessary(string newColor)
_console.SetCursorPosition(0, _initialY + currentLines);
currentLines++;
- var lenToClear = currentLines == previousPhysicalLine ? lenPrevLastLine : bufferWidth;
- if (lenToClear > 0)
+ if (currentLines == previousPhysicalLine)
{
- _console.Write(Spaces(lenToClear));
+ _console.Write(Spaces(lenPrevLastLine));
+ }
+ else
+ {
+ _console.BlankRestOfLine();
}
}
@@ -791,7 +840,8 @@ void UpdateColorsIfNecessary(string newColor)
}
// No need to write new line if all we need is to clear the extra previous render.
- _console.Write(Spaces(previousRenderLines[line].columns));
+ int lineCount = previousRenderLines[line].PhysicalLineCount(bufferWidth, _initialX, out _);
+ WriteBlankLines(lineCount);
}
// Preserve the current render data.
@@ -872,6 +922,9 @@ void UpdateColorsIfNecessary(string newColor)
_console.SetCursorPosition(point.X, point.Y);
_console.CursorVisible = true;
+ _previousRender.UpdateConsoleInfo(_console);
+ _previousRender.initialY = _initialY;
+
// TODO: set WindowTop if necessary
_lastRenderTime.Restart();
@@ -945,19 +998,127 @@ private void GetRegion(out int start, out int length)
}
}
- private void RecomputeInitialCoords()
+ private void RecomputeInitialCoords(bool isTextBufferUnchanged)
{
- if ((_previousRender.bufferWidth != _console.BufferWidth)
- || (_previousRender.bufferHeight != _console.BufferHeight))
+ if (!_handlePotentialResizing)
+ {
+ return;
+ }
+
+ // We attempt to handle window resizing only once per a keybinding processing, because we assume the
+ // window resizing cannot and shouldn't happen within the processing of a given keybinding.
+ // This is, in particular, to avoid unneeded checks while we are in the 'MenuComplete' or a similar
+ // function that handles some keystroke inputs directly within the function, does rendering multiple
+ // times, and changes cursor position directly by '_console.SetCursorPosition'.
+ // For 'MenuComplete', we will not attempt to handle resizing while the menu is displayed, because
+ // that's simply a wrong thing to do.
+ _handlePotentialResizing = false;
+
+ // Operations like menu completion and inline dynamic help may cause the screen buffer to scroll up,
+ // and '_initialY' would have been adjusted accordingly.
+ // In that case, we need to adjust the old cursor position accordingly too.
+ int preInitialY = _previousRender.initialY;
+ if (preInitialY != _initialY)
+ {
+ _previousRender.cursorTop -= preInitialY - _initialY;
+ _previousRender.initialY = _initialY;
+ }
+
+ if (_previousRender.bufferWidth == _console.BufferWidth &&
+ _previousRender.bufferHeight == _console.BufferHeight)
+ {
+ int left = _console.CursorLeft;
+ int top = _console.CursorTop;
+
+ int preLeft = _previousRender.cursorLeft;
+ int preTop = _previousRender.cursorTop;
+
+ if (preLeft == left && preTop > top)
+ {
+ // Try to handle a special scenario: the max-size terminal windows gets restored to
+ // the normal size, and then is immediately changed to max size again.
+ _initialY -= preTop - top;
+ }
+ else if (left == 0 && top == 0 && preLeft != 0 && preTop != 0)
+ {
+ // Try to handle a special scenario: a Terminal User Interface (TUI) utility is used
+ // with a custom key-binding to get rich editing experience, for example:
+ // Set-PSReadlineKeyHandler -Chord "Shift+Tab" -ScriptBlock {
+ // $s = fzf.exe
+ // [Microsoft.PowerShell.PSConsoleReadLine]::Insert($s)
+ // }
+ // The TUI utility will likely erase the screen buffer, so we try writing out prompt
+ // and start afresh in this case.
+ string newPrompt = GetPrompt();
+ if (!string.IsNullOrEmpty(newPrompt))
+ {
+ _console.Write(newPrompt);
+ }
+
+ _initialX = _console.CursorLeft;
+ _initialY = _console.CursorTop;
+ _previousRender = _initialPrevRender;
+ }
+
+ return;
+ }
+
+ // If the console buffer width or height changed, our initial coordinates may have as well.
+ if (isTextBufferUnchanged)
{
- // If the buffer width changed, our initial coordinates
- // may have as well.
+ // The '_buffer' and '_current' still reflects what has been rendered on the screen,
+ // so we can use them to re-calculate the initial coordinates in this case.
+
// Recompute X from the buffer width:
- _initialX = _initialX % _console.BufferWidth;
+ _initialX %= _console.BufferWidth;
// Recompute Y from the cursor
_initialY = 0;
+ // Calculate the new cursor position when assuming '_initialY' is at line 0.
var pt = ConvertOffsetToPoint(_current);
+ // Update '_initialY' based on the difference from the actual current cursor position after the resize.
+ _initialY = _console.CursorTop - pt.Y;
+ }
+ else
+ {
+ // The '_buffer' and '_current' have changed since the last rendering, so we cannot rely on them
+ // for the re-calculation. A typical example would be the user clears the input with `Escape` after
+ // a resize. That will cause the '_buffer' to be empty and '_current' to be 0 when we reach here.
+ //
+ // Instead, we will use the saved previous cursor position to re-calculate the initial coordinates,
+ // based on the previous rendering data.
+ // First, calculate the offset in the previous rendering data based on the old initial coordinates,
+ // old buffer width, and the old cursor position.
+ RenderDataOffset offset = ConvertPointToRenderDataOffset(_initialX, _initialY, _previousRender);
+ if (offset.LogicalLineIndex == -1)
+ {
+ // This should never happen unless it's a bug in 'ConvertPointToRenderDataOffset'.
+ string message = string.Format(
+ CultureInfo.CurrentCulture,
+ PSReadLineResources.FailedToConvertPointToRenderDataOffset,
+ _initialX,
+ _initialY,
+ _previousRender.bufferWidth,
+ _previousRender.bufferHeight,
+ _previousRender.cursorLeft,
+ _previousRender.cursorTop);
+ throw new InvalidOperationException(message);
+ }
+
+ // Recompute X from the buffer width:
+ _initialX %= _console.BufferWidth;
+
+ // Recompute Y from the cursor
+ _initialY = 0;
+ // Now, use the new initial coordinates, new buffer width, and the rendering data offset to calculate
+ // the new cursor position when assuming '_initialY' is at line 0.
+ Point pt = ConvertRenderDataOffsetToPoint(_initialX, _initialY, _console.BufferWidth, _previousRender, offset);
+ // Update '_initialY' based on the difference from the actual current cursor position after the resize.
+ // This is based on the assumption that the cursor is still pointing to the same character after resizing,
+ // or at least pointing to the physical line where the same character is located after resizing.
+ // However, that assumption is not always guaranteed in Windows Terminal, see the issue:
+ // https://github.com/microsoft/terminal/issues/10848, and
+ // https://github.com/microsoft/terminal/issues/10868
_initialY = _console.CursorTop - pt.Y;
}
}
@@ -968,9 +1129,7 @@ private void MoveCursor(int newCursor)
if (!_waitingToRender)
{
// In case the buffer was resized
- RecomputeInitialCoords();
- _previousRender.bufferWidth = _console.BufferWidth;
- _previousRender.bufferHeight = _console.BufferHeight;
+ RecomputeInitialCoords(isTextBufferUnchanged: true);
var point = ConvertOffsetToPoint(newCursor);
if (point.Y < 0)
@@ -981,7 +1140,8 @@ private void MoveCursor(int newCursor)
if (point.Y == _console.BufferHeight)
{
- // The cursor top exceeds the buffer height, so adjust the initial cursor
+ // The cursor top exceeds the buffer height. This may happen when moving cursor to the end of line,
+ // while the end of line is actually the end of buffer. In this case, we adjust the initial cursor
// position and the to-be-set cursor position for scrolling up the buffer.
_initialY -= 1;
point.Y -= 1;
@@ -997,6 +1157,9 @@ private void MoveCursor(int newCursor)
{
_console.SetCursorPosition(point.X, point.Y);
}
+
+ _previousRender.UpdateConsoleInfo(_console);
+ _previousRender.initialY = _initialY;
}
// While waiting to render, and a keybinding has occured that is moving the cursor,
@@ -1109,6 +1272,296 @@ private int ConvertLineAndColumnToOffset(Point point)
return (point.Y == y) ? offset : -1;
}
+ internal Point ConvertRenderDataOffsetToPoint(int initialX, int initialY, int bufferWidth, RenderData renderData, RenderDataOffset offset)
+ {
+ if (offset.LogicalLineIndex == 0 && offset.VisibleCharIndex == -1)
+ {
+ // (0, -1) means the cursor should be right at the initial coordinate.
+ return new Point { X = initialX, Y = initialY };
+ }
+
+ int x = initialX;
+ int y = initialY;
+
+ int lengthOfLastPhysicalLine = -1;
+ int limit = offset.LogicalLineIndex;
+ if (offset.VisibleCharIndex == int.MaxValue)
+ {
+ limit++;
+ }
+
+ // Make sure 'x' and 'y' are pointing to the end of the last processed logical line after this loop ends.
+ for (int i = 0; i < limit; i++)
+ {
+ if (i > 0)
+ {
+ // Move 'x' and 'y' to the start position where the next logical line would be rendered from.
+ // If 'x == 0 && lengthOfLastPhysicalLine == 0', it's a special case we need to handle:
+ // * the last logical line starts from the beginning of a physical line (x == 0), AND
+ // * the last logical line contains no visible character.
+ if (x > 0 || lengthOfLastPhysicalLine == 0)
+ {
+ x = 0;
+ y++;
+ }
+ }
+
+ RenderedLineData lineData = renderData.lines[i];
+ int physicalLineCount = lineData.PhysicalLineCount(bufferWidth, initialX, out lengthOfLastPhysicalLine);
+ y += physicalLineCount - 1;
+
+ if (y == initialY)
+ {
+ x += lengthOfLastPhysicalLine;
+ }
+ else
+ {
+ x = lengthOfLastPhysicalLine;
+ }
+
+ if (x == bufferWidth)
+ {
+ // In the case that the length of last physical line takes the whole buffer width,
+ // the cursor would be pushed to the start of the next line.
+ x = 0;
+ y++;
+ }
+ }
+
+ if (offset.VisibleCharIndex == int.MaxValue)
+ {
+ // The cursor is right at the end of the logical line.
+ return new Point { X = x, Y = y };
+ }
+
+ if (limit > 0)
+ {
+ // The logical line we are going to scan character by character is not the first logical line,
+ // so we need to move 'x' and 'y' to the start of the next physical line.
+ if (x > 0 || lengthOfLastPhysicalLine == 0)
+ {
+ x = 0;
+ y++;
+ }
+ }
+
+ int visibleCharIndex = -1, size = 0;
+ string line = renderData.lines[limit].Line;
+ for (int i = 0; i < line.Length; i++)
+ {
+ var c = line[i];
+
+ // Simple escape sequence skipping.
+ if (c == 0x1b && (i + 1) < line.Length && line[i + 1] == '[')
+ {
+ i += 2;
+ while (i < line.Length && line[i] != 'm')
+ {
+ i++;
+ }
+
+ continue;
+ }
+
+ visibleCharIndex++;
+ size = LengthInBufferCells(c);
+
+ if (visibleCharIndex == offset.VisibleCharIndex)
+ {
+ break;
+ }
+
+ x += size;
+ if (x == bufferWidth)
+ {
+ x = 0;
+ y++;
+ }
+ else if (x > bufferWidth)
+ {
+ // It could wrap to the next line in case of a multi-cell character.
+ // If character didn't fit on current line, it will move entirely to the next line.
+ x = size;
+ y++;
+ }
+ }
+
+ // If the offset is pointing to a double-cell character that happens to be wrapped to the next physical line,
+ // then we move 'x' and 'y' to the start of the next physical line, so the new cursor continues to point to
+ // that specific character.
+ if (x + size > bufferWidth)
+ {
+ x = 0;
+ y++;
+ }
+
+ return new Point { X = x, Y = y };
+ }
+
+ internal RenderDataOffset ConvertPointToRenderDataOffset(int initialX, int initialY, RenderData renderData)
+ {
+ int x = initialX;
+ int y = initialY;
+ var point = new Point { X = renderData.cursorLeft, Y = renderData.cursorTop };
+
+ if (point.Y == y && point.X == x)
+ {
+ // The given cursor is the same as the initial coordinate, return (0, -1) in this case.
+ return new RenderDataOffset(logicalLineIndex: 0, visibleCharIndex: -1);
+ }
+
+ if (point.Y < y || (point.Y == y && point.X < x))
+ {
+ // The given cursor is out of range, return (-1, -1).
+ return new RenderDataOffset(-1, -1);
+ }
+
+ int prevX = 0, prevY = 0;
+ int logicalLineIndex = 0;
+ int bufferWidth = renderData.bufferWidth;
+
+ for (; logicalLineIndex < renderData.lines.Length; logicalLineIndex++)
+ {
+ // Make 'prevX' and 'prevY' point to the start position where the current logical line would be rendered from.
+ prevX = x;
+ prevY = y;
+
+ RenderedLineData lineData = renderData.lines[logicalLineIndex];
+ int physicalLineCount = lineData.PhysicalLineCount(bufferWidth, initialX, out int lengthOfLastPhysicalLine);
+ y += physicalLineCount - 1;
+
+ if (y == initialY)
+ {
+ x += lengthOfLastPhysicalLine;
+ }
+ else
+ {
+ x = lengthOfLastPhysicalLine;
+ }
+
+ if (x == bufferWidth)
+ {
+ // In the case that the length of last physical line takes the whole buffer width,
+ // the cursor would be pushed to the start of the next line.
+ x = 0;
+ y++;
+ }
+
+ if (point.Y == y && point.X == x)
+ {
+ // The cursor is right at the end of the logical line.
+ // We use 'int.MaxValue' as the character index to indicate that the whole logical line is included.
+ return new RenderDataOffset(logicalLineIndex, visibleCharIndex: int.MaxValue);
+ }
+
+ if (point.Y < y || (point.Y == y && point.X < x))
+ {
+ // The current logical line covers where the cursor is pointing at, so we will look for the character
+ // index within the logical line next.
+ break;
+ }
+
+ // Now move 'x' and 'y' to the start position where the next logical line would be rendered from.
+ // - when 'x > 0', move to the start of the next line;
+ // - when 'x == 0', we either already did this from above, or it's a special case:
+ // * the current logical line starts from the beginning of a physical line (x == 0), AND
+ // * the current logical line contains no visible character.
+ if (x > 0 || lengthOfLastPhysicalLine == 0)
+ {
+ x = 0;
+ y++;
+ }
+ }
+
+ // If we didn't find the given cursor within the range of the rendered lines, return (-1, -1).
+ if (logicalLineIndex == renderData.lines.Length || point.Y < prevY)
+ {
+ // - logicalLineIndex == renderData.lines.Length
+ // This could happen when the cursor was somehow pointing to a screen buffer position
+ // beyond the ending coordinate of the last logical line.
+ //
+ // - point.Y < prevY
+ // This could happen when the cursor was somehow pointing to a screen buffer position
+ // on the same last physical line of a logical line, but was beyond the ending X of
+ // the logical line. For example, the end of the logical line is at (x:3, y:2), and the
+ // cursor was pointing to (x:7, y:2).
+ //
+ // Both should never happen practically because we should never move cursor to an invalid
+ // position like that. But we need to handle the extreme situation where either of them
+ // just happened due to a bug in our code.
+ return new RenderDataOffset(-1, -1);
+ }
+
+ // Now we have found the logical line that contains the visible character that the cursor was pointing at.
+ // Move 'x' and 'y' back to the start point where that logical line would be rendered from.
+ x = prevX;
+ y = prevY;
+
+ // If it's right at where the cursor was pointing to, then we are done.
+ if (point.Y == y && point.X == x)
+ {
+ return new RenderDataOffset(logicalLineIndex, 0);
+ }
+
+ // Now we will scan the current logical line to find which character the cursor was pointing at.
+ int visibleCharIndex = 0;
+ string line = renderData.lines[logicalLineIndex].Line;
+
+ for (int i = 0; i < line.Length; i++)
+ {
+ var c = line[i];
+
+ // Simple escape sequence skipping.
+ if (c == 0x1b && (i + 1) < line.Length && line[i + 1] == '[')
+ {
+ i += 2;
+ while (i < line.Length && line[i] != 'm')
+ {
+ i++;
+ }
+
+ continue;
+ }
+
+ int size = LengthInBufferCells(c);
+ x += size;
+
+ if (x == bufferWidth)
+ {
+ x = 0;
+ y++;
+ }
+ else if (x > bufferWidth)
+ {
+ // It could wrap to the next line in case of a multi-cell character.
+ // If character didn't fit on current line, it will move entirely to the next line.
+ x = size;
+ y++;
+ }
+
+ if (point.Y == y)
+ {
+ if (point.X < x)
+ {
+ // This could happen when the cursor was pointing to a double-cell character
+ // that was wrapped to the next physical line -- because there was only one
+ // cell space left at the end of the previous physical line.
+ return new RenderDataOffset(logicalLineIndex, visibleCharIndex);
+ }
+ else if (point.X == x)
+ {
+ // 'x' is pointing to where the next visible character would be rendered.
+ return new RenderDataOffset(logicalLineIndex, visibleCharIndex + 1);
+ }
+ }
+
+ visibleCharIndex++;
+ }
+
+ // We should never reach here in theory.
+ return new RenderDataOffset(-1, -1);
+ }
+
///
/// Returns the logical line number under the cursor in a multi-line buffer.
/// When rendering, a logical line may span multiple physical lines.
diff --git a/test/PSReadLine.Tests.csproj b/test/PSReadLine.Tests.csproj
index 65accd066..891df2f9d 100644
--- a/test/PSReadLine.Tests.csproj
+++ b/test/PSReadLine.Tests.csproj
@@ -46,18 +46,19 @@
-
+
PreserveNewest
-
-
+ PreserveNewest
+
+
PreserveNewest
-
-
+ PreserveNewest
+
+
+ assets\%(RecursiveDir)\%(FileName)%(Extension)
PreserveNewest
-
-
- PreserveNewest
-
+ PreserveNewest
+
diff --git a/test/ResizingTest.cs b/test/ResizingTest.cs
new file mode 100644
index 000000000..a02ed783a
--- /dev/null
+++ b/test/ResizingTest.cs
@@ -0,0 +1,186 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Reflection;
+using Microsoft.PowerShell;
+using Newtonsoft.Json;
+using Xunit;
+
+namespace Test.Resizing
+{
+#pragma warning disable 0649
+
+ ///
+ /// This class is initialized by JSON deserialization.
+ ///
+ internal sealed class LogicalToPhysicalLineTestData
+ {
+ public string Name;
+ public string Line;
+ public bool IsFirstLogicalLine;
+ public List Context;
+ }
+
+ ///
+ /// This class is initialized by JSON deserialization.
+ ///
+ internal sealed class LogicalToPhysicalLineTestContext
+ {
+ public int BufferWidth;
+ public int InitialX;
+ public int LineCount;
+ public int LastLineLen;
+ }
+
+ ///
+ /// This class is initialized by JSON deserialization.
+ ///
+ internal sealed class ResizingTestData
+ {
+ public string Name;
+ public List Lines;
+ public int OldBufferWidth;
+ public int NewBufferWidth;
+ public List Context;
+ }
+
+ ///
+ /// This class is initialized by JSON deserialization.
+ ///
+ internal sealed class ResizingTestContext
+ {
+ public Point OldInitial;
+ public Point OldCursor;
+ public Point NewInitial;
+ public Point NewCursor;
+ public RenderOffset Offset;
+
+ internal sealed class RenderOffset
+ {
+ public int LineIndex;
+ public int CharIndex;
+ }
+ }
+
+#pragma warning restore 0649
+}
+
+namespace Test
+{
+ using Test.Resizing;
+
+ public partial class ReadLine
+ {
+ private static List s_resizingTestData;
+
+ private void InitializeTestData()
+ {
+ if (s_resizingTestData is null)
+ {
+ string path = Path.Combine("assets", "resizing", "renderdata-to-cursor-point.json");
+ string text = File.ReadAllText(path);
+ s_resizingTestData = JsonConvert.DeserializeObject>(text);
+ }
+ }
+
+ private PSConsoleReadLine GetPSConsoleReadLineSingleton()
+ {
+ return (PSConsoleReadLine)typeof(PSConsoleReadLine)
+ .GetField("_singleton", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null);
+ }
+
+ [Fact]
+ public void ConvertPointToRenderDataOffset_ShouldWork()
+ {
+ InitializeTestData();
+ PSConsoleReadLine instance = GetPSConsoleReadLineSingleton();
+
+ foreach (ResizingTestData test in s_resizingTestData)
+ {
+ RenderData renderData = new()
+ {
+ lines = new RenderedLineData[test.Lines.Count],
+ bufferWidth = test.OldBufferWidth
+ };
+
+ for (int i = 0; i < test.Lines.Count; i++)
+ {
+ renderData.lines[i] = new RenderedLineData(test.Lines[i], isFirstLogicalLine: i == 0);
+ }
+
+ for (int j = 0; j < test.Context.Count; j++)
+ {
+ ResizingTestContext context = test.Context[j];
+ renderData.cursorLeft = context.OldCursor.X;
+ renderData.cursorTop = context.OldCursor.Y;
+
+ RenderDataOffset offset = instance.ConvertPointToRenderDataOffset(context.OldInitial.X, context.OldInitial.Y, renderData);
+ Assert.True(
+ context.Offset.LineIndex == offset.LogicalLineIndex &&
+ context.Offset.CharIndex == offset.VisibleCharIndex,
+ $"{test.Name}-context_{j}: calculated offset is not what's expected [line: {offset.LogicalLineIndex}, char: {offset.VisibleCharIndex}]");
+ }
+ }
+ }
+
+ [Fact]
+ public void ConvertRenderDataOffsetToPoint_ShouldWork()
+ {
+ InitializeTestData();
+ PSConsoleReadLine instance = GetPSConsoleReadLineSingleton();
+
+ foreach (ResizingTestData test in s_resizingTestData)
+ {
+ RenderData renderData = new()
+ {
+ lines = new RenderedLineData[test.Lines.Count],
+ bufferWidth = test.OldBufferWidth
+ };
+
+ for (int i = 0; i < test.Lines.Count; i++)
+ {
+ renderData.lines[i] = new RenderedLineData(test.Lines[i], isFirstLogicalLine: i == 0);
+ }
+
+ for (int j = 0; j < test.Context.Count; j++)
+ {
+ ResizingTestContext context = test.Context[j];
+ if (context.Offset.LineIndex != -1)
+ {
+ renderData.cursorLeft = context.OldCursor.X;
+ renderData.cursorTop = context.OldCursor.Y;
+
+ var offset = new RenderDataOffset(context.Offset.LineIndex, context.Offset.CharIndex);
+ Point newCursor = instance.ConvertRenderDataOffsetToPoint(context.NewInitial.X, context.NewInitial.Y, test.NewBufferWidth, renderData, offset);
+ Assert.True(
+ context.NewCursor.X == newCursor.X &&
+ context.NewCursor.Y == newCursor.Y,
+ $"{test.Name}-context_{j}: calculated new cursor is not what's expected [X: {newCursor.X}, Y: {newCursor.Y}]");
+ }
+ }
+ }
+ }
+
+ [Fact]
+ public void PhysicalLineCountMethod_ShouldWork()
+ {
+ var path = Path.Combine("assets", "resizing", "physical-line-count.json");
+ var text = File.ReadAllText(path);
+ var testDataList = JsonConvert.DeserializeObject>(text);
+
+ foreach (LogicalToPhysicalLineTestData test in testDataList)
+ {
+ RenderedLineData lineData = new(test.Line, test.IsFirstLogicalLine);
+ for (int i = 0; i < test.Context.Count; i++)
+ {
+ LogicalToPhysicalLineTestContext context = test.Context[i];
+ int lineCount = lineData.PhysicalLineCount(context.BufferWidth, context.InitialX, out int lastLinelen);
+ Assert.True(
+ context.LineCount == lineCount &&
+ context.LastLineLen == lastLinelen,
+ $"{test.Name}-context_{i}: calculated physical line count or length of last physical line is not what's expected [count: {lineCount}, lastLen: {lastLinelen}]");
+ }
+ }
+ }
+ }
+}
diff --git a/test/assets/resizing/physical-line-count.json b/test/assets/resizing/physical-line-count.json
new file mode 100644
index 000000000..9daf51929
--- /dev/null
+++ b/test/assets/resizing/physical-line-count.json
@@ -0,0 +1,225 @@
+[
+ {
+ "Name": "BasicTest_1",
+ // The prompt and input used are actually:
+ // PS:1> new-ilNugetPackage -PackagePath C:\arena\tmp\NugetPackages -PackageVersion 7.2.0-preview.1 -WinFxdBinPath C:\Users\rocky\Downloads\Win\ -LinuxFxdBinPath C:\Users\rocky\Downloads\Linux\ -GenAPIToolPath C:\Users\rocky\Tools\genapi
+ "Line": "\u001b[0m\u001b[93mnew-ILNugetPackage\u001b[0m\u001b[39;49m \u001b[0m\u001b[90m-PackagePath\u001b[0m\u001b[39;49m \u001b[0m\u001b[37mC:\\arena\\tmp\\NugetPackages\u001b[0m\u001b[39;49m \u001b[0m\u001b[90m-PackageVersion\u001b[0m\u001b[39;49m \u001b[0m\u001b[37m7.2.0-preview.1\u001b[0m\u001b[39;49m \u001b[0m\u001b[90m-WinFxdBinPath\u001b[0m\u001b[39;49m \u001b[0m\u001b[37mC:\\Users\\rocky\\Downloads\\Win\\\u001b[0m\u001b[39;49m \u001b[0m\u001b[90m-LinuxFxdBinPath\u001b[0m\u001b[39;49m \u001b[0m\u001b[37mC:\\Users\\rocky\\Downloads\\Linux\\\u001b[0m\u001b[39;49m \u001b[0m\u001b[90m-GenAPIToolPath\u001b[0m\u001b[39;49m \u001b[0m\u001b[37mC:\\Users\\rocky\\Tools\\genapi",
+ "IsFirstLogicalLine": true,
+ "Context": [
+ {
+ "BufferWidth": 150,
+ "InitialX": 6,
+ "LineCount": 2,
+ "LastLineLen": 84
+ },
+ {
+ "BufferWidth": 102,
+ "InitialX": 6,
+ "LineCount": 3,
+ "LastLineLen": 30
+ },
+ {
+ "BufferWidth": 59,
+ "InitialX": 6,
+ "LineCount": 4,
+ "LastLineLen": 57
+ },
+ {
+ "BufferWidth": 58,
+ "InitialX": 6,
+ "LineCount": 5,
+ "LastLineLen": 2
+ },
+ {
+ "BufferWidth": 117,
+ "InitialX": 6,
+ "LineCount": 2,
+ "LastLineLen": 117
+ },
+ {
+ "BufferWidth": 234,
+ "InitialX": 6,
+ "LineCount": 1,
+ "LastLineLen": 228
+ },
+ {
+ "BufferWidth": 236,
+ "InitialX": 6,
+ "LineCount": 1,
+ "LastLineLen": 228
+ }
+ ]
+ },
+
+ {
+ "Name": "BasicTest_2",
+ // The continuation prompt and input used are actually:
+ // >> new-ilNugetPackage -PackagePath C:\arena\tmp\NugetPackages -PackageVersion 7.2.0-preview.1 -WinFxdBinPath C:\Users\rocky\Downloads\Win\ -LinuxFxdBinPath C:\Users\rocky\Downloads\Linux\ -GenAPIToolPath C:\Users\rocky\Tools\genapi
+ "Line": "\u001b[0m\u001b[37m>> \u001b[0m\u001b[93mnew-ILNugetPackage\u001b[0m\u001b[39;49m \u001b[0m\u001b[90m-PackagePath\u001b[0m\u001b[39;49m \u001b[0m\u001b[37mC:\\arena\\tmp\\NugetPackages\u001b[0m\u001b[39;49m \u001b[0m\u001b[90m-PackageVersion\u001b[0m\u001b[39;49m \u001b[0m\u001b[37m7.2.0-preview.1\u001b[0m\u001b[39;49m \u001b[0m\u001b[90m-WinFxdBinPath\u001b[0m\u001b[39;49m \u001b[0m\u001b[37mC:\\Users\\rocky\\Downloads\\Win\\\u001b[0m\u001b[39;49m \u001b[0m\u001b[90m-LinuxFxdBinPath\u001b[0m\u001b[39;49m \u001b[0m\u001b[37mC:\\Users\\rocky\\Downloads\\Linux\\\u001b[0m\u001b[39;49m \u001b[0m\u001b[90m-GenAPIToolPath\u001b[0m\u001b[39;49m \u001b[0m\u001b[37mC:\\Users\\rocky\\Tools\\genapi",
+ "IsFirstLogicalLine": false,
+ "Context": [
+ {
+ "BufferWidth": 150,
+ "InitialX": 6,
+ "LineCount": 2,
+ "LastLineLen": 81
+ },
+ {
+ "BufferWidth": 102,
+ "InitialX": 6,
+ "LineCount": 3,
+ "LastLineLen": 27
+ },
+ {
+ "BufferWidth": 77,
+ "InitialX": 6,
+ "LineCount": 3,
+ "LastLineLen": 77
+ },
+ {
+ "BufferWidth": 54,
+ "InitialX": 6,
+ "LineCount": 5,
+ "LastLineLen": 15
+ },
+ {
+ "BufferWidth": 234,
+ "InitialX": 6,
+ "LineCount": 1,
+ "LastLineLen": 231
+ },
+ {
+ "BufferWidth": 236,
+ "InitialX": 6,
+ "LineCount": 1,
+ "LastLineLen": 231
+ }
+ ]
+ },
+
+ {
+ "Name": "BasicTest_3",
+ // Empty prompt, and the input contains no visible character.
+ "Line": "\u001b[0m\u001b[93m",
+ "IsFirstLogicalLine": true,
+ "Context": [
+ {
+ "BufferWidth": 58,
+ "InitialX": 6,
+ "LineCount": 1,
+ "LastLineLen": 0
+ },
+ {
+ "BufferWidth": 54,
+ "InitialX": 0,
+ "LineCount": 1,
+ "LastLineLen": 0
+ }
+ ]
+ },
+
+ {
+ "Name": "BasicTest_4",
+ // Empty prompt, and the input contains no visible character.
+ "Line": "\u001b[0m\u001b[93m",
+ "IsFirstLogicalLine": false,
+ "Context": [
+ {
+ "BufferWidth": 58,
+ "InitialX": 6,
+ "LineCount": 1,
+ "LastLineLen": 0
+ },
+ {
+ "BufferWidth": 54,
+ "InitialX": 0,
+ "LineCount": 1,
+ "LastLineLen": 0
+ }
+ ]
+ },
+
+ {
+ "Name": "BasicTest_5",
+ // Continuation prompt followed by an empty line.
+ "Line": "\u001b[0m\u001b[93m>> ",
+ "IsFirstLogicalLine": false,
+ "Context": [
+ {
+ "BufferWidth": 58,
+ "InitialX": 6,
+ "LineCount": 1,
+ "LastLineLen": 3
+ },
+ {
+ "BufferWidth": 54,
+ "InitialX": 0,
+ "LineCount": 1,
+ "LastLineLen": 3
+ }
+ ]
+ },
+
+ {
+ "Name": "BasicTest_6",
+ // Input is: dir *.psd1 -Recurse | sls -SimpleMatch Desktop-ab
+ "Line": "\u001b[0m\u001b[93mdir\u001b[0m\u001b[39;49m \u001b[0m\u001b[37m*.psd1\u001b[0m\u001b[39;49m \u001b[0m\u001b[90m-Recurse\u001b[0m\u001b[39;49m \u001b[0m\u001b[37m|\u001b[0m\u001b[39;49m \u001b[0m\u001b[93msls\u001b[0m\u001b[39;49m \u001b[0m\u001b[90m-SimpleMatch\u001b[0m\u001b[39;49m \u001b[0m\u001b[37mDesktop-ab",
+ "IsFirstLogicalLine": true,
+ "Context": [
+ {
+ "BufferWidth": 55,
+ "InitialX": 6,
+ "LineCount": 1,
+ "LastLineLen": 49
+ },
+ {
+ "BufferWidth": 54,
+ "InitialX": 6,
+ "LineCount": 2,
+ "LastLineLen": 1
+ }
+ ]
+ },
+
+ {
+ "Name": "BasicTest_7",
+ // Input is: dir *.psd1 -Recurse | sls -SimpleMatch Desktop-有
+ "Line": "\u001b[0m\u001b[93mdir\u001b[0m\u001b[39;49m \u001b[0m\u001b[37m*.psd1\u001b[0m\u001b[39;49m \u001b[0m\u001b[90m-Recurse\u001b[0m\u001b[39;49m \u001b[0m\u001b[37m|\u001b[0m\u001b[39;49m \u001b[0m\u001b[93msls\u001b[0m\u001b[39;49m \u001b[0m\u001b[90m-SimpleMatch\u001b[0m\u001b[39;49m \u001b[0m\u001b[37mDesktop-有",
+ "IsFirstLogicalLine": true,
+ "Context": [
+ {
+ "BufferWidth": 54,
+ "InitialX": 6,
+ "LineCount": 2,
+ "LastLineLen": 2
+ },
+ {
+ "BufferWidth": 54,
+ "InitialX": 7,
+ "LineCount": 2,
+ "LastLineLen": 2
+ }
+ ]
+ },
+
+ {
+ "Name": "BasicTest_8",
+ // dir *.psd1 -Recurse | sls -SimpleMatch Desktop-有人
+ "Line": "\u001b[0m\u001b[93mdir\u001b[0m\u001b[39;49m \u001b[0m\u001b[37m*.psd1\u001b[0m\u001b[39;49m \u001b[0m\u001b[90m-Recurse\u001b[0m\u001b[39;49m \u001b[0m\u001b[37m|\u001b[0m\u001b[39;49m \u001b[0m\u001b[93msls\u001b[0m\u001b[39;49m \u001b[0m\u001b[90m-SimpleMatch\u001b[0m\u001b[39;49m \u001b[0m\u001b[37mDesktop-有人",
+ "IsFirstLogicalLine": true,
+ "Context": [
+ {
+ "BufferWidth": 54,
+ "InitialX": 6,
+ "LineCount": 2,
+ "LastLineLen": 4
+ },
+ {
+ "BufferWidth": 54,
+ "InitialX": 7,
+ "LineCount": 2,
+ "LastLineLen": 4
+ }
+ ]
+ }
+]
diff --git a/test/assets/resizing/renderdata-to-cursor-point.json b/test/assets/resizing/renderdata-to-cursor-point.json
new file mode 100644
index 000000000..d481ad25b
--- /dev/null
+++ b/test/assets/resizing/renderdata-to-cursor-point.json
@@ -0,0 +1,1008 @@
+[
+ {
+ "Name": "BasicTest_1",
+ "Lines": [
+ "\u001b[0m\u001b[93mnew-ilNugetPackage\u001b[0m\u001b[39;49m \u001b[0m\u001b[90m-PackagePath\u001b[0m\u001b[39;49m \u001b[0m\u001b[37mC:\\arena\\tmp\\NugetPackages\u001b[0m\u001b[39;49m \u001b[0m\u001b[90m-PackageVersion\u001b[0m\u001b[39;49m \u001b[0m\u001b[37m7.2.0-preview.1\u001b[0m\u001b[39;49m \u001b[0m\u001b[90m-WinFxdBinPath\u001b[0m\u001b[39;49m \u001b[0m\u001b[37mC:\\Users\\rocky\\Downloads\\Win\\\u001b[0m\u001b[39;49m \u001b[0m\u001b[90m-LinuxFxdBinPath\u001b[0m\u001b[39;49m \u001b[0m\u001b[37mC:\\Users\\rocky\\Downloads\\Linux\\\u001b[0m\u001b[39;49m \u001b[0m\u001b[90m-GenAPIToolPath\u001b[0m\u001b[39;49m \u001b[0m\u001b[37mC:\\Users\\rocky\\Tools\\genapi",
+ "\u001b[0m\u001b[37m>> ",
+ "\u001b[0m\u001b[37m>> \u001b[0m\u001b[93mnew-ilNugetPackage\u001b[0m\u001b[39;49m \u001b[0m\u001b[90m-PackagePath\u001b[0m\u001b[39;49m \u001b[0m\u001b[37mC:\\arena\\tmp\\NugetPackages\u001b[0m\u001b[39;49m \u001b[0m\u001b[90m-PackageVersion\u001b[0m\u001b[39;49m \u001b[0m\u001b[37m7.2.0-preview.1\u001b[0m\u001b[39;49m \u001b[0m\u001b[90m-WinFxdBinPath\u001b[0m\u001b[39;49m \u001b[0m\u001b[37mC:\\Users\\rocky\\Downloads\\Win\\\u001b[0m\u001b[39;49m \u001b[0m\u001b[90m-LinuxFxdBinPath\u001b[0m\u001b[39;49m \u001b[0m\u001b[37mC:\\Users\\rocky\\Downloads\\Linux\\\u001b[0m\u001b[39;49m \u001b[0m\u001b[90m-GenAPIToolPath\u001b[0m\u001b[39;49m \u001b[0m\u001b[37mC:\\Users\\rocky\\Tools\\genapi"
+ ],
+ "OldBufferWidth": 90,
+ "NewBufferWidth": 124,
+ "Context": [
+ {
+ "OldInitial": {
+ "X": 7,
+ "Y": 9
+ },
+ // Pointing to the end of the third logical line.
+ "OldCursor": {
+ "X": 51,
+ "Y": 15
+ },
+
+ "Offset": {
+ "LineIndex": 2,
+ "CharIndex": 2147483647
+ },
+
+ "NewInitial": {
+ "X": 7,
+ "Y": 9
+ },
+ "NewCursor": {
+ "X": 107,
+ "Y": 13
+ }
+ },
+ {
+ "OldInitial": {
+ "X": 7,
+ "Y": 9
+ },
+ // Pointing to '\' at "Downloads\Linux" of the third logical line.
+ "OldCursor": {
+ "X": 0,
+ "Y": 15
+ },
+
+ "Offset": {
+ "LineIndex": 2,
+ "CharIndex": 180
+ },
+
+ "NewInitial": {
+ "X": 7,
+ "Y": 9
+ },
+ "NewCursor": {
+ "X": 56,
+ "Y": 13
+ }
+ },
+ {
+ "OldInitial": {
+ "X": 7,
+ "Y": 9
+ },
+ // Pointing to the space right after "Downloads\Linux" of the third logical line.
+ "OldCursor": {
+ "X": 7,
+ "Y": 15
+ },
+
+ "Offset": {
+ "LineIndex": 2,
+ "CharIndex": 187
+ },
+
+ "NewInitial": {
+ "X": 7,
+ "Y": 9
+ },
+ "NewCursor": {
+ "X": 63,
+ "Y": 13
+ }
+ },
+ {
+ "OldInitial": {
+ "X": 7,
+ "Y": 9
+ },
+ // Pointing to the start of the third logical line.
+ "OldCursor": {
+ "X": 3,
+ "Y": 13
+ },
+
+ "Offset": {
+ "LineIndex": 2,
+ "CharIndex": 3
+ },
+
+ "NewInitial": {
+ "X": 7,
+ "Y": 10
+ },
+ "NewCursor": {
+ "X": 3,
+ "Y": 13
+ }
+ },
+ {
+ "OldInitial": {
+ "X": 7,
+ "Y": 14
+ },
+ // Pointing to the end of the first physical line -- 'r' from "7.2.0-preview.1" of the first logical line.
+ "OldCursor": {
+ "X": 89,
+ "Y": 14
+ },
+
+ "Offset": {
+ "LineIndex": 0,
+ "CharIndex": 82
+ },
+
+ "NewInitial": {
+ "X": 7,
+ "Y": 14
+ },
+ "NewCursor": {
+ "X": 89,
+ "Y": 14
+ }
+ },
+ {
+ "OldInitial": {
+ "X": 7,
+ "Y": 14
+ },
+ // Pointing to the start of the second physical line -- first 'e' from "7.2.0-preview.1" of the first logical line.
+ "OldCursor": {
+ "X": 0,
+ "Y": 15
+ },
+
+ "Offset": {
+ "LineIndex": 0,
+ "CharIndex": 83
+ },
+
+ "NewInitial": {
+ "X": 7,
+ "Y": 14
+ },
+ "NewCursor": {
+ "X": 90,
+ "Y": 14
+ }
+ },
+ {
+ "OldInitial": {
+ "X": 7,
+ "Y": 14
+ },
+ // Pointing at the start of the second logical line.
+ "OldCursor": {
+ "X": 3,
+ "Y": 17
+ },
+
+ "Offset": {
+ "LineIndex": 1,
+ "CharIndex": 2147483647
+ },
+
+ "NewInitial": {
+ "X": 7,
+ "Y": 14
+ },
+ "NewCursor": {
+ "X": 3,
+ "Y": 16
+ }
+ },
+ {
+ "OldInitial": {
+ "X": 7,
+ "Y": 14
+ },
+ // Pointing to the initial coordinates.
+ "OldCursor": {
+ "X": 7,
+ "Y": 14
+ },
+
+ "Offset": {
+ "LineIndex": 0,
+ "CharIndex": -1
+ },
+
+ "NewInitial": {
+ "X": 7,
+ "Y": 14
+ },
+ "NewCursor": {
+ "X": 7,
+ "Y": 14
+ }
+ },
+ {
+ "OldInitial": {
+ "X": 7,
+ "Y": 1
+ },
+ // Pointing to a position that is beyond the end of third logical line.
+ "OldCursor": {
+ "X": 7,
+ "Y": 8
+ },
+
+ "Offset": {
+ "LineIndex": -1,
+ "CharIndex": -1
+ },
+
+ "NewInitial": {
+ "X": -1,
+ "Y": -1
+ },
+ "NewCursor": {
+ "X": -1,
+ "Y": -1
+ }
+ },
+ {
+ "OldInitial": {
+ "X": 7,
+ "Y": 1
+ },
+ // Pointing to a position that is beyond the end of first logical line at its last physical line.
+ "OldCursor": {
+ "X": 70,
+ "Y": 3
+ },
+
+ "Offset": {
+ "LineIndex": -1,
+ "CharIndex": -1
+ },
+
+ "NewInitial": {
+ "X": -1,
+ "Y": -1
+ },
+ "NewCursor": {
+ "X": -1,
+ "Y": -1
+ }
+ },
+ {
+ "OldInitial": {
+ "X": 7,
+ "Y": 1
+ },
+ // Pointing to a position that is beyond the end of the second logical line, at its last physical line.
+ "OldCursor": {
+ "X": 4,
+ "Y": 4
+ },
+
+ "Offset": {
+ "LineIndex": -1,
+ "CharIndex": -1
+ },
+
+ "NewInitial": {
+ "X": -1,
+ "Y": -1
+ },
+ "NewCursor": {
+ "X": -1,
+ "Y": -1
+ }
+ }
+ ]
+ },
+
+ {
+ "Name": "BasicTest_2",
+ "Lines": [
+ "\u001b[0m\u001b[93mnew-ilNugetPackage\u001b[0m\u001b[39;49m \u001b[0m\u001b[90m-PackagePath\u001b[0m\u001b[39;49m \u001b[0m\u001b[37mC:\\arena\\tmp\\NugetPackages\u001b[0m\u001b[39;49m \u001b[0m\u001b[90m-PackageVersion\u001b[0m\u001b[39;49m \u001b[0m\u001b[37m7.2.0-preview.1\u001b[0m\u001b[39;49m \u001b[0m\u001b[90m-WinFxdBinPath\u001b[0m\u001b[39;49m \u001b[0m\u001b[37mC:\\Users\\rocky\\Downloads\\Win\\\u001b[0m\u001b[39;49m \u001b[0m\u001b[90m-LinuxFxdBinPath\u001b[0m\u001b[39;49m \u001b[0m\u001b[37mC:\\Users\\rocky\\Downloads\\Linux\\\u001b[0m\u001b[39;49m \u001b[0m\u001b[90m-GenAPIToolPath\u001b[0m\u001b[39;49m \u001b[0m\u001b[37mC:\\Users\\rocky\\Tools\\genapi",
+ "\u001b[0m\u001b[37m>> ",
+ "\u001b[0m\u001b[37m>> \u001b[0m\u001b[93mnew-ilNugetPackage\u001b[0m\u001b[39;49m \u001b[0m\u001b[90m-PackagePath\u001b[0m\u001b[39;49m \u001b[0m\u001b[37mC:\\arena\\tmp\\NugetPackages\u001b[0m\u001b[39;49m \u001b[0m\u001b[90m-PackageVersion\u001b[0m\u001b[39;49m \u001b[0m\u001b[37m7.2.0-preview.1\u001b[0m\u001b[39;49m \u001b[0m\u001b[90m-WinFxdBinPath\u001b[0m\u001b[39;49m \u001b[0m\u001b[37mC:\\Users\\rocky\\Downloads\\Win\\\u001b[0m\u001b[39;49m \u001b[0m\u001b[90m-LinuxFxdBinPath\u001b[0m\u001b[39;49m \u001b[0m\u001b[37mC:\\Users\\rocky\\Downloads\\Linux\\\u001b[0m\u001b[39;49m \u001b[0m\u001b[90m-GenAPIToolPath\u001b[0m\u001b[39;49m \u001b[0m\u001b[37mC:\\Users\\rocky\\Tools\\genapi"
+ ],
+ "OldBufferWidth": 124,
+ "NewBufferWidth": 90,
+ "Context": [
+ {
+ "OldInitial": {
+ "X": 7,
+ "Y": 11
+ },
+ // Pointing to the end of the third logical line.
+ "OldCursor": {
+ "X": 107,
+ "Y": 15
+ },
+
+ "Offset": {
+ "LineIndex": 2,
+ "CharIndex": 2147483647
+ },
+
+ "NewInitial": {
+ "X": 7,
+ "Y": 9
+ },
+ "NewCursor": {
+ "X": 51,
+ "Y": 15
+ }
+ },
+ {
+ "OldInitial": {
+ "X": 7,
+ "Y": 11
+ },
+ // Pointing to '\' at "Downloads\Linux" of the third logical line.
+ "OldCursor": {
+ "X": 56,
+ "Y": 15
+ },
+
+ "Offset": {
+ "LineIndex": 2,
+ "CharIndex": 180
+ },
+
+ "NewInitial": {
+ "X": 7,
+ "Y": 8
+ },
+ "NewCursor": {
+ "X": 0,
+ "Y": 14
+ }
+ },
+ {
+ "OldInitial": {
+ "X": 7,
+ "Y": 11
+ },
+ // Pointing to the space right after "Downloads\Linux" of the third logical line.
+ "OldCursor": {
+ "X": 63,
+ "Y": 15
+ },
+
+ "Offset": {
+ "LineIndex": 2,
+ "CharIndex": 187
+ },
+
+ "NewInitial": {
+ "X": 7,
+ "Y": 8
+ },
+ "NewCursor": {
+ "X": 7,
+ "Y": 14
+ }
+ },
+ {
+ "OldInitial": {
+ "X": 7,
+ "Y": 11
+ },
+ // Pointing to the start of the third logical line.
+ "OldCursor": {
+ "X": 3,
+ "Y": 14
+ },
+
+ "Offset": {
+ "LineIndex": 2,
+ "CharIndex": 3
+ },
+
+ "NewInitial": {
+ "X": 7,
+ "Y": 9
+ },
+ "NewCursor": {
+ "X": 3,
+ "Y": 13
+ }
+ },
+ {
+ "OldInitial": {
+ "X": 7,
+ "Y": 11
+ },
+ // Pointing to the 'D' at "Downloads\Win" of the third logical line.
+ "OldCursor": {
+ "X": 0,
+ "Y": 15
+ },
+
+ "Offset": {
+ "LineIndex": 2,
+ "CharIndex": 124
+ },
+
+ "NewInitial": {
+ "X": 7,
+ "Y": 9
+ },
+ "NewCursor": {
+ "X": 34,
+ "Y": 14
+ }
+ },
+ {
+ "OldInitial": {
+ "X": 7,
+ "Y": 11
+ },
+ // Pointing to the end of the first physical line -- 'o' at "-WinFxdBinPath C:\Users\ro" of the first logical line.
+ "OldCursor": {
+ "X": 123,
+ "Y": 11
+ },
+
+ "Offset": {
+ "LineIndex": 0,
+ "CharIndex": 116
+ },
+
+ "NewInitial": {
+ "X": 7,
+ "Y": 9
+ },
+ "NewCursor": {
+ "X": 33,
+ "Y": 10
+ }
+ },
+ {
+ "OldInitial": {
+ "X": 7,
+ "Y": 11
+ },
+ // Pointing to the start of the second logical line.
+ "OldCursor": {
+ "X": 3,
+ "Y": 13
+ },
+
+ "Offset": {
+ "LineIndex": 1,
+ "CharIndex": 2147483647
+ },
+
+ "NewInitial": {
+ "X": 7,
+ "Y": 9
+ },
+ "NewCursor": {
+ "X": 3,
+ "Y": 12
+ }
+ }
+ ]
+ },
+
+ {
+ "Name": "BasicTest_3",
+ // Use empty string as the continuation prompt.
+ "Lines": [
+ "\u001b[0m\u001b[93mnew-ilNugetPackage\u001b[0m\u001b[39;49m \u001b[0m\u001b[90m-PackagePath\u001b[0m\u001b[39;49m \u001b[0m\u001b[37mC:\\arena\\tmp\\NugetPackages\u001b[0m\u001b[39;49m \u001b[0m\u001b[90m-PackageVersion\u001b[0m\u001b[39;49m \u001b[0m\u001b[37m7.2.0-preview.1\u001b[0m\u001b[39;49m \u001b[0m\u001b[90m-WinFxdBinPath\u001b[0m\u001b[39;49m \u001b[0m\u001b[37mC:\\Users\\rocky\\Downloads\\Win\\\u001b[0m\u001b[39;49m \u001b[0m\u001b[90m-LinuxFxdBinPath\u001b[0m\u001b[39;49m \u001b[0m\u001b[37mC:\\Users\\rocky\\Downloads\\Linux\\\u001b[0m\u001b[39;49m \u001b[0m\u001b[90m-GenAPIToolPath\u001b[0m\u001b[39;49m \u001b[0m\u001b[37mC:\\Users\\rocky\\Tools\\genapi",
+ "",
+ "\u001b[0m\u001b[93mnew-ilNugetPackage\u001b[0m\u001b[39;49m \u001b[0m\u001b[90m-PackagePath\u001b[0m\u001b[39;49m \u001b[0m\u001b[37mC:\\arena\\tmp\\NugetPackages\u001b[0m\u001b[39;49m \u001b[0m\u001b[90m-PackageVersion\u001b[0m\u001b[39;49m \u001b[0m\u001b[37m7.2.0-preview.1\u001b[0m\u001b[39;49m \u001b[0m\u001b[90m-WinFxdBinPath\u001b[0m\u001b[39;49m \u001b[0m\u001b[37mC:\\Users\\rocky\\Downloads\\Win\\\u001b[0m\u001b[39;49m \u001b[0m\u001b[90m-LinuxFxdBinPath\u001b[0m\u001b[39;49m \u001b[0m\u001b[37mC:\\Users\\rocky\\Downloads\\Linux\\\u001b[0m\u001b[39;49m \u001b[0m\u001b[90m-GenAPIToolPath\u001b[0m\u001b[39;49m \u001b[0m\u001b[37mC:\\Users\\rocky\\Tools\\genapi",
+ "\u001b[0m\u001b[93mhello"
+ ],
+ "OldBufferWidth": 90,
+ "NewBufferWidth": 124,
+ "Context": [
+ {
+ "OldInitial": {
+ "X": 6,
+ "Y": 9
+ },
+ // Pointing to the start of the third logical line.
+ "OldCursor": {
+ "X": 0,
+ "Y": 13
+ },
+
+ "Offset": {
+ "LineIndex": 2,
+ "CharIndex": 0
+ },
+
+ "NewInitial": {
+ "X": 6,
+ "Y": 9
+ },
+ "NewCursor": {
+ "X": 0,
+ "Y": 12
+ }
+ },
+ {
+ "OldInitial": {
+ "X": 6,
+ "Y": 9
+ },
+ // Pointing to the start of the second logical line.
+ "OldCursor": {
+ "X": 0,
+ "Y": 12
+ },
+
+ "Offset": {
+ "LineIndex": 1,
+ "CharIndex": 2147483647
+ },
+
+ "NewInitial": {
+ "X": 6,
+ "Y": 9
+ },
+ "NewCursor": {
+ "X": 0,
+ "Y": 11
+ }
+ },
+ {
+ "OldInitial": {
+ "X": 6,
+ "Y": 9
+ },
+ // Pointing to the start of the fourth logical line.
+ "OldCursor": {
+ "X": 0,
+ "Y": 16
+ },
+
+ "Offset": {
+ "LineIndex": 3,
+ "CharIndex": 0
+ },
+
+ "NewInitial": {
+ "X": 6,
+ "Y": 9
+ },
+ "NewCursor": {
+ "X": 0,
+ "Y": 14
+ }
+ },
+ {
+ "OldInitial": {
+ "X": 6,
+ "Y": 9
+ },
+ // Pointing to 'e' of the fourth logical line.
+ "OldCursor": {
+ "X": 1,
+ "Y": 16
+ },
+
+ "Offset": {
+ "LineIndex": 3,
+ "CharIndex": 1
+ },
+
+ "NewInitial": {
+ "X": 6,
+ "Y": 9
+ },
+ "NewCursor": {
+ "X": 1,
+ "Y": 14
+ }
+ }
+ ]
+ },
+
+ {
+ "Name": "BasicTest_4",
+ // Use empty string as the continuation prompt.
+ "Lines": [
+ "\u001b[0m\u001b[93mhello",
+ "\u001b[0m\u001b[93mworld",
+ "\u001b[0m\u001b[93mnew-ilNugetPackage\u001b[0m\u001b[39;49m \u001b[0m\u001b[90m-PackagePath\u001b[0m\u001b[39;49m \u001b[0m\u001b[37mC:\\arena\\tmp\\NugetPackages\u001b[0m\u001b[39;49m \u001b[0m\u001b[90m-PackageVersion\u001b[0m\u001b[39;49m \u001b[0m\u001b[37m7.2.0-preview.1\u001b[0m\u001b[39;49m \u001b[0m\u001b[90m-WinFxdBinPath\u001b[0m\u001b[39;49m \u001b[0m\u001b[37mC:\\Users\\rocky\\Downloads\\Win\\\u001b[0m\u001b[39;49m \u001b[0m\u001b[90m-LinuxFxdBinPath\u001b[0m\u001b[39;49m \u001b[0m\u001b[37mC:\\Users\\rocky\\Downloads\\Linux\\\u001b[0m\u001b[39;49m \u001b[0m\u001b[90m-GenAPIToolPath\u001b[0m\u001b[39;49m \u001b[0m\u001b[37mC:\\Users\\rocky\\Tools\\genapi"
+ ],
+ "OldBufferWidth": 90,
+ "NewBufferWidth": 124,
+ "Context": [
+ {
+ "OldInitial": {
+ "X": 6,
+ "Y": 11
+ },
+ // Pointing to the position right after 'o' in the first logical line.
+ "OldCursor": {
+ "X": 11,
+ "Y": 11
+ },
+
+ "Offset": {
+ "LineIndex": 0,
+ "CharIndex": 2147483647
+ },
+
+ "NewInitial": {
+ "X": 6,
+ "Y": 11
+ },
+ "NewCursor": {
+ "X": 11,
+ "Y": 11
+ }
+ },
+ {
+ "OldInitial": {
+ "X": 6,
+ "Y": 11
+ },
+ // Pointing to the 'w' in the second logical line.
+ "OldCursor": {
+ "X": 0,
+ "Y": 12
+ },
+
+ "Offset": {
+ "LineIndex": 1,
+ "CharIndex": 0
+ },
+
+ "NewInitial": {
+ "X": 6,
+ "Y": 11
+ },
+ "NewCursor": {
+ "X": 0,
+ "Y": 12
+ }
+ },
+ {
+ "OldInitial": {
+ "X": 6,
+ "Y": 11
+ },
+ // Pointing to 'r' in the second logical line.
+ "OldCursor": {
+ "X": 2,
+ "Y": 12
+ },
+
+ "Offset": {
+ "LineIndex": 1,
+ "CharIndex": 2
+ },
+
+ "NewInitial": {
+ "X": 6,
+ "Y": 11
+ },
+ "NewCursor": {
+ "X": 2,
+ "Y": 12
+ }
+ }
+ ]
+ },
+
+ {
+ "Name": "BasicTest_5",
+ // Use empty string as the continuation prompt.
+ "Lines": [
+ "\u001b[0m\u001b[93m12345678901234567890123456789012345678901234567890123456789012345678901234567890123有4567890",
+ "\u001b[0m\u001b[93m12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789有准备0"
+ ],
+ "OldBufferWidth": 90,
+ "NewBufferWidth": 124,
+ "Context": [
+ {
+ "OldInitial": {
+ "X": 6,
+ "Y": 12
+ },
+ // Pointing to '有' in the first logical line.
+ "OldCursor": {
+ "X": 0,
+ "Y": 13
+ },
+
+ "Offset": {
+ "LineIndex": 0,
+ "CharIndex": 83
+ },
+
+ "NewInitial": {
+ "X": 6,
+ "Y": 12
+ },
+ "NewCursor": {
+ "X": 89,
+ "Y": 12
+ }
+ },
+ {
+ "OldInitial": {
+ "X": 6,
+ "Y": 12
+ },
+ // Pointing to '4' after the '有' in the first logical line.
+ "OldCursor": {
+ "X": 2,
+ "Y": 13
+ },
+
+ "Offset": {
+ "LineIndex": 0,
+ "CharIndex": 84
+ },
+
+ "NewInitial": {
+ "X": 6,
+ "Y": 12
+ },
+ "NewCursor": {
+ "X": 91,
+ "Y": 12
+ }
+ },
+ {
+ "OldInitial": {
+ "X": 6,
+ "Y": 12
+ },
+ // Pointing to '有' in the second logical line.
+ "OldCursor": {
+ "X": 0,
+ "Y": 15
+ },
+
+ "Offset": {
+ "LineIndex": 1,
+ "CharIndex": 89
+ },
+
+ "NewInitial": {
+ "X": 6,
+ "Y": 12
+ },
+ "NewCursor": {
+ "X": 89,
+ "Y": 13
+ }
+ },
+ {
+ "OldInitial": {
+ "X": 6,
+ "Y": 12
+ },
+ // Pointing to '准' in the second logical line.
+ "OldCursor": {
+ "X": 2,
+ "Y": 15
+ },
+
+ "Offset": {
+ "LineIndex": 1,
+ "CharIndex": 90
+ },
+
+ "NewInitial": {
+ "X": 6,
+ "Y": 12
+ },
+ "NewCursor": {
+ "X": 91,
+ "Y": 13
+ }
+ },
+ {
+ "OldInitial": {
+ "X": 6,
+ "Y": 12
+ },
+ // Pointing to '备' in the second logical line.
+ "OldCursor": {
+ "X": 4,
+ "Y": 15
+ },
+
+ "Offset": {
+ "LineIndex": 1,
+ "CharIndex": 91
+ },
+
+ "NewInitial": {
+ "X": 6,
+ "Y": 12
+ },
+ "NewCursor": {
+ "X": 93,
+ "Y": 13
+ }
+ },
+ {
+ "OldInitial": {
+ "X": 6,
+ "Y": 12
+ },
+ // Pointing to the '0' right after '备' in the second logical line.
+ "OldCursor": {
+ "X": 6,
+ "Y": 15
+ },
+
+ "Offset": {
+ "LineIndex": 1,
+ "CharIndex": 92
+ },
+
+ "NewInitial": {
+ "X": 6,
+ "Y": 11
+ },
+ "NewCursor": {
+ "X": 95,
+ "Y": 12
+ }
+ }
+ ]
+ },
+
+ {
+ // Testing scenarios where the logical lines happen to fit in the whole buffer width.
+ "Name": "BasicTest_6",
+ "Lines": [
+ "\u001b[0m\u001b[97m123456789012345678901234567890123456789012345678901234567890123456789012345678901234",
+ "\u001b[0m\u001b[37m>> \u001b[0m\u001b[97m123456789012345678901234567890123456789012345678901234567890123456789012345678901234567",
+ "\u001b[0m\u001b[37m>> \u001b[0m\u001b[97m123456"
+ ],
+ "OldBufferWidth": 90,
+ "NewBufferWidth": 124,
+ "Context": [
+ {
+ "OldInitial": {
+ "X": 6,
+ "Y": 3
+ },
+ // Pointing to the end of the the third logical line.
+ "OldCursor": {
+ "X": 9,
+ "Y": 5
+ },
+
+ "Offset": {
+ "LineIndex": 2,
+ "CharIndex": 2147483647
+ },
+
+ "NewInitial": {
+ "X": 6,
+ "Y": 3
+ },
+ "NewCursor": {
+ "X": 9,
+ "Y": 5
+ }
+ },
+ {
+ "OldInitial": {
+ "X": 6,
+ "Y": 3
+ },
+ // Pointing to '3' of the third logical line.
+ "OldCursor": {
+ "X": 5,
+ "Y": 5
+ },
+
+ "Offset": {
+ "LineIndex": 2,
+ "CharIndex": 5
+ },
+
+ "NewInitial": {
+ "X": 6,
+ "Y": 3
+ },
+ "NewCursor": {
+ "X": 5,
+ "Y": 5
+ }
+ },
+ {
+ "OldInitial": {
+ "X": 6,
+ "Y": 3
+ },
+ // Pointing to '1' of the third logical line.
+ "OldCursor": {
+ "X": 3,
+ "Y": 5
+ },
+
+ "Offset": {
+ "LineIndex": 2,
+ "CharIndex": 3
+ },
+
+ "NewInitial": {
+ "X": 6,
+ "Y": 3
+ },
+ "NewCursor": {
+ "X": 3,
+ "Y": 5
+ }
+ },
+ {
+ "OldInitial": {
+ "X": 6,
+ "Y": 3
+ },
+ // Pointing to the end of the second logical line.
+ "OldCursor": {
+ "X": 0,
+ "Y": 5
+ },
+
+ "Offset": {
+ "LineIndex": 1,
+ "CharIndex": 2147483647
+ },
+
+ "NewInitial": {
+ "X": 6,
+ "Y": 3
+ },
+ "NewCursor": {
+ "X": 90,
+ "Y": 4
+ }
+ },
+ {
+ "OldInitial": {
+ "X": 6,
+ "Y": 3
+ },
+ // Pointing to the end of the second logical line.
+ "OldCursor": {
+ "X": 89,
+ "Y": 4
+ },
+
+ "Offset": {
+ "LineIndex": 1,
+ "CharIndex": 89
+ },
+
+ "NewInitial": {
+ "X": 6,
+ "Y": 3
+ },
+ "NewCursor": {
+ "X": 89,
+ "Y": 4
+ }
+ },
+ {
+ "OldInitial": {
+ "X": 6,
+ "Y": 3
+ },
+ // Pointing to the end of the first logical line.
+ "OldCursor": {
+ "X": 0,
+ "Y": 4
+ },
+
+ "Offset": {
+ "LineIndex": 0,
+ "CharIndex": 2147483647
+ },
+
+ "NewInitial": {
+ "X": 6,
+ "Y": 3
+ },
+ "NewCursor": {
+ "X": 90,
+ "Y": 3
+ }
+ }
+ ]
+ }
+]
diff --git a/tools/helper.psm1 b/tools/helper.psm1
index c5e4e0d9e..29ba9f06f 100644
--- a/tools/helper.psm1
+++ b/tools/helper.psm1
@@ -1,5 +1,5 @@
-$MinimalSDKVersion = '6.0.100-preview.1.21103.13'
+$MinimalSDKVersion = '6.0.100'
$IsWindowsEnv = [System.Environment]::OSVersion.Platform -eq "Win32NT"
$RepoRoot = (Resolve-Path "$PSScriptRoot/..").Path
$LocalDotnetDirPath = if ($IsWindowsEnv) { "$env:LocalAppData\Microsoft\dotnet" } else { "$env:HOME/.dotnet" }
@@ -39,7 +39,7 @@ function Find-Dotnet
$env:PATH = $LocalDotnetDirPath + [IO.Path]::PathSeparator + $env:PATH
}
else {
- throw "Cannot find the dotnet SDK for .NET 5. Please specify '-Bootstrap' to install build dependencies."
+ throw "Cannot find the dotnet SDK with the version $MinimalSDKVersion or higher. Please specify '-Bootstrap' to install build dependencies."
}
}
}