diff --git a/doc/source/commands/terminalgui.rst b/doc/source/commands/terminalgui.rst index 353c13996d..8210760325 100644 --- a/doc/source/commands/terminalgui.rst +++ b/doc/source/commands/terminalgui.rst @@ -18,6 +18,8 @@ Terminal and game environment PRINT 4+1. PRINT "4 times 8 is: " + (4*8). + This is an alias for TERMINAL:PUTLN, see :ref:`terminal struct `. + .. global:: SET TERMINAL:WIDTH. GET TERMINAL:WIDTH Gets or sets the terminal's width in characters. @@ -33,7 +35,10 @@ Terminal and game environment :parameter col: (integer) column starting with zero (left) :parameter line: (integer) line starting with zero (top) - Used in combination with :global:`PRINT`. Prints the selected text to the screen at specified location. Can print strings, or the result of an expression:: + Used in combination with :global:`PRINT`. Prints the selected text to the screen at specified location. + This does **not** move the terminal cursor, as opposed to PRINT and most :ref:`terminal ` output methods. + This is an alias for TERMINAL:PUTAT, see :ref:`terminal struct `. + Can print strings, or the result of an expression:: PRINT "Hello" AT(0,10). PRINT 4+1 AT(0,10). diff --git a/doc/source/structures/misc/terminal.rst b/doc/source/structures/misc/terminal.rst index 55b72a3bc9..ab873d3247 100644 --- a/doc/source/structures/misc/terminal.rst +++ b/doc/source/structures/misc/terminal.rst @@ -55,6 +55,36 @@ Structure - get and set - Height of a character cell in pixels. + * - :attr:`CURSORCOL` + - :struct:`Scalar` + - get and set + - Current cursor column, between 0 and WIDTH-1. + + * - :attr:`CURSORROW` + - :struct:`Scalar` + - get and set + - Current cursor row, between 0 and HEIGHT-1. + + * - :meth:`MOVECURSOR(column,row)` + - None + - Method Call + - Move cursor to position. + + * - :meth:`PUT(string)` + - None + - Method Call + - Output string without newline. + + * - :meth:`PUTLN(string)` + - None + - Method Call + - Output string with newline. + + * - :meth:`PUTAT(text,column,row)` + - None + - Method Call + - Output string at position + * - :attr:`INPUT` - :struct:`TerminalInput` - get @@ -166,6 +196,49 @@ Structure divisible by 2. If you try to set it to any other value, it will snap to the allowed range and increment. +.. attribute:: Terminal:CURSORCOL + + :access: Get/Set + :type: :struct:`Scalar` + + Current cursor column, between 0 and WIDTH-1. + +.. attribute:: Terminal:CURSORROW + + :access: Get/Set + :type: :struct:`Scalar` + + Current cursor row, between 0 and HEIGHT-1. + +.. method:: Terminal:MOVECURSOR(column,row) + + :parameter column: (scalar) column to move to. + :parameter row: (scalar) row to move to. + + Move both coordinates of the cursor at once. + +.. method:: Terminal:PUT(text) + + :parameter text: (string) Text to print + + Put string at current cursor position (without implied newline). + +.. method:: Terminal:PUTLN(text) + + :parameter text: (string) Text to print + + Put string at current cursor position (with implied newline). + This is an alias for :global:`PRINT` + +.. method:: Terminal:PUTAT(text,column,row) + + :parameter text: (string) Text to print + :parameter column: (scalar) Horizontal starting position + :parameter row: (scalar) Vertical starting position + + Put string at position without moving the cursor. + This is an alias for PRINT AT. + .. attribute:: Terminal:INPUT :access: Get diff --git a/kerboscript_tests/terminalcursor/echo.ks b/kerboscript_tests/terminalcursor/echo.ks new file mode 100644 index 0000000000..698bffdee1 --- /dev/null +++ b/kerboscript_tests/terminalcursor/echo.ks @@ -0,0 +1,19 @@ +@lazyglobal off. + +terminal:putln("enter text to echo:"). +local line is "". +until false { + local c is terminal:input:getchar(). + if(c = terminal:input:enter) { + terminal:putln(""). //linefeed + if(line:length > 0) { + terminal:putln(line). + set line to "". + } else { + break. + } + } else { + terminal:put(c). + set line to line + c. + } +} \ No newline at end of file diff --git a/kerboscript_tests/terminalcursor/loadingbar.ks b/kerboscript_tests/terminalcursor/loadingbar.ks new file mode 100644 index 0000000000..360f103320 --- /dev/null +++ b/kerboscript_tests/terminalcursor/loadingbar.ks @@ -0,0 +1,17 @@ +@lazyglobal off. + +//do a 25 s loading bar in the current line + +local row is terminal:cursorrow. +terminal:putln(""). //linefeed the cursor + +from {local progress is 0.} until progress = 100 step { set progress to progress+1. } do { + local bar is "". + local prog_per_bar is (terminal:width - 4) / 100.0. + from { local pos is 0.} until pos/prog_per_bar >= progress step { set pos to pos+1. } do { + terminal:putat("-", pos, row). + } + terminal:putat(progress:tostring():padleft(3), terminal:width-4, row). + terminal:putat("%", terminal:width-1, row). + wait 0.25. +} \ No newline at end of file diff --git a/kerboscript_tests/terminalcursor/scrambledoutput.ks b/kerboscript_tests/terminalcursor/scrambledoutput.ks new file mode 100644 index 0000000000..4747e9e4ce --- /dev/null +++ b/kerboscript_tests/terminalcursor/scrambledoutput.ks @@ -0,0 +1,22 @@ +@lazyglobal off. + +local teststring is "This is a teststring". +local words is teststring:split(" "). + +print "The following lines should all look identical". +print teststring. //comparison, do not go back to this line +print words[0]. +set terminal:cursorcol to words[0]:length + 1. +set terminal:cursorrow to terminal:cursorrow - 1. +terminal:putln(words[1]). +set terminal:cursorrow to terminal:cursorrow - 1. +set terminal:cursorcol to words[0]:length + words[1]:length + 2. +terminal:putln(words[2] + " " + words[3]). +print words[0] + " " + words[1]. +terminal:putat(words[0], 0, terminal:cursorrow - 1). +terminal:movecursor(words[0]:length + words[1]:length +2, terminal:cursorrow - 2). +terminal:put(words[2]). +set terminal:cursorrow to terminal:cursorrow + 1. +set terminal:cursorcol to terminal:cursorcol - words[2]:length. +terminal:put(words[2]). +print " " + words[3]. diff --git a/src/kOS.Safe.Test/Execution/Screen.cs b/src/kOS.Safe.Test/Execution/Screen.cs index e1b848dbde..d56bd94dcc 100644 --- a/src/kOS.Safe.Test/Execution/Screen.cs +++ b/src/kOS.Safe.Test/Execution/Screen.cs @@ -149,6 +149,14 @@ public int CursorRowShow } } + public bool CursorVisible + { + get + { + return false; + } + } + public bool ReverseScreen { get diff --git a/src/kOS.Safe.Test/kOS.Safe.Test.csproj b/src/kOS.Safe.Test/kOS.Safe.Test.csproj index 8d68b2a28e..3456956e7f 100644 --- a/src/kOS.Safe.Test/kOS.Safe.Test.csproj +++ b/src/kOS.Safe.Test/kOS.Safe.Test.csproj @@ -8,7 +8,7 @@ Properties kOS.Safe.Test kOS.Safe.Test - v4.5 + v4.5.1 512 diff --git a/src/kOS.Safe/Encapsulation/TerminalStruct.cs b/src/kOS.Safe/Encapsulation/TerminalStruct.cs index eaf775ea2f..fbdb3f761b 100644 --- a/src/kOS.Safe/Encapsulation/TerminalStruct.cs +++ b/src/kOS.Safe/Encapsulation/TerminalStruct.cs @@ -2,6 +2,7 @@ using kOS.Safe.Encapsulation.Suffixes; using kOS.Safe.Screen; using kOS.Safe.Execution; +using kOS.Safe.Utilities; namespace kOS.Safe.Encapsulation { @@ -161,6 +162,20 @@ private void InitializeSuffixes() "Character height on in-game terminal screen in pixels")); AddSuffix("RESIZEWATCHERS", new NoArgsSuffix>(() => resizeWatchers)); AddSuffix("INPUT", new Suffix(GetTerminalInputInstance)); + AddSuffix("CURSORCOL", new SetSuffix(() => Shared.Screen.CursorColumnShow, + value => Shared.Screen.MoveCursor(Shared.Screen.AbsoluteCursorRow, (int)KOSMath.Clamp(value,0,Shared.Screen.ColumnCount)), + "Current cursor column, between 0 and WIDTH-1.")); + AddSuffix("CURSORROW", new SetSuffix(() => Shared.Screen.AbsoluteCursorRow, + value => Shared.Screen.MoveCursor(value, Shared.Screen.CursorColumnShow), + "Current cursor row, between 0 and HEIGHT-1.")); + AddSuffix("MOVECURSOR", new TwoArgsSuffix((ScalarValue col, ScalarValue row) => Shared.Screen.MoveCursor(row, col), + "Move cursor to (column, row).")); + AddSuffix("PUT", new OneArgsSuffix(value => Shared.Screen.Print(value.ToString(),false), + "Put string at current cursor position (without implied newline).")); + AddSuffix("PUTLN", new OneArgsSuffix(value => Shared.Screen.Print(value.ToString()), + "Put string at current cursor position (with implied newline).")); + AddSuffix("PUTAT", new ThreeArgsSuffix((Structure value, ScalarValue col, ScalarValue row) => Shared.Screen.PrintAt(value.ToString(), row, col), + "Put string at position without moving the cursor.")); } private void CannotSetWidth(ScalarValue newWidth) diff --git a/src/kOS.Safe/Execution/CPU.cs b/src/kOS.Safe/Execution/CPU.cs index d6d35cd1dd..67d56a6a77 100644 --- a/src/kOS.Safe/Execution/CPU.cs +++ b/src/kOS.Safe/Execution/CPU.cs @@ -1,4 +1,4 @@ -using kOS.Safe.Binding; +using kOS.Safe.Binding; using kOS.Safe.Callback; using kOS.Safe.Compilation; using kOS.Safe.Encapsulation; diff --git a/src/kOS.Safe/Screen/IScreenBuffer.cs b/src/kOS.Safe/Screen/IScreenBuffer.cs index 5910939f0f..fb9e1633fe 100644 --- a/src/kOS.Safe/Screen/IScreenBuffer.cs +++ b/src/kOS.Safe/Screen/IScreenBuffer.cs @@ -20,7 +20,6 @@ public interface IScreenBuffer void SetSize(int rowCount, int columnCount); int ScrollVertical(int deltaRows); void MoveCursor(int row, int column); - void MoveToNextLine(); void PrintAt(string textToPrint, int row, int column); void Print(string textToPrint); void Print(string textToPrint, bool addNewLine); diff --git a/src/kOS.Safe/Screen/IScreenBufferLine.cs b/src/kOS.Safe/Screen/IScreenBufferLine.cs index e3197eaa28..4ea418cbb8 100644 --- a/src/kOS.Safe/Screen/IScreenBufferLine.cs +++ b/src/kOS.Safe/Screen/IScreenBufferLine.cs @@ -9,5 +9,19 @@ public interface IScreenBufferLine void ArrayCopyFrom(IScreenBufferLine source, int sourceStart, int destinationStart, int length = -1); void ArrayCopyFrom(char[] source, int sourceStart, int destinationStart, int length = -1); void TouchTime(); + + /// + /// Set a single character without changing the timestamp. Should be used carefully in conjunction with SetTimestamp, + /// e.g. by SubBuffer.MergeTo + /// + /// Target Index + /// Value to set + void SetCharIgnoreTime(int i, char c); + + /// + /// Manually set the change timestamp. Should be used carefully in conjunction with SetCharIgnoreTime, e.g. by SubBuffer.MergeTo + /// + /// Timestamp to set + void SetTimestamp(ulong timestamp); } } \ No newline at end of file diff --git a/src/kOS.Safe/Screen/IScreenSnapshot.cs b/src/kOS.Safe/Screen/IScreenSnapshot.cs index 58d0764e36..906d55b5fd 100644 --- a/src/kOS.Safe/Screen/IScreenSnapshot.cs +++ b/src/kOS.Safe/Screen/IScreenSnapshot.cs @@ -9,7 +9,8 @@ public interface IScreenSnapShot int CursorColumn {get;} int CursorRow {get;} int RowCount {get;} - + bool CursorVisible { get; } + string DiffFrom(IScreenSnapShot older); IScreenSnapShot DeepCopy(); diff --git a/src/kOS.Safe/Screen/PrintingBuffer.cs b/src/kOS.Safe/Screen/PrintingBuffer.cs new file mode 100644 index 0000000000..76db30368d --- /dev/null +++ b/src/kOS.Safe/Screen/PrintingBuffer.cs @@ -0,0 +1,220 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace kOS.Safe.Screen +{ + public class PrintingBuffer : SubBuffer + { + public int CursorRow { private set; get; } + public int CursorColumn { private set; get; } + + /// + /// Move the cursor. + /// Clamps the cursor to buffer bounds, generally. + /// If AutoExtend is true, this may extend the Buffer to move the cursor past the previous last row. + /// + /// Target row + /// Target column + /// If the cursor was moved to the given position (and not clamped to bounds) + public bool MoveCursor(int row, int column) + { + bool outOfBounds = false; + + if(column < 0) + { + column = 0; + outOfBounds = true; + } + else if(column >= ColumnCount) + { + column = ColumnCount - 1; + outOfBounds = true; + } + + if (row >= RowCount) + { + if (AutoExtend) + { + SetSize(row + 1, ColumnCount); + } + else + { + row = RowCount - 1; + outOfBounds = true; + } + } + else if(row < 0) + { + row = 0; + outOfBounds = true; + } + + CursorColumn = column; + CursorRow = row; + + return !outOfBounds; + } + + /// + /// Invokes MoveCursor, and if the cursor is clamped, reset it to the previous position + /// + /// Target row + /// Target column + /// If the cursor was moved to the given position (and not reset to previous) + public bool TryMoveCursor(int row, int column) + { + int oldRow = CursorRow; + int oldColumn = CursorColumn; + + if(!MoveCursor(row, column)) + { + CursorRow = oldRow; + CursorColumn = oldColumn; + return false; + } + return true; + } + + + /// + /// Prints the given text, including wrapover as necessary, and advances the cursor. + /// If AutoExtend is true, this may add rows to accomodate the text. + /// + /// Text to print + /// Ensure there is a newline after the text + /// Could we print the full text or did we run out of buffer? + public bool Print(string text, bool trailingNewLine) + { + List lines = SplitIntoLines(text); + bool internalNewLine = false; + foreach (string line in lines) + { + if(internalNewLine) + { + bool inBounds = TryMoveCursor(CursorRow + 1, 0); + if(!inBounds) + { + return false; + } + } + PrintLine(line); + internalNewLine = true; + } + if(trailingNewLine || CursorColumn == ColumnCount) + { + TryMoveCursor(CursorRow + 1, 0); + } + return true; + } + + /// + /// Insert a string at the current cursor, which must not be longer than the remaining line. + /// + private void PrintLine(string textToPrint) + { + IScreenBufferLine lineBuffer = Buffer[CursorRow]; + textToPrint = StripUnprintables(textToPrint); + lineBuffer.ArrayCopyFrom(textToPrint.ToCharArray(), 0, CursorColumn); + CursorColumn += textToPrint.Length; + } + + private string StripUnprintables(string textToPrint) + { + StringBuilder sb = new StringBuilder(); + foreach (char ch in textToPrint) + { + if (0x0020 <= ch) + sb.Append(ch); + } + return sb.ToString(); + } + + /// + /// Split a text into lines starting from the current cursor. + /// + protected List SplitIntoLines(string textToPrint) + { + return ScreenBuffer.SplitIntoLines(textToPrint, ColumnCount, CursorColumn); + } + + public override string DebugDump() + { + StringBuilder sb = new StringBuilder(); + sb.Append("DebugDump PrintingBuffer: CursorRow = " + CursorRow + ", CursorColumn = " + CursorColumn + ", Base: "); + sb.Append(base.DebugDump()); + return sb.ToString(); + } + + /// + /// Replaces Buffer with one of the new current size, copying old contents + /// over. This implementation respects WillTruncate and KeepShortenedLines, as well as moving the cursor along. + /// + /// The amount of scrolling to happen as a result + protected override int ResizeBuffer() + { + List newBuffer = new List(); + + int newRow = 0; + int oldRow = 0; + int newCol = 0; + bool isFull = false; + int scrollDiff = 0; + + // First, copy everything from old to new, and perhaps enlarge the new if the old won't fit. + while (oldRow < Buffer.Count && !isFull) + { + int oldCol = 0; + while (oldCol < Buffer[oldRow].Length && !isFull) + { + if (newCol >= ColumnCount) // wrap to new line when cur line is full. + { + ++newRow; + newCol = 0; + } + if (newRow >= newBuffer.Count) // grow to required size, or quit if we aren't supposed to grow + { + if (WillTruncate) + { + isFull = true; + break; + } + else + newBuffer.Add(new ScreenBufferLine(ColumnCount)); + } + newBuffer[newRow][newCol] = Buffer[oldRow][oldCol]; + if (oldCol == CursorColumn && oldRow == CursorRow) + { //keep cursor position relative to content + CursorColumn = newCol; + scrollDiff = newRow - CursorRow; + CursorRow = newRow; + } + ++newCol; + ++oldCol; + } + ++oldRow; + if (KeepShortenedLines) + { //create empty space instead of wrapping back over + ++newRow; + newCol = 0; + } + } + + // Then maybe append more empty rows if the above wasn't enough to fill the new size: + while (newBuffer.Count < RowCount) + newBuffer.Add(new ScreenBufferLine(ColumnCount)); + + // Because Buffer is readonly, copy the data from newBuffer into it rather than just resetting the reference to newBuffer: + Buffer.Clear(); + foreach (var t in newBuffer) + { + Buffer.Add(t); + } + + RowCount = newBuffer.Count; + + return Fixed ? 0 : scrollDiff; + } + } +} diff --git a/src/kOS.Safe/Screen/ScreenBuffer.cs b/src/kOS.Safe/Screen/ScreenBuffer.cs index 18fe25909c..3db9068c11 100644 --- a/src/kOS.Safe/Screen/ScreenBuffer.cs +++ b/src/kOS.Safe/Screen/ScreenBuffer.cs @@ -11,9 +11,14 @@ public class ScreenBuffer : IScreenBuffer private const int DEFAULT_ROWS = 36; private const int DEFAULT_COLUMNS = 50; private int topRow; - private readonly List buffer; private readonly List subBuffers; + /// The main output buffer + private readonly PrintingBuffer ScrollingOutput; + + /// Used to print independently of the main cursor. Always merged into ScrollingOutput, not visible itself. + private readonly PrintingBuffer PositionalOutput; + public Queue CharInputQueue { get; private set; } public int BeepsPending {get; set;} @@ -34,25 +39,21 @@ public class ScreenBuffer : IScreenBuffer public int ColumnCount { get; private set; } - public virtual int CursorRowShow { get { return CursorRow; } } + public virtual int CursorRowShow { get { return ScrollingOutput.CursorRow - topRow; } } - public virtual int CursorColumnShow { get { return CursorColumn; } } + public virtual int CursorColumnShow { get { return ScrollingOutput.CursorColumn; } } public int RowCount { get; private set; } public int AbsoluteCursorRow { - get { return CursorRow + topRow; } - set { CursorRow = value - topRow; } + get { return ScrollingOutput.CursorRow; } + set { ScrollingOutput.MoveCursor(value, ScrollingOutput.CursorColumn); } } // Needed so the terminal knows when it's been scrolled, for its diffing purposes. public int TopRow { get { return topRow; } } - protected int CursorRow { get; set; } - - protected int CursorColumn { get; set; } - protected List Notifyees { get; set; } /// Delegate prototype expected by AddResizeNotifier @@ -62,10 +63,24 @@ public int AbsoluteCursorRow public ScreenBuffer() { - buffer = new List(); Notifyees = new List(); subBuffers = new List(); + + ScrollingOutput = new PrintingBuffer() + { + AutoExtend = true, + WillTruncate = false, + Enabled = true, + KeepShortenedLines = true + }; + AddSubBuffer(ScrollingOutput); + + PositionalOutput = new PrintingBuffer() + { + AutoExtend = true + }; + AddSubBuffer(PositionalOutput); CharInputQueue = new Queue(); @@ -94,11 +109,11 @@ public void SetSize(int rows, int columns) { RowCount = rows; ColumnCount = columns; - ResizeBuffer(); int scrollDiff = Notifyees .Where(notifier => notifier != null) .Sum(notifier => notifier(this)); ScrollVertical(scrollDiff); + MoveCursor(ScrollingOutput.CursorRow, ScrollingOutput.CursorColumn); //synchronize cursors } public virtual int ScrollVertical(int deltaRows) @@ -114,122 +129,48 @@ public virtual int ScrollVertical(int deltaRows) /// for this many rows, or up to the max row the buffer has if this number is too large public void MarkRowsDirty(int startRow, int numRows) { - // Mark fewer rows than asked to if the reqeusted number would have blown past the end of the buffer: - int numSafeRows = (numRows + startRow <= buffer.Count) ? numRows : buffer.Count - startRow; - - for( int i = 0; i < numSafeRows ; ++i) - buffer[startRow + i].TouchTime(); + ScrollingOutput.MarkRowsDirty(startRow, numRows); } - public void MoveCursor(int row, int column) + /// Move the cursor to the given absolute position, scrolling the screen to make it visible + public virtual void MoveCursor(int row, int column) { - if (row >= RowCount) - { - row = RowCount - 1; - MoveToNextLine(); - } - if (row < 0) row = 0; - - if (column >= ColumnCount) column = ColumnCount - 1; - if (column < 0) column = 0; - - CursorRow = row; - CursorColumn = column; - } - - public void MoveToNextLine() - { - if ((CursorRow + 1) >= RowCount) - { - // scrolling up - AddNewBufferLines(); - ScrollVerticalInternal(); - } - else - { - CursorRow++; - } - - CursorColumn = 0; + ScrollingOutput.MoveCursor(row, column); + ScrollCursorVisible(); } + /// Print text at the given position, not auto-scrolling the view public virtual void PrintAt(string textToPrint, int row, int column) { - MoveCursor(row, column); - Print(textToPrint, false); + PositionalOutput.MoveCursor(0, column); + PositionalOutput.PositionRow = row; + PositionalOutput.Print(StripUnprintables(textToPrint), false); + PositionalOutput.MergeTo(ScrollingOutput, topRow); + PositionalOutput.Wipe(); } + /// + /// Print text with a trailing newline at the cursor. + /// Scrolls the view to keep the cursor visible. + /// public void Print(string textToPrint) { Print(textToPrint, true); } - public void Print(string textToPrint, bool addNewLine) - { - List lines = SplitIntoLines(textToPrint); - foreach (string line in lines) - { - PrintLine(line); - - if (CursorColumn > 0 && addNewLine) - { - MoveToNextLine(); - CursorColumn = 0; - } - } - } - - protected void AddNewBufferLines(int howMany = 1) - { - while (howMany-- > 0) - buffer.Add(new ScreenBufferLine(ColumnCount)); - } - - protected void ResizeBuffer() + /// Print text at the cursor, scrolling to keep the screen visible + public void Print(string textToPrint, bool trailingNewLine) { - // Grow or shrink the width of the buffer lines to match the new - // value. Note that this does not (yet) account for preserving lines and wrapping them. - for (int row = 0; row < buffer.Count; ++row) - { - var newRow = new ScreenBufferLine(ColumnCount); - newRow.ArrayCopyFrom(buffer[row], 0, 0, Math.Min(buffer[row].Length, ColumnCount)); - buffer[row] = newRow; - } - - // Add more buffer lines if needed to pad out the rest of the screen: - while (buffer.Count - topRow < RowCount) - buffer.Add(new ScreenBufferLine(ColumnCount)); + ScrollingOutput.Print(StripUnprintables(textToPrint), trailingNewLine); + ScrollCursorVisible(); } - - protected List SplitIntoLines(string textToPrint) - { - var lineList = new List(); - int availableColumns = ColumnCount - CursorColumn; - - string[] lines = textToPrint.Trim(new[] { '\r', '\n' }).Split('\n'); - - foreach (string line in lines) - { - string lineToPrint = line.TrimEnd('\r'); - int startIndex = 0; - - while ((lineToPrint.Length - startIndex) > availableColumns) - { - lineList.Add(lineToPrint.Substring(startIndex, availableColumns)); - startIndex += availableColumns; - availableColumns = ColumnCount; - } - - lineList.Add(lineToPrint.Substring(startIndex)); - availableColumns = ColumnCount; - } - - return lineList; - } - + public void ClearScreen() { - buffer.Clear(); + MoveCursor(0, 0); + ScrollingOutput.Wipe(); + ScrollingOutput.SetSize(1, ColumnCount); + PositionalOutput.PositionRow = 0; InitializeBuffer(); } @@ -244,62 +185,33 @@ public void RemoveSubBuffer(SubBuffer subBuffer) subBuffers.Remove(subBuffer); } + /// Merge all enabled SubBuffers, considering topRow, and return the resulting view public List GetBuffer() { - // base buffer - int extraPadRows = Math.Max(0, (topRow + RowCount) - buffer.Count); // When screen extends past the buffer bottom., this is needed to prevent GetRange() exception. - var mergedBuffer = new List(buffer.GetRange(topRow, RowCount - extraPadRows)); - int lastLineWidth = mergedBuffer[mergedBuffer.Count - 1].Length; - while (extraPadRows > 0) + SubBuffer View = new SubBuffer() { - mergedBuffer.Add(new ScreenBufferLine(lastLineWidth)); - --extraPadRows; - } + Fixed = true + }; + View.SetSize(RowCount, ColumnCount); // merge sub buffers UpdateSubBuffers(); foreach (SubBuffer subBuffer in subBuffers) { - if (subBuffer.RowCount > 0 && subBuffer.Enabled) + if (subBuffer.Enabled) { - int mergeRow = subBuffer.Fixed ? subBuffer.PositionRow : (subBuffer.PositionRow - topRow); - - if ((mergeRow + subBuffer.RowCount) > 0 && mergeRow < RowCount) - { - int startRow = (mergeRow < 0) ? -mergeRow : 0; - int rowsToMerge = subBuffer.RowCount - startRow; - if ((mergeRow + rowsToMerge) > RowCount) rowsToMerge = (RowCount - mergeRow); - List bufferRange = subBuffer.Buffer.GetRange(startRow, rowsToMerge); - - // Remove the replaced rows, but protect against the case where they didn't exist in - // the first place because sizes just got changed in a window drag during the GUI pass: - int mergeRowClamped = Math.Min(Math.Max(mergeRow, 0), mergedBuffer.Count - 1); - int rowsToMergeClamped = Math.Min(Math.Max(rowsToMerge, 0), (mergedBuffer.Count - mergeRowClamped)); - mergedBuffer.RemoveRange(mergeRowClamped, rowsToMergeClamped); - // Replace them: - mergedBuffer.InsertRange(mergeRow, bufferRange); - } + subBuffer.MergeTo(View, topRow); } } - return mergedBuffer; + return View.GetBuffer(); } // This was handy when trying to figure out what was going on. public string DebugDump() { StringBuilder sb = new StringBuilder(); - sb.Append("DebugDump ScreenBuffer: RowCount=" + RowCount + ", ColumnCount=" + ColumnCount + ", topRow=" + topRow + ", buffer.count=" + buffer.Count + "\n"); - for (int i = 0; i < buffer.Count; ++i) - { - sb.Append(" line " + i + " = ["); - for (int j = 0; j < buffer[i].Length; ++j) - { - char ch = buffer[i][j]; - sb.Append((int)ch < 32 ? (" \\" + (int)ch) : (" " + ch)); - } - sb.Append("]\n"); - } + sb.Append("DebugDump ScreenBuffer: RowCount=" + RowCount + ", ColumnCount=" + ColumnCount + ", topRow=" + topRow + "\n"); foreach (SubBuffer sub in subBuffers) sb.Append(sub.DebugDump()); return sb.ToString(); @@ -312,50 +224,60 @@ protected virtual void UpdateSubBuffers() private void InitializeBuffer() { - buffer.Clear(); - AddNewBufferLines(RowCount); - topRow = 0; - CursorRow = 0; - CursorColumn = 0; + + ScrollingOutput.SetSize(1, ColumnCount); + PositionalOutput.SetSize(1, ColumnCount); } + /// Scroll so the cursor is visible, if neccessary + protected void ScrollCursorVisible() + { + if (CursorRowShow < 0) + { + ScrollVerticalInternal(CursorRowShow); + } + else if (CursorRowShow >= RowCount) + { + ScrollVerticalInternal(CursorRowShow - RowCount +1); + } + } + + /// + /// Scroll the view. This does not scroll past 0 or the end of the last non-fixed buffer. + /// + /// Rows we want to scroll, positive for down + /// Rows actually scrolled, positive for down private int ScrollVerticalInternal(int deltaRows = 1) { - int maxTopRow = buffer.Count - RowCount; // refuse to allow a scroll past the end of the visible buffer. + int maxTopRow = 0; + foreach(SubBuffer buf in subBuffers) + { + if(!buf.Fixed && buf.Enabled) + { + int bufMaxTopRow = buf.PositionRow + buf.RowCount - RowCount; + if(bufMaxTopRow > maxTopRow) + { + maxTopRow = bufMaxTopRow; + } + } + } // boundary checks if (topRow + deltaRows < 0) deltaRows = -topRow; else if (topRow + deltaRows > maxTopRow) - deltaRows = (maxTopRow - topRow); - + deltaRows = maxTopRow - topRow; + topRow += deltaRows; return deltaRows; } - - private void MoveColumn(int deltaPosition) - { - if (deltaPosition > 0) - { - CursorColumn += deltaPosition; - while (CursorColumn >= ColumnCount) - { - CursorColumn -= ColumnCount; - MoveToNextLine(); - } - } - } - - private void PrintLine(string textToPrint) - { - IScreenBufferLine lineBuffer = buffer[AbsoluteCursorRow]; - textToPrint = StripUnprintables(textToPrint); - lineBuffer.ArrayCopyFrom(textToPrint.ToCharArray(), 0, CursorColumn); - MoveColumn(textToPrint.Length); - } + /// + /// Strip beeps from the text and add them to pending. + /// Everything else is handled by SubBuffer, which needs at least the newlines. + /// private string StripUnprintables(string textToPrint) { StringBuilder sb = new StringBuilder(); @@ -368,12 +290,60 @@ private string StripUnprintables(string textToPrint) ++BeepsPending; break; default: - if (0x0020 <= ch) - sb.Append(ch); + sb.Append(ch); break; } } return sb.ToString(); } + + /// + /// Split a text into lines, considering both newlines in the text, and wrapover. + /// + /// Text to split + /// Number of columns in a full line + /// Starting column, i.e. how many columns are already used in the first line + public static List SplitIntoLines(string textToPrint, int columnCount, int cursorColumn) + { + var lineList = new List(); + int availableColumns = columnCount - cursorColumn; + + string[] lines = textToPrint.Trim(new[] { '\r' }).Split('\n'); + + foreach (string line in lines) + { + string lineToPrint = line.TrimEnd('\r'); + int startIndex = 0; + + while ((lineToPrint.Length - startIndex) > availableColumns) + { + lineList.Add(lineToPrint.Substring(startIndex, availableColumns)); + startIndex += availableColumns; + availableColumns = columnCount; + } + + lineList.Add(lineToPrint.Substring(startIndex)); + availableColumns = columnCount; + } + + return lineList; + } + + /// + /// Create debug output for a character + /// + /// Character to debug + /// Character if it's definitly printable, the escaped code point otherwise + public static string DebugCharacter(char c) + { + if ((int)c >= 0x20 && (int)c < 0x80) + { + return c.ToString(); + } + else + { + return "\\" + (int)c; + } + } } -} \ No newline at end of file +} diff --git a/src/kOS.Safe/Screen/ScreenBufferLine.cs b/src/kOS.Safe/Screen/ScreenBufferLine.cs index 4ad0bb7405..ece5a36aba 100644 --- a/src/kOS.Safe/Screen/ScreenBufferLine.cs +++ b/src/kOS.Safe/Screen/ScreenBufferLine.cs @@ -45,6 +45,26 @@ public char[] ToArray() /// public char this[int i] { get { return charArray[i]; } set { charArray[i] = value; TouchTime(); } } + /// + /// Set a single character without changing the timestamp. Should be used carefully in conjunction with SetTimestamp, + /// e.g. by SubBuffer.MergeTo + /// + /// Target Index + /// Value to set + public void SetCharIgnoreTime(int i, char c) + { + charArray[i] = c; + } + + /// + /// Manually set the change timestamp. Should be used carefully in conjunction with SetCharIgnoreTime, e.g. by SubBuffer.MergeTo + /// + /// Timestamp to set + public void SetTimestamp(ulong timestamp) + { + LastChangeTick = timestamp; + } + /// /// Constructor given the array size. This fills the same role as doing:
/// new char[size];
diff --git a/src/kOS.Safe/Screen/ScreenSnapshot.cs b/src/kOS.Safe/Screen/ScreenSnapshot.cs index 6d0eb66fbc..8e9ab55c6e 100644 --- a/src/kOS.Safe/Screen/ScreenSnapshot.cs +++ b/src/kOS.Safe/Screen/ScreenSnapshot.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Text; using kOS.Safe.UserIO; @@ -13,7 +13,8 @@ public class ScreenSnapShot : IScreenSnapShot public List Buffer { get; private set;} public int TopRow {get; private set;} public int CursorColumn {get; private set;} - public int CursorRow {get; private set;} + public int CursorRow {get; private set; } + public bool CursorVisible { get { var row = CursorRow; return row >= 0 && row < RowCount && CursorColumn < Buffer[row].Length; } } public int RowCount {get; private set;} // Tweakable setting: @@ -107,6 +108,13 @@ public string DiffFrom(IScreenSnapShot older) int trackCursorColumn = older.CursorColumn; // track the movements that will occur as the outputs happen. int trackCursorRow = older.CursorRow; // track the movements that will occur as the outputs happen. + //invalidate the cursor tracking because the last send cursor position will not actually be there (see below) + if(!older.CursorVisible) + { + trackCursorColumn = -100; + trackCursorRow = -100; + } + // First, output the command to make the terminal scroll to match: if (verticalScroll > 0) // scrolling text up (eyeballs panning down) output.Append(new String((char)UnicodeCommand.SCROLLSCREENUPONE, verticalScroll)); // A run of scrollup chars @@ -203,7 +211,7 @@ public string DiffFrom(IScreenSnapShot older) } } - + // Now set the cursor back to the right spot one more time, unless it's already there: if (trackCursorRow != CursorRow || trackCursorColumn != CursorColumn) { @@ -212,6 +220,20 @@ public string DiffFrom(IScreenSnapShot older) (char)CursorColumn, (char)CursorRow)); } + + //Do cursor (un)hiding + if (CursorVisible != older.CursorVisible) + { + if(CursorVisible) + { + output.Append((char)UnicodeCommand.SHOWCURSOR); + } + else + { + output.Append((char)UnicodeCommand.HIDECURSOR); + } + } + return output.ToString(); } diff --git a/src/kOS.Safe/Screen/SubBuffer.cs b/src/kOS.Safe/Screen/SubBuffer.cs index f4115f1a56..325cf80a3e 100644 --- a/src/kOS.Safe/Screen/SubBuffer.cs +++ b/src/kOS.Safe/Screen/SubBuffer.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Text; @@ -7,14 +8,25 @@ public class SubBuffer { public readonly List Buffer = new List(); - public int RowCount { get; private set; } + public int RowCount { get; protected set; } public int ColumnCount { get; private set; } - public bool Fixed { get; set; } public int PositionRow { get; set; } - public int PositionColumn { get; set; } - public bool Enabled { get; set; } + + /// is the position of this buffer fixed relative to the View, or is it absolute? + public bool Fixed { get; set; } + + /// should ScreenBuffer merge this into the View? + public bool Enabled { get; set; } + + /// should resizing truncate contents, or extend the rows? public bool WillTruncate { get; set; } + /// should print and movecursor autoextend if it goes past the existing rows? + public bool AutoExtend { get; set; } + + /// should resize not wrap lines back around when resizing to larger? + public bool KeepShortenedLines { get; set; } + /// /// Stretch/shrink the subbuffer to a new size, trying as best as possible to /// preserve existing contents, including wrapping lines if needed. Note that @@ -41,6 +53,79 @@ public void Wipe() RowCount = 0; } + /// + /// Marks the given section of rows in the buffer as dirty and + /// in need of a diff check. + /// + /// Starting with this row number + /// for this many rows, or up to the max row the buffer has if this number is too large + public void MarkRowsDirty(int startRow, int numRows) + { + //limit to rows actually existing in the buffer + if(startRow < 0) + { + numRows += startRow; + startRow = 0; + } + int numSafeRows = (numRows + startRow <= RowCount) ? numRows : RowCount - startRow; + + for (int i = 0; i < numSafeRows; ++i) + Buffer[startRow + i].TouchTime(); + } + + /// + /// Write this subbuffer to another subbuffer. Null-characters are not written but used as a mask. + /// In general, only the overlapping area is actually transfered. If AutoExtend is true, new rows may be added to the end of target to accomodate source. + /// + /// Target SubBuffer + /// PositionRow offset for SubBuffers that are Fixed + public void MergeTo(SubBuffer other, int fixedOffset) + { + int myAbsoluteStartRow = PositionRow + (Fixed ? fixedOffset : 0); + int otherAbsoluteStartRow = other.PositionRow + (other.Fixed ? fixedOffset : 0); + + int myInternalRow = Math.Max(0, otherAbsoluteStartRow - myAbsoluteStartRow); + int otherInternalRow = Math.Max(0, myAbsoluteStartRow - otherAbsoluteStartRow); + + if(other.AutoExtend) + { + int delta = (RowCount - myInternalRow) - (other.RowCount - otherInternalRow); + if(delta > 0) + { + other.SetSize(other.RowCount + delta, other.ColumnCount); + } + } + + if (myAbsoluteStartRow + RowCount < otherAbsoluteStartRow || otherAbsoluteStartRow + other.RowCount < myAbsoluteStartRow) + { + //no overlap, nothing to do + return; + } + + while (myInternalRow < RowCount && otherInternalRow < other.RowCount) + { + bool actuallyChanged = false; + IScreenBufferLine myRow = Buffer[myInternalRow]; + IScreenBufferLine otherRow = other.Buffer[otherInternalRow]; + + for(int i = 0; i < ColumnCount && i < other.ColumnCount; ++i) + { + if(myRow[i] != '\0') + { + otherRow.SetCharIgnoreTime(i, myRow[i]); + actuallyChanged = true; + } + } + if(actuallyChanged) + { + otherRow.SetTimestamp(Math.Max(otherRow.LastChangeTick, myRow.LastChangeTick)); + } + + myInternalRow++; + otherInternalRow++; + } + } + /// /// The default behavior is to resize the subbuffer's width to match the parent /// screenbuffer's width, but to leave the height alone as-is. All the subbuffers @@ -60,59 +145,29 @@ public virtual int NotifyOfParentResize(IScreenBuffer sb) /// /// Replaces Buffer with one of the new current size, copying old contents - /// over. + /// over. The base implementation does no longer wrap over lines in any way, it just truncates/leaves empty space. /// - /// (Not implemented, hardcoded to zero) The new row to scroll to if need be. + /// (Not implemented, hardcoded to zero) The amount of scrolling to happen as a result protected virtual int ResizeBuffer() { - // This method also is being left open for potential overriding in subclasses - // if we want subbuffers that don't behave this way. The reason is that - // this behavior is very specific to implementing a command line editor, and - // the assumption that the entire subbuffer was meant to 'flow' and wrap as one long line. - // That assumption may be very different for other cases, if any exist in the future. - List newBuffer = new List(); - int newRow = 0; - int oldRow = 0; - int newCol = 0; - bool isFull = false; - - // First, copy everything from old to new, and perhaps enlarge the new if the old won't fit. - while (oldRow < Buffer.Count && !isFull) + // First, copy everything that fits from old to new + for(int row = 0; row < RowCount; ++row) { - int oldCol = 0; - while (oldCol < Buffer[oldRow].Length && !isFull) + newBuffer.Add(new ScreenBufferLine(ColumnCount)); + if (row >= Buffer.Count) break; + for(int col = 0; col < ColumnCount; ++col) { - if (newCol >= ColumnCount) // wrap to new line when cur line is full. - { - ++newRow; - newCol = 0; - } - if (newRow >= newBuffer.Count) // grow to required size, or quit if we aren't supposed to grow - { - if (WillTruncate) - { - isFull = true; - break; - } - else - newBuffer.Add(new ScreenBufferLine(ColumnCount)); - } - newBuffer[newRow][newCol] = Buffer[oldRow][oldCol]; - ++newCol; - ++oldCol; + if (col >= Buffer[0].Length) break; + newBuffer[row][col] = Buffer[row][col]; } - ++oldRow; } // Then maybe append more empty rows if the above wasn't enough to fill the new size: while (newBuffer.Count < RowCount) newBuffer.Add(new ScreenBufferLine(ColumnCount)); - // Reset the subbuffer row size since the new size might have had to grow (i.e. shrinking the width of a 80 col to 60 col so wrap text added a row.) - // int scrollDiff = (newBuffer.Count - RowCount); - this logic not quite working - disabled for now. - RowCount = newBuffer.Count; // Because Buffer is readonly, copy the data from newBuffer into it rather than just resetting the reference to newBuffer: @@ -121,23 +176,33 @@ protected virtual int ResizeBuffer() { Buffer.Add(t); } - - // return Fixed ? 0 : scrollDiff; - this logic not quite working - disabled for now. + return 0; } - - public string DebugDump() + + /// + /// Get the actual List Buffer. + /// WARNING: right now this returns a reference to the internal buffer, + /// because this is currently only used to extract the list from a temporary SubBuffer. + /// + /// A reference to the internal List + public List GetBuffer() + { + return Buffer; + } + + public virtual string DebugDump() { StringBuilder sb = new StringBuilder(); sb.Append("DebugDump Subbuffer: Fixed="+Fixed+", Enabled="+Enabled+", RowCount="+RowCount+ - ", ColumnCount=" + ColumnCount + ", PositionRow="+PositionRow + ", PositionCol="+PositionColumn+"\n"); + ", ColumnCount=" + ColumnCount + ", PositionRow="+PositionRow + "\n"); for (int i = 0; i < Buffer.Count ; ++i) { sb.Append(" line "+i+" = ["); for (int j = 0 ; j < Buffer[i].Length ; ++j) { char ch = Buffer[i][j]; - sb.Append((int)ch < 32 ? (" \\"+(int)ch) : (" "+ch) ); + sb.Append(" " + ScreenBuffer.DebugCharacter(ch)); } sb.Append("]\n"); } diff --git a/src/kOS.Safe/Screen/TextEditor.cs b/src/kOS.Safe/Screen/TextEditor.cs index 72ae75408b..e687c0e90a 100644 --- a/src/kOS.Safe/Screen/TextEditor.cs +++ b/src/kOS.Safe/Screen/TextEditor.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Text; using System; using kOS.Safe.UserIO; @@ -7,16 +7,14 @@ namespace kOS.Safe.Screen { public class TextEditor : ScreenBuffer { - private int savedCursorRow; - private int savedCursorColumn; private int cursorColumnBuffer; private int cursorRowBuffer; - public override int CursorColumnShow { get { return cursorColumnBuffer; } } - public override int CursorRowShow { get { return CursorRow + cursorRowBuffer; } } + public override int CursorColumnShow { get { return cursorRowBuffer == 0 ? base.CursorColumnShow + cursorColumnBuffer : cursorColumnBuffer; } } + public override int CursorRowShow { get { return base.CursorRowShow + cursorRowBuffer; } } protected StringBuilder LineBuilder { get; set; } - protected SubBuffer LineSubBuffer { get; set; } + protected PrintingBuffer LineSubBuffer { get; set; } protected int LineCursorIndex { get; set; } @@ -28,12 +26,28 @@ public TextEditor() private void CreateSubBuffer() { - LineSubBuffer = new SubBuffer(); + LineSubBuffer = new PrintingBuffer(); LineSubBuffer.SetSize(1, ColumnCount); LineSubBuffer.Enabled = true; + LineSubBuffer.AutoExtend = true; AddSubBuffer(LineSubBuffer); } + // Hook into MoveCursor to dirty the lines we leave behind + public override void MoveCursor(int row, int column) + { + if (row > AbsoluteCursorRow) + { + MarkRowsDirty(AbsoluteCursorRow, row - AbsoluteCursorRow); + } + else + { + MarkRowsDirty(row + LineSubBuffer.RowCount - 1, LineSubBuffer.RowCount + 2); + } + base.MoveCursor(row, column); + UpdateLineSubBuffer(); + } + public virtual void Type(char ch) { switch ((int)ch) @@ -92,19 +106,8 @@ public virtual bool SpecialKey(char key) gotUsed = false; break; } - return gotUsed; - } - protected void SaveCursorPos() - { - savedCursorRow = AbsoluteCursorRow; - savedCursorColumn = CursorColumn; - } - - protected void RestoreCursorPos() - { - AbsoluteCursorRow = savedCursorRow; - CursorColumn = savedCursorColumn; + return gotUsed; } protected void InsertChar(char character) @@ -127,48 +130,38 @@ protected void RemoveChar() } } - private List SplitIntoLinesPreserveNewLine(string text) + /// SplitIntoLines with our base cursor + private List SplitIntoLines(string text) { - List lines = SplitIntoLines(text); - - if (text.EndsWith("\n")) - { - int newLinesCount = text.Length - text.TrimEnd('\n').Length; - while(newLinesCount-- > 0) - lines.Add(""); - } - - return lines; + return SplitIntoLines(text, ColumnCount, base.CursorColumnShow); } - + protected void UpdateLineSubBuffer() { string commandText = LineBuilder.ToString(); - List lines = SplitIntoLinesPreserveNewLine(commandText); - - if (lines.Count != LineSubBuffer.RowCount) - { - LineSubBuffer.SetSize(lines.Count, LineSubBuffer.ColumnCount); - } - for (int lineIndex = 0; lineIndex < lines.Count; lineIndex++) + LineSubBuffer.Wipe(); + LineSubBuffer.PositionRow = AbsoluteCursorRow; + LineSubBuffer.MoveCursor(0, base.CursorColumnShow); + + if (commandText.Length == 0) { - char[] lineCharArray = lines[lineIndex].PadRight(LineSubBuffer.ColumnCount, ' ').ToCharArray(); - LineSubBuffer.Buffer[lineIndex].ArrayCopyFrom(lineCharArray, 0, 0); + cursorColumnBuffer = 0; + cursorRowBuffer = 0; } + else + { + LineSubBuffer.Print(commandText, false); - UpdateSubBufferCursor(lines); + UpdateSubBufferCursor(); + } } private void UpdateSubBufferCursor() { string commandText = LineBuilder.ToString(); - List lines = SplitIntoLinesPreserveNewLine(commandText); - UpdateSubBufferCursor(lines); - } + List lines = SplitIntoLines(commandText); - private void UpdateSubBufferCursor(List lines) - { int lineIndex = 0; int lineCursorIndex = LineCursorIndex; @@ -178,8 +171,12 @@ private void UpdateSubBufferCursor(List lines) lineCursorIndex -= lines[lineIndex].Length; // if the line is shorter than the width of the screen then move // the cursor another position to compensate for the newline character - if (lines[lineIndex].Length < ColumnCount) + if (lines[lineIndex].Length < ColumnCount && + // we need to catch and not count the case where the line is actually full, because we didn't start on the first column + !(lineIndex == 0 && lines[lineIndex].Length == ColumnCount - base.CursorColumnShow)) + { lineCursorIndex--; + } lineIndex++; } @@ -193,19 +190,12 @@ protected void KeepCursorInBounds() { // Check to see if wrapping or scrolling needs to be done // because the cursor went off the screen, and if so, do it: - if (CursorColumnShow >= ColumnCount) + if (CursorColumnShow == ColumnCount) { - int tooBigColumn = CursorColumnShow; - cursorColumnBuffer = (tooBigColumn % ColumnCount); - cursorRowBuffer += (tooBigColumn / ColumnCount); // truncating integer division. - } - if (CursorRowShow >= RowCount) - { - int rowsToScroll = (CursorRowShow-RowCount) + 1; - CursorRow -= rowsToScroll; - ScrollVertical(rowsToScroll); - AddNewBufferLines(rowsToScroll); + cursorColumnBuffer = 0; + cursorRowBuffer++; } + ScrollCursorVisible(); } protected virtual void NewLine() @@ -238,11 +228,7 @@ public virtual void Reset() { LineBuilder = new StringBuilder(); LineCursorIndex = 0; - } - - protected override void UpdateSubBuffers() - { - LineSubBuffer.PositionRow = AbsoluteCursorRow; + UpdateLineSubBuffer(); } } } diff --git a/src/kOS.Safe/UserIO/UnicodeCommand.cs b/src/kOS.Safe/UserIO/UnicodeCommand.cs index 13531fe573..c25b845b52 100644 --- a/src/kOS.Safe/UserIO/UnicodeCommand.cs +++ b/src/kOS.Safe/UserIO/UnicodeCommand.cs @@ -211,6 +211,12 @@ public enum UnicodeCommand // /// // RIGHTCURSORNUM, + /// + /// Enable or disable actually displaying the cursor. + /// This is basicly a passthrough of ScreenBuffer.CursorVisible. + /// + SHOWCURSOR, HIDECURSOR, + /// /// Tell the terminal to resize itself to a new row/col size. Not all terminals will be capable of doing this. /// This can be communicated in either direction - for the client telling the server it has been resized, or diff --git a/src/kOS.Safe/kOS.Safe.csproj b/src/kOS.Safe/kOS.Safe.csproj index f7e5f37a4e..3f3b453f26 100644 --- a/src/kOS.Safe/kOS.Safe.csproj +++ b/src/kOS.Safe/kOS.Safe.csproj @@ -9,7 +9,7 @@ Properties kOS.Safe kOS.Safe - v4.5 + v4.5.1 512 @@ -217,6 +217,7 @@ + diff --git a/src/kOS/Screen/Interpreter.cs b/src/kOS/Screen/Interpreter.cs index 997fc3d201..d4f6522888 100644 --- a/src/kOS/Screen/Interpreter.cs +++ b/src/kOS/Screen/Interpreter.cs @@ -36,9 +36,6 @@ protected override void NewLine() // the command is present in the history to be found and printed in the // error message. ProcessCommand(commandText); - int numRows = LineSubBuffer.RowCount; - LineSubBuffer.Wipe(); - LineSubBuffer.SetSize(numRows,ColumnCount); // refill it to its previous size } else { @@ -112,7 +109,6 @@ private void ShowCommandHistoryEntry(int deltaIndex) LineBuilder.Append(commandHistory[commandHistoryIndex]); LineCursorIndex = LineBuilder.Length; MarkRowsDirty(LineSubBuffer.PositionRow, LineSubBuffer.RowCount); - LineSubBuffer.Wipe(); UpdateLineSubBuffer(); } } @@ -183,13 +179,6 @@ public override void Reset() base.Reset(); } - public override void PrintAt(string textToPrint, int row, int column) - { - SaveCursorPos(); - base.PrintAt(textToPrint, row, column); - RestoreCursorPos(); - } - private class InterpreterPath : InternalPath { private Interpreter interpreter; diff --git a/src/kOS/Screen/TermWindow.cs b/src/kOS/Screen/TermWindow.cs index 2152912419..121cf2a188 100644 --- a/src/kOS/Screen/TermWindow.cs +++ b/src/kOS/Screen/TermWindow.cs @@ -1030,7 +1030,7 @@ void TerminalGui(int windowId) // Only if the cursor is within terminal bounds, to avoid throwing array bounds exceptions. // (Cursor can be temporarily out of bounds if the up-arrow recalled a long cmdline, or if // the terminal just got resized.) - cursorRow < screen.RowCount && cursorRow < buffer.Count && cursorCol < buffer[cursorRow].Length && + mostRecentScreen.CursorVisible && // Only when the CPU has power IsPowered && // Only when expecting input diff --git a/src/kOS/UserIO/TerminalXtermMapper.cs b/src/kOS/UserIO/TerminalXtermMapper.cs index 59fbc360ca..906ad9a19f 100644 --- a/src/kOS/UserIO/TerminalXtermMapper.cs +++ b/src/kOS/UserIO/TerminalXtermMapper.cs @@ -77,6 +77,12 @@ public override char[] OutputConvert(string str) sb.AppendFormat("{0}?47h", ESCAPE_CHARACTER); // <-- Tells xterm to use fixed-buffer mode, not saving in scrollback. sb.AppendFormat("{0}2J{0}H", CSI); // <-- The normal clear screen char from vt100. break; + case (char)UnicodeCommand.SHOWCURSOR: + sb.AppendFormat("{0}?25h", CSI); + break; + case (char)UnicodeCommand.HIDECURSOR: + sb.AppendFormat("{0}?25l", CSI); + break; default: sb.Append(t); // default passhtrough break; diff --git a/src/kOS/kOS.csproj b/src/kOS/kOS.csproj index 336b73c56e..d551d829da 100644 --- a/src/kOS/kOS.csproj +++ b/src/kOS/kOS.csproj @@ -8,7 +8,7 @@ Properties kOS kOS - v4.5 + v4.5.1 512