From f2594893b46570aa8cdbf3e15614d0115b53e593 Mon Sep 17 00:00:00 2001 From: Philipp Caratiola Date: Thu, 27 Dec 2018 20:18:22 +0100 Subject: [PATCH 01/14] New suffixes :cursorcol and :cursorrow for terminal - see #2394 --- kerboscript_tests/terminalcursor.ks | 17 +++++++++++++++++ src/kOS.Safe/Encapsulation/TerminalStruct.cs | 15 +++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 kerboscript_tests/terminalcursor.ks diff --git a/kerboscript_tests/terminalcursor.ks b/kerboscript_tests/terminalcursor.ks new file mode 100644 index 0000000000..0967bd9b88 --- /dev/null +++ b/kerboscript_tests/terminalcursor.ks @@ -0,0 +1,17 @@ +@lazyglobal off. + +local teststring is "This is a teststring". +local words is teststring:split(" "). + +local remember is terminal:width. +set terminal:width to teststring:length. +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 terminal:cursorcol - terminal:width + words[0]:length + 1. +print words[1]. +set terminal:cursorrow to terminal:cursorrow - 1. +set terminal:cursorcol to words[0]:length + words[1]:length + 2. +print words[2] + " " + words[3]. + +set terminal:width to remember. \ No newline at end of file diff --git a/src/kOS.Safe/Encapsulation/TerminalStruct.cs b/src/kOS.Safe/Encapsulation/TerminalStruct.cs index 5d8f910699..9acc20e0b9 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 => { //Screen.MoveCursor rolls over the end of line, but not over the front, so we have to implement the latter ourselves. + int row = Shared.Screen.CursorRowShow; + while(value < 0) + { + row--; + value += Shared.Screen.ColumnCount; + } + Shared.Screen.MoveCursor(row, value); + }, + "Current cursor column. Will roll over the screen edges into the next or previous row.")); + AddSuffix("CURSORROW", new SetSuffix(() => Shared.Screen.CursorRowShow, + value => Shared.Screen.MoveCursor((int)KOSMath.Clamp(value,0,Shared.Screen.RowCount), Shared.Screen.CursorColumnShow), + "Current cursor row, between 0 and HEIGHT-1")); } private void CannotSetWidth(ScalarValue newWidth) From 713373525d26d6abece8e5725e6875b87a615afa Mon Sep 17 00:00:00 2001 From: Philipp Caratiola Date: Fri, 28 Dec 2018 11:22:00 +0100 Subject: [PATCH 02/14] - new suffix terminal:put, see #2394 - changed TextEditor.CursorColumnShow to fix terminal:cursorcol --- kerboscript_tests/terminalcursor.ks | 15 ++++++++++----- src/kOS.Safe/Encapsulation/TerminalStruct.cs | 12 +++--------- src/kOS.Safe/Screen/TextEditor.cs | 4 ++-- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/kerboscript_tests/terminalcursor.ks b/kerboscript_tests/terminalcursor.ks index 0967bd9b88..e76c460c62 100644 --- a/kerboscript_tests/terminalcursor.ks +++ b/kerboscript_tests/terminalcursor.ks @@ -3,15 +3,20 @@ local teststring is "This is a teststring". local words is teststring:split(" "). -local remember is terminal:width. -set terminal:width to teststring:length. 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 terminal:cursorcol - terminal:width + words[0]:length + 1. +set terminal:cursorcol to words[0]:length + 1. +set terminal:cursorrow to terminal:cursorrow - 1. print words[1]. set terminal:cursorrow to terminal:cursorrow - 1. set terminal:cursorcol to words[0]:length + words[1]:length + 2. print words[2] + " " + words[3]. - -set terminal:width to remember. \ No newline at end of file +print words[0] + " " + words[1]. +set terminal:cursorrow to terminal:cursorrow - 2. +set terminal:cursorcol to words[0]:length + words[1]:length +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/Encapsulation/TerminalStruct.cs b/src/kOS.Safe/Encapsulation/TerminalStruct.cs index 9acc20e0b9..5c4230a695 100644 --- a/src/kOS.Safe/Encapsulation/TerminalStruct.cs +++ b/src/kOS.Safe/Encapsulation/TerminalStruct.cs @@ -163,19 +163,13 @@ private void InitializeSuffixes() AddSuffix("RESIZEWATCHERS", new NoArgsSuffix>(() => resizeWatchers)); AddSuffix("INPUT", new Suffix(GetTerminalInputInstance)); AddSuffix("CURSORCOL", new SetSuffix(() => Shared.Screen.CursorColumnShow, - value => { //Screen.MoveCursor rolls over the end of line, but not over the front, so we have to implement the latter ourselves. - int row = Shared.Screen.CursorRowShow; - while(value < 0) - { - row--; - value += Shared.Screen.ColumnCount; - } - Shared.Screen.MoveCursor(row, value); - }, + value => Shared.Screen.MoveCursor(Shared.Screen.CursorRowShow, (int)KOSMath.Clamp(value,0,Shared.Screen.ColumnCount)), "Current cursor column. Will roll over the screen edges into the next or previous row.")); AddSuffix("CURSORROW", new SetSuffix(() => Shared.Screen.CursorRowShow, value => Shared.Screen.MoveCursor((int)KOSMath.Clamp(value,0,Shared.Screen.RowCount), Shared.Screen.CursorColumnShow), "Current cursor row, between 0 and HEIGHT-1")); + AddSuffix("PUT", new OneArgsSuffix(value => Shared.Screen.Print(value,false), + "Put string at current cursor position (without implied newline).")); } private void CannotSetWidth(ScalarValue newWidth) diff --git a/src/kOS.Safe/Screen/TextEditor.cs b/src/kOS.Safe/Screen/TextEditor.cs index 72ae75408b..3ef53638ec 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; @@ -12,7 +12,7 @@ public class TextEditor : ScreenBuffer private int cursorColumnBuffer; private int cursorRowBuffer; - public override int CursorColumnShow { get { return cursorColumnBuffer; } } + public override int CursorColumnShow { get { return CursorColumn + cursorColumnBuffer; } } public override int CursorRowShow { get { return CursorRow + cursorRowBuffer; } } protected StringBuilder LineBuilder { get; set; } From 0ce362db609149f50a0dc54fa5161bc54b7c140f Mon Sep 17 00:00:00 2001 From: Philipp Caratiola Date: Fri, 28 Dec 2018 12:28:45 +0100 Subject: [PATCH 03/14] Updated documentation for #2394 --- doc/source/structures/misc/terminal.rst | 35 ++++++++++++++++++++ src/kOS.Safe/Encapsulation/TerminalStruct.cs | 4 +-- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/doc/source/structures/misc/terminal.rst b/doc/source/structures/misc/terminal.rst index 55b72a3bc9..ff020fbeb7 100644 --- a/doc/source/structures/misc/terminal.rst +++ b/doc/source/structures/misc/terminal.rst @@ -55,6 +55,21 @@ 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:`PUT(string)` + - None + - Method Call + - Output string without newline. + * - :attr:`INPUT` - :struct:`TerminalInput` - get @@ -166,6 +181,26 @@ 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:PUT(text) + + :parameter text: (string) Text to print + + Put string at current cursor position (without implied newline). + .. attribute:: Terminal:INPUT :access: Get diff --git a/src/kOS.Safe/Encapsulation/TerminalStruct.cs b/src/kOS.Safe/Encapsulation/TerminalStruct.cs index 5c4230a695..adf4a5f554 100644 --- a/src/kOS.Safe/Encapsulation/TerminalStruct.cs +++ b/src/kOS.Safe/Encapsulation/TerminalStruct.cs @@ -164,10 +164,10 @@ private void InitializeSuffixes() AddSuffix("INPUT", new Suffix(GetTerminalInputInstance)); AddSuffix("CURSORCOL", new SetSuffix(() => Shared.Screen.CursorColumnShow, value => Shared.Screen.MoveCursor(Shared.Screen.CursorRowShow, (int)KOSMath.Clamp(value,0,Shared.Screen.ColumnCount)), - "Current cursor column. Will roll over the screen edges into the next or previous row.")); + "Current cursor column, between 0 and WIDTH-1.")); AddSuffix("CURSORROW", new SetSuffix(() => Shared.Screen.CursorRowShow, value => Shared.Screen.MoveCursor((int)KOSMath.Clamp(value,0,Shared.Screen.RowCount), Shared.Screen.CursorColumnShow), - "Current cursor row, between 0 and HEIGHT-1")); + "Current cursor row, between 0 and HEIGHT-1.")); AddSuffix("PUT", new OneArgsSuffix(value => Shared.Screen.Print(value,false), "Put string at current cursor position (without implied newline).")); } From 0c48a6c79f0175e61e8621a731ad95dfa111593b Mon Sep 17 00:00:00 2001 From: Philipp Caratiola Date: Sat, 29 Dec 2018 14:27:18 +0100 Subject: [PATCH 04/14] Added terminal:putln, aliasing global print --- doc/source/commands/terminalgui.rst | 2 ++ doc/source/structures/misc/terminal.rst | 12 ++++++++++++ kerboscript_tests/terminalcursor.ks | 4 ++-- src/kOS.Safe/Encapsulation/TerminalStruct.cs | 2 ++ 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/doc/source/commands/terminalgui.rst b/doc/source/commands/terminalgui.rst index e9173e54b8..b82f8e5a9a 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. diff --git a/doc/source/structures/misc/terminal.rst b/doc/source/structures/misc/terminal.rst index ff020fbeb7..c9ea7e72b6 100644 --- a/doc/source/structures/misc/terminal.rst +++ b/doc/source/structures/misc/terminal.rst @@ -70,6 +70,11 @@ Structure - Method Call - Output string without newline. + * - :meth:`PUTLN(string)` + - None + - Method Call + - Output string with newline. + * - :attr:`INPUT` - :struct:`TerminalInput` - get @@ -201,6 +206,13 @@ Structure 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` + .. attribute:: Terminal:INPUT :access: Get diff --git a/kerboscript_tests/terminalcursor.ks b/kerboscript_tests/terminalcursor.ks index e76c460c62..90742a0606 100644 --- a/kerboscript_tests/terminalcursor.ks +++ b/kerboscript_tests/terminalcursor.ks @@ -8,10 +8,10 @@ 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. -print words[1]. +terminal:putln(words[1]). set terminal:cursorrow to terminal:cursorrow - 1. set terminal:cursorcol to words[0]:length + words[1]:length + 2. -print words[2] + " " + words[3]. +terminal:putln(words[2] + " " + words[3]). print words[0] + " " + words[1]. set terminal:cursorrow to terminal:cursorrow - 2. set terminal:cursorcol to words[0]:length + words[1]:length +2. diff --git a/src/kOS.Safe/Encapsulation/TerminalStruct.cs b/src/kOS.Safe/Encapsulation/TerminalStruct.cs index adf4a5f554..480e393883 100644 --- a/src/kOS.Safe/Encapsulation/TerminalStruct.cs +++ b/src/kOS.Safe/Encapsulation/TerminalStruct.cs @@ -170,6 +170,8 @@ private void InitializeSuffixes() "Current cursor row, between 0 and HEIGHT-1.")); AddSuffix("PUT", new OneArgsSuffix(value => Shared.Screen.Print(value,false), "Put string at current cursor position (without implied newline).")); + AddSuffix("PUTLN", new OneArgsSuffix(value => Shared.Screen.Print(value), + "Put string at current cursor position (with implied newline).")); } private void CannotSetWidth(ScalarValue newWidth) From bb539f7dc3a7ea9ff910b8303405530e682caa56 Mon Sep 17 00:00:00 2001 From: Philipp Caratiola Date: Sat, 29 Dec 2018 14:32:07 +0100 Subject: [PATCH 05/14] Clarified docs for print at. --- doc/source/commands/terminalgui.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/source/commands/terminalgui.rst b/doc/source/commands/terminalgui.rst index b82f8e5a9a..2c604a243a 100644 --- a/doc/source/commands/terminalgui.rst +++ b/doc/source/commands/terminalgui.rst @@ -35,7 +35,9 @@ 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 the :ref:`terminal ` output methods. + Can print strings, or the result of an expression:: PRINT "Hello" AT(0,10). PRINT 4+1 AT(0,10). From 091f0a20d7b557a3bb5a9e19cdbc9fa145fd5301 Mon Sep 17 00:00:00 2001 From: Philipp Caratiola Date: Thu, 3 Jan 2019 14:03:19 +0100 Subject: [PATCH 06/14] Added terminal:putat, aliasing global print at --- doc/source/commands/terminalgui.rst | 3 ++- doc/source/structures/misc/terminal.rst | 14 ++++++++++++++ kerboscript_tests/terminalcursor.ks | 1 + src/kOS.Safe/Encapsulation/TerminalStruct.cs | 2 ++ 4 files changed, 19 insertions(+), 1 deletion(-) diff --git a/doc/source/commands/terminalgui.rst b/doc/source/commands/terminalgui.rst index 2c604a243a..cf783386f9 100644 --- a/doc/source/commands/terminalgui.rst +++ b/doc/source/commands/terminalgui.rst @@ -36,7 +36,8 @@ Terminal and game environment :parameter line: (integer) line starting with zero (top) 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 the :ref:`terminal ` output methods. + 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). diff --git a/doc/source/structures/misc/terminal.rst b/doc/source/structures/misc/terminal.rst index c9ea7e72b6..ab9fcba5a4 100644 --- a/doc/source/structures/misc/terminal.rst +++ b/doc/source/structures/misc/terminal.rst @@ -75,6 +75,11 @@ Structure - Method Call - Output string with newline. + * - :meth:`PUTAT(text,column,row)` + - None + - Method Call + - Output string at position + * - :attr:`INPUT` - :struct:`TerminalInput` - get @@ -213,6 +218,15 @@ Structure 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.ks b/kerboscript_tests/terminalcursor.ks index 90742a0606..c200c9bb2c 100644 --- a/kerboscript_tests/terminalcursor.ks +++ b/kerboscript_tests/terminalcursor.ks @@ -13,6 +13,7 @@ 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). set terminal:cursorrow to terminal:cursorrow - 2. set terminal:cursorcol to words[0]:length + words[1]:length +2. terminal:put(words[2]). diff --git a/src/kOS.Safe/Encapsulation/TerminalStruct.cs b/src/kOS.Safe/Encapsulation/TerminalStruct.cs index 480e393883..0f253196bd 100644 --- a/src/kOS.Safe/Encapsulation/TerminalStruct.cs +++ b/src/kOS.Safe/Encapsulation/TerminalStruct.cs @@ -172,6 +172,8 @@ private void InitializeSuffixes() "Put string at current cursor position (without implied newline).")); AddSuffix("PUTLN", new OneArgsSuffix(value => Shared.Screen.Print(value), "Put string at current cursor position (with implied newline).")); + AddSuffix("PUTAT", new ThreeArgsSuffix((StringValue text, ScalarValue col, ScalarValue row) => Shared.Screen.PrintAt(text, row, col), + "Put string at position without moving the cursor.")); } private void CannotSetWidth(ScalarValue newWidth) From 1d3cb7f6261314666136052bbab963de10e85a69 Mon Sep 17 00:00:00 2001 From: Philipp Caratiola Date: Thu, 3 Jan 2019 14:14:08 +0100 Subject: [PATCH 07/14] Added convenience method terminal:movecursor --- doc/source/structures/misc/terminal.rst | 12 ++++++++++++ kerboscript_tests/terminalcursor.ks | 3 +-- src/kOS.Safe/Encapsulation/TerminalStruct.cs | 2 ++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/doc/source/structures/misc/terminal.rst b/doc/source/structures/misc/terminal.rst index ab9fcba5a4..ab873d3247 100644 --- a/doc/source/structures/misc/terminal.rst +++ b/doc/source/structures/misc/terminal.rst @@ -65,6 +65,11 @@ Structure - 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 @@ -205,6 +210,13 @@ Structure 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 diff --git a/kerboscript_tests/terminalcursor.ks b/kerboscript_tests/terminalcursor.ks index c200c9bb2c..4747e9e4ce 100644 --- a/kerboscript_tests/terminalcursor.ks +++ b/kerboscript_tests/terminalcursor.ks @@ -14,8 +14,7 @@ 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). -set terminal:cursorrow to terminal:cursorrow - 2. -set terminal:cursorcol to words[0]:length + words[1]:length +2. +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. diff --git a/src/kOS.Safe/Encapsulation/TerminalStruct.cs b/src/kOS.Safe/Encapsulation/TerminalStruct.cs index 0f253196bd..325bcd8243 100644 --- a/src/kOS.Safe/Encapsulation/TerminalStruct.cs +++ b/src/kOS.Safe/Encapsulation/TerminalStruct.cs @@ -168,6 +168,8 @@ private void InitializeSuffixes() AddSuffix("CURSORROW", new SetSuffix(() => Shared.Screen.CursorRowShow, value => Shared.Screen.MoveCursor((int)KOSMath.Clamp(value,0,Shared.Screen.RowCount), Shared.Screen.CursorColumnShow), "Current cursor row, between 0 and HEIGHT-1.")); + AddSuffix("MOVECURSOR", new TwoArgsSuffix((ScalarValue col, ScalarValue row) => Shared.Screen.MoveCursor(col, row), + "Move cursor to (column, row).")); AddSuffix("PUT", new OneArgsSuffix(value => Shared.Screen.Print(value,false), "Put string at current cursor position (without implied newline).")); AddSuffix("PUTLN", new OneArgsSuffix(value => Shared.Screen.Print(value), From 2ecb3c1b0db5200db6e5f3fb039b7a07aa6fc577 Mon Sep 17 00:00:00 2001 From: Philipp Caratiola Date: Fri, 8 Feb 2019 18:56:44 +0100 Subject: [PATCH 08/14] Rework the SubBuffer/ScreenBuffer stack - everything's a SubBuffer now - cursor/printing positions are now absolute instead of screen relative - no SplitLines strip newlines anymore - fixes scrolling moving the cursor - fixes resizing not moving the cursor correctly - fixes the interactive prompt hiding output in the same lines - fixes multiline interactive input - fixes output containing explicit newlines --- src/kOS.Safe.Test/Execution/Screen.cs | 8 + src/kOS.Safe/Encapsulation/TerminalStruct.cs | 8 +- src/kOS.Safe/Execution/CPU.cs | 2 +- src/kOS.Safe/Screen/IScreenBuffer.cs | 4 +- src/kOS.Safe/Screen/IScreenBufferLine.cs | 14 + src/kOS.Safe/Screen/IScreenSnapshot.cs | 3 +- src/kOS.Safe/Screen/PrintingBuffer.cs | 220 +++++++++++++ src/kOS.Safe/Screen/ScreenBuffer.cs | 327 +++++++++---------- src/kOS.Safe/Screen/ScreenBufferLine.cs | 20 ++ src/kOS.Safe/Screen/ScreenSnapshot.cs | 59 +++- src/kOS.Safe/Screen/SubBuffer.cs | 158 ++++++--- src/kOS.Safe/Screen/TextEditor.cs | 110 +++---- src/kOS.Safe/UserIO/UnicodeCommand.cs | 6 + src/kOS.Safe/kOS.Safe.csproj | 3 +- src/kOS/Screen/Interpreter.cs | 11 - src/kOS/Screen/TermWindow.cs | 4 +- src/kOS/UserIO/TerminalXtermMapper.cs | 6 + 17 files changed, 647 insertions(+), 316 deletions(-) create mode 100644 src/kOS.Safe/Screen/PrintingBuffer.cs 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/Encapsulation/TerminalStruct.cs b/src/kOS.Safe/Encapsulation/TerminalStruct.cs index 325bcd8243..5280a86384 100644 --- a/src/kOS.Safe/Encapsulation/TerminalStruct.cs +++ b/src/kOS.Safe/Encapsulation/TerminalStruct.cs @@ -163,12 +163,12 @@ private void InitializeSuffixes() AddSuffix("RESIZEWATCHERS", new NoArgsSuffix>(() => resizeWatchers)); AddSuffix("INPUT", new Suffix(GetTerminalInputInstance)); AddSuffix("CURSORCOL", new SetSuffix(() => Shared.Screen.CursorColumnShow, - value => Shared.Screen.MoveCursor(Shared.Screen.CursorRowShow, (int)KOSMath.Clamp(value,0,Shared.Screen.ColumnCount)), + 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.CursorRowShow, - value => Shared.Screen.MoveCursor((int)KOSMath.Clamp(value,0,Shared.Screen.RowCount), Shared.Screen.CursorColumnShow), + 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(col, row), + 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,false), "Put string at current cursor position (without implied newline).")); diff --git a/src/kOS.Safe/Execution/CPU.cs b/src/kOS.Safe/Execution/CPU.cs index e3feb0c09b..930afe785b 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..84e825270f 100644 --- a/src/kOS.Safe/Screen/IScreenBuffer.cs +++ b/src/kOS.Safe/Screen/IScreenBuffer.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; namespace kOS.Safe.Screen { @@ -9,6 +9,7 @@ public interface IScreenBuffer double Brightness { get; set; } // double is overkill, but floats don't work in KSP config.xml files. int CursorRowShow { get; } int CursorColumnShow { get; } + bool CursorVisible { get; } int RowCount { get; } int ColumnCount { get; } int AbsoluteCursorRow { get; set; } @@ -20,7 +21,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 a3293d0e20..f072ae1608 100644 --- a/src/kOS.Safe/Screen/ScreenBuffer.cs +++ b/src/kOS.Safe/Screen/ScreenBuffer.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -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,23 @@ 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 bool CursorVisible { get { var row = CursorRowShow; return row >= 0 && row < RowCount; } } + 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 +65,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 +111,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 +131,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(); + ScrollingOutput.Wipe(); + ScrollingOutput.SetSize(1, ColumnCount); + ScrollingOutput.MoveCursor(0, 0); + PositionalOutput.PositionRow = 0; InitializeBuffer(); } @@ -244,59 +187,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 - mergedBuffer.RemoveRange(mergeRow, rowsToMerge); - // 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(); @@ -309,50 +226,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(); @@ -365,12 +292,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..10f36366bb 100644 --- a/src/kOS.Safe/Screen/ScreenSnapshot.cs +++ b/src/kOS.Safe/Screen/ScreenSnapshot.cs @@ -14,6 +14,7 @@ public class ScreenSnapShot : IScreenSnapShot public int TopRow {get; private set;} public int CursorColumn {get; private set;} public int CursorRow {get; private set;} + public bool CursorVisible { get; private set; } public int RowCount {get; private set;} // Tweakable setting: @@ -40,6 +41,7 @@ public ScreenSnapShot(IScreenBuffer fromScreen) TopRow = fromScreen.TopRow; CursorColumn = fromScreen.CursorColumnShow; CursorRow = fromScreen.CursorRowShow; + CursorVisible = fromScreen.CursorVisible; RowCount = fromScreen.RowCount; } @@ -60,6 +62,7 @@ public static ScreenSnapShot EmptyScreen(IScreenBuffer fromScreen) newThing.CursorColumn = fromScreen.CursorColumnShow; newThing.CursorRow = fromScreen.CursorRowShow; newThing.RowCount = fromScreen.RowCount; + newThing.CursorVisible = fromScreen.CursorVisible; newThing.Buffer = new List(); for (int i = 0; i < newThing.RowCount ; ++i) newThing.Buffer.Add(new ScreenBufferLine(fromScreen.ColumnCount)); @@ -203,15 +206,57 @@ 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) + + if (CursorVisible) + { + // Now set the cursor back to the right spot one more time, unless it's already there: + if (trackCursorRow != CursorRow || trackCursorColumn != CursorColumn) + { + output.Append(String.Format("{0}{1}{2}", + (char)UnicodeCommand.TELEPORTCURSOR, + (char)CursorColumn, + (char)CursorRow)); + } + } + else + { + // Move the screen to the boundary where it went offscreen, in case the terminal can't hide it + if(CursorRow < 0) + { + output.Append(String.Format("{0}{1}{2}", + (char)UnicodeCommand.TELEPORTCURSOR, + (char)0, + (char)0)); + } + else if(RowCount > 0) + { + output.Append(String.Format("{0}{1}{2}", + (char)UnicodeCommand.TELEPORTCURSOR, + (char)Buffer[0].Length, + (char)RowCount-1)); + } + else + { + output.Append(String.Format("{0}{1}{2}", + (char)UnicodeCommand.TELEPORTCURSOR, + (char)Buffer[0].Length, + (char)RowCount - 1)); + } + } + + //Do cursor (un)hiding + if(CursorVisible != older.CursorVisible) { - output.Append(String.Format("{0}{1}{2}", - (char)UnicodeCommand.TELEPORTCURSOR, - (char)CursorColumn, - (char)CursorRow)); + 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..4e89b39b98 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,74 @@ 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) + { + // Mark fewer rows than asked to if the reqeusted number would have blown past the end of the buffer: + 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 +140,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 +171,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 3ef53638ec..b7fbd8b5b8 100644 --- a/src/kOS.Safe/Screen/TextEditor.cs +++ b/src/kOS.Safe/Screen/TextEditor.cs @@ -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 CursorColumn + 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(); + + 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.PositionRow = AbsoluteCursorRow; + LineSubBuffer.MoveCursor(0, base.CursorColumnShow); + 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) - { - int tooBigColumn = CursorColumnShow; - cursorColumnBuffer = (tooBigColumn % ColumnCount); - cursorRowBuffer += (tooBigColumn / ColumnCount); // truncating integer division. - } - if (CursorRowShow >= RowCount) + if (CursorColumnShow == ColumnCount) { - 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 bfb446d4f8..75c8b1ac56 100644 --- a/src/kOS.Safe/kOS.Safe.csproj +++ b/src/kOS.Safe/kOS.Safe.csproj @@ -213,6 +213,7 @@ + @@ -319,4 +320,4 @@ --> - + \ No newline at end of file diff --git a/src/kOS/Screen/Interpreter.cs b/src/kOS/Screen/Interpreter.cs index bdd93e0315..a633539fe2 100644 --- a/src/kOS/Screen/Interpreter.cs +++ b/src/kOS/Screen/Interpreter.cs @@ -35,9 +35,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 { @@ -111,7 +108,6 @@ private void ShowCommandHistoryEntry(int deltaIndex) LineBuilder.Append(commandHistory[commandHistoryIndex]); LineCursorIndex = LineBuilder.Length; MarkRowsDirty(LineSubBuffer.PositionRow, LineSubBuffer.RowCount); - LineSubBuffer.Wipe(); UpdateLineSubBuffer(); } } @@ -180,13 +176,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 d2a7ddb57b..a7802a04db 100644 --- a/src/kOS/Screen/TermWindow.cs +++ b/src/kOS/Screen/TermWindow.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.Collections.Generic; using kOS.Safe.Persistence; @@ -939,7 +939,7 @@ void TerminalGui(int windowId) } bool blinkOn = cursorBlinkTime < 0.5f && - screen.CursorRowShow < screen.RowCount && + screen.CursorVisible && IsPowered && ShowCursor; 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; From 92b998b0094ecc016a28b14fb7f7f22525e59a6d Mon Sep 17 00:00:00 2001 From: Philipp Caratiola Date: Sun, 10 Feb 2019 10:12:18 +0100 Subject: [PATCH 09/14] fixes the cursor vanishing when scrolling after clearscreen --- src/kOS.Safe/Screen/ScreenBuffer.cs | 4 ++-- src/kOS.Safe/Screen/SubBuffer.cs | 7 ++++++- src/kOS.Safe/Screen/TextEditor.cs | 4 ++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/kOS.Safe/Screen/ScreenBuffer.cs b/src/kOS.Safe/Screen/ScreenBuffer.cs index f072ae1608..273adf92fd 100644 --- a/src/kOS.Safe/Screen/ScreenBuffer.cs +++ b/src/kOS.Safe/Screen/ScreenBuffer.cs @@ -169,9 +169,9 @@ public void Print(string textToPrint, bool trailingNewLine) public void ClearScreen() { + MoveCursor(0, 0); ScrollingOutput.Wipe(); ScrollingOutput.SetSize(1, ColumnCount); - ScrollingOutput.MoveCursor(0, 0); PositionalOutput.PositionRow = 0; InitializeBuffer(); } @@ -270,7 +270,7 @@ private int ScrollVerticalInternal(int deltaRows = 1) deltaRows = -topRow; else if (topRow + deltaRows > maxTopRow) deltaRows = maxTopRow - topRow; - + topRow += deltaRows; return deltaRows; diff --git a/src/kOS.Safe/Screen/SubBuffer.cs b/src/kOS.Safe/Screen/SubBuffer.cs index 4e89b39b98..325cf80a3e 100644 --- a/src/kOS.Safe/Screen/SubBuffer.cs +++ b/src/kOS.Safe/Screen/SubBuffer.cs @@ -61,7 +61,12 @@ public void Wipe() /// 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: + //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) diff --git a/src/kOS.Safe/Screen/TextEditor.cs b/src/kOS.Safe/Screen/TextEditor.cs index b7fbd8b5b8..e687c0e90a 100644 --- a/src/kOS.Safe/Screen/TextEditor.cs +++ b/src/kOS.Safe/Screen/TextEditor.cs @@ -141,6 +141,8 @@ protected void UpdateLineSubBuffer() string commandText = LineBuilder.ToString(); LineSubBuffer.Wipe(); + LineSubBuffer.PositionRow = AbsoluteCursorRow; + LineSubBuffer.MoveCursor(0, base.CursorColumnShow); if (commandText.Length == 0) { @@ -149,8 +151,6 @@ protected void UpdateLineSubBuffer() } else { - LineSubBuffer.PositionRow = AbsoluteCursorRow; - LineSubBuffer.MoveCursor(0, base.CursorColumnShow); LineSubBuffer.Print(commandText, false); UpdateSubBufferCursor(); From 1c7874189ec6ec394c7975505cd09001c17e6781 Mon Sep 17 00:00:00 2001 From: Philipp Caratiola Date: Mon, 11 Feb 2019 11:40:18 +0100 Subject: [PATCH 10/14] fixes artifacting on telnet clients --- src/kOS.Safe/Screen/ScreenSnapshot.cs | 51 ++++++++------------------- 1 file changed, 15 insertions(+), 36 deletions(-) diff --git a/src/kOS.Safe/Screen/ScreenSnapshot.cs b/src/kOS.Safe/Screen/ScreenSnapshot.cs index 10f36366bb..996e9988ec 100644 --- a/src/kOS.Safe/Screen/ScreenSnapshot.cs +++ b/src/kOS.Safe/Screen/ScreenSnapshot.cs @@ -110,6 +110,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 @@ -206,46 +213,18 @@ public string DiffFrom(IScreenSnapShot older) } } - - if (CursorVisible) - { - // Now set the cursor back to the right spot one more time, unless it's already there: - if (trackCursorRow != CursorRow || trackCursorColumn != CursorColumn) - { - output.Append(String.Format("{0}{1}{2}", - (char)UnicodeCommand.TELEPORTCURSOR, - (char)CursorColumn, - (char)CursorRow)); - } - } - else + + // Now set the cursor back to the right spot one more time, unless it's already there: + if (trackCursorRow != CursorRow || trackCursorColumn != CursorColumn) { - // Move the screen to the boundary where it went offscreen, in case the terminal can't hide it - if(CursorRow < 0) - { - output.Append(String.Format("{0}{1}{2}", - (char)UnicodeCommand.TELEPORTCURSOR, - (char)0, - (char)0)); - } - else if(RowCount > 0) - { - output.Append(String.Format("{0}{1}{2}", - (char)UnicodeCommand.TELEPORTCURSOR, - (char)Buffer[0].Length, - (char)RowCount-1)); - } - else - { - output.Append(String.Format("{0}{1}{2}", - (char)UnicodeCommand.TELEPORTCURSOR, - (char)Buffer[0].Length, - (char)RowCount - 1)); - } + output.Append(String.Format("{0}{1}{2}", + (char)UnicodeCommand.TELEPORTCURSOR, + (char)CursorColumn, + (char)CursorRow)); } //Do cursor (un)hiding - if(CursorVisible != older.CursorVisible) + if (CursorVisible != older.CursorVisible) { if(CursorVisible) { From 1278c56f0308eb749399614fba26e6240e540b68 Mon Sep 17 00:00:00 2001 From: Philipp Caratiola Date: Mon, 11 Feb 2019 11:49:12 +0100 Subject: [PATCH 11/14] terminal:put suffixes now autocast to string --- src/kOS.Safe/Encapsulation/TerminalStruct.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/kOS.Safe/Encapsulation/TerminalStruct.cs b/src/kOS.Safe/Encapsulation/TerminalStruct.cs index 5280a86384..8837be5291 100644 --- a/src/kOS.Safe/Encapsulation/TerminalStruct.cs +++ b/src/kOS.Safe/Encapsulation/TerminalStruct.cs @@ -170,11 +170,11 @@ private void InitializeSuffixes() "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,false), + 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), + AddSuffix("PUTLN", new OneArgsSuffix(value => Shared.Screen.Print(value.ToString()), "Put string at current cursor position (with implied newline).")); - AddSuffix("PUTAT", new ThreeArgsSuffix((StringValue text, ScalarValue col, ScalarValue row) => Shared.Screen.PrintAt(text, row, col), + 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.")); } From 9a61f2d0d4ebdc6193e874f1f019d6b21c974432 Mon Sep 17 00:00:00 2001 From: Philipp Caratiola Date: Sat, 28 May 2022 10:05:08 +0200 Subject: [PATCH 12/14] bumped .NET version to 4.5.1 because 4.5 is no longer supported --- src/kOS.Safe.Test/kOS.Safe.Test.csproj | 1 + src/kOS.Safe/kOS.Safe.csproj | 1 + src/kOS/kOS.csproj | 1 + 3 files changed, 3 insertions(+) diff --git a/src/kOS.Safe.Test/kOS.Safe.Test.csproj b/src/kOS.Safe.Test/kOS.Safe.Test.csproj index c21c3b64c4..51fa248ac6 100644 --- a/src/kOS.Safe.Test/kOS.Safe.Test.csproj +++ b/src/kOS.Safe.Test/kOS.Safe.Test.csproj @@ -9,6 +9,7 @@ kOS.Safe.Test kOS.Safe.Test v3.5 + v4.5.1 512 diff --git a/src/kOS.Safe/kOS.Safe.csproj b/src/kOS.Safe/kOS.Safe.csproj index 75c8b1ac56..f218bc37ab 100644 --- a/src/kOS.Safe/kOS.Safe.csproj +++ b/src/kOS.Safe/kOS.Safe.csproj @@ -10,6 +10,7 @@ kOS.Safe kOS.Safe v3.5 + v4.5.1 512 diff --git a/src/kOS/kOS.csproj b/src/kOS/kOS.csproj index 79f48320bd..98f15a19b4 100644 --- a/src/kOS/kOS.csproj +++ b/src/kOS/kOS.csproj @@ -9,6 +9,7 @@ kOS kOS v3.5 + v4.5.1 512 From b0d0764a9e38b2c728a674382d191d05e3cd0ebd Mon Sep 17 00:00:00 2001 From: Philipp Caratiola Date: Sat, 28 May 2022 13:48:55 +0200 Subject: [PATCH 13/14] Added additonal tests/examples for #2394 terminal cursor manipulation. --- kerboscript_tests/terminalcursor/echo.ks | 19 +++++++++++++++++++ .../terminalcursor/loadingbar.ks | 17 +++++++++++++++++ .../scrambledoutput.ks} | 0 3 files changed, 36 insertions(+) create mode 100644 kerboscript_tests/terminalcursor/echo.ks create mode 100644 kerboscript_tests/terminalcursor/loadingbar.ks rename kerboscript_tests/{terminalcursor.ks => terminalcursor/scrambledoutput.ks} (100%) 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.ks b/kerboscript_tests/terminalcursor/scrambledoutput.ks similarity index 100% rename from kerboscript_tests/terminalcursor.ks rename to kerboscript_tests/terminalcursor/scrambledoutput.ks From acc98dc658d7397852f2489294ac7f2da8b5dcdc Mon Sep 17 00:00:00 2001 From: Philipp Caratiola Date: Sat, 28 May 2022 17:03:47 +0200 Subject: [PATCH 14/14] moved CursorVisible to ScreenSnapshot --- src/kOS.Safe/Screen/IScreenBuffer.cs | 3 +-- src/kOS.Safe/Screen/ScreenBuffer.cs | 4 +--- src/kOS.Safe/Screen/ScreenSnapshot.cs | 8 +++----- src/kOS/Screen/TermWindow.cs | 5 ++--- 4 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/kOS.Safe/Screen/IScreenBuffer.cs b/src/kOS.Safe/Screen/IScreenBuffer.cs index 84e825270f..fb9e1633fe 100644 --- a/src/kOS.Safe/Screen/IScreenBuffer.cs +++ b/src/kOS.Safe/Screen/IScreenBuffer.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; namespace kOS.Safe.Screen { @@ -9,7 +9,6 @@ public interface IScreenBuffer double Brightness { get; set; } // double is overkill, but floats don't work in KSP config.xml files. int CursorRowShow { get; } int CursorColumnShow { get; } - bool CursorVisible { get; } int RowCount { get; } int ColumnCount { get; } int AbsoluteCursorRow { get; set; } diff --git a/src/kOS.Safe/Screen/ScreenBuffer.cs b/src/kOS.Safe/Screen/ScreenBuffer.cs index 273adf92fd..3db9068c11 100644 --- a/src/kOS.Safe/Screen/ScreenBuffer.cs +++ b/src/kOS.Safe/Screen/ScreenBuffer.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -45,8 +45,6 @@ public class ScreenBuffer : IScreenBuffer public int RowCount { get; private set; } - public bool CursorVisible { get { var row = CursorRowShow; return row >= 0 && row < RowCount; } } - public int AbsoluteCursorRow { get { return ScrollingOutput.CursorRow; } diff --git a/src/kOS.Safe/Screen/ScreenSnapshot.cs b/src/kOS.Safe/Screen/ScreenSnapshot.cs index 996e9988ec..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,8 +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 bool CursorVisible { 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: @@ -41,7 +41,6 @@ public ScreenSnapShot(IScreenBuffer fromScreen) TopRow = fromScreen.TopRow; CursorColumn = fromScreen.CursorColumnShow; CursorRow = fromScreen.CursorRowShow; - CursorVisible = fromScreen.CursorVisible; RowCount = fromScreen.RowCount; } @@ -62,7 +61,6 @@ public static ScreenSnapShot EmptyScreen(IScreenBuffer fromScreen) newThing.CursorColumn = fromScreen.CursorColumnShow; newThing.CursorRow = fromScreen.CursorRowShow; newThing.RowCount = fromScreen.RowCount; - newThing.CursorVisible = fromScreen.CursorVisible; newThing.Buffer = new List(); for (int i = 0; i < newThing.RowCount ; ++i) newThing.Buffer.Add(new ScreenBufferLine(fromScreen.ColumnCount)); diff --git a/src/kOS/Screen/TermWindow.cs b/src/kOS/Screen/TermWindow.cs index dd494649a2..121cf2a188 100644 --- a/src/kOS/Screen/TermWindow.cs +++ b/src/kOS/Screen/TermWindow.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.Collections.Generic; using kOS.Safe.Persistence; @@ -1030,8 +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.) - // TODO: move row length check into screen if possible, or reintegrate CursorVisible here - screen.CursorVisible && cursorCol < buffer[cursorRow].Length && + mostRecentScreen.CursorVisible && // Only when the CPU has power IsPowered && // Only when expecting input