From d86aa7ca8f9cc75f9c22cd5c2dc39861e4537d1e Mon Sep 17 00:00:00 2001 From: Greg Munn Date: Fri, 17 Apr 2020 09:38:39 -0400 Subject: [PATCH 01/34] Fixes a crash when reflowing narrower and content needs to be pushed out the "top" of the buffer. This appears to happen when we are near the end of the buffer (including scrollback) and content needs to be squished and overflows out the top of the buffer. Possible enhancements in the future might be to increase the scrollback and shift the content but this fixes the immediate crash. ``` XtermSharp.CircularList`1[[XtermSharp.BufferLine, XtermSharp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]].set_Item(Int32,BufferLine) XtermSharp.ReflowNarrower.Rearrange(List`1,Int32) XtermSharp.ReflowNarrower.Reflow(Int32,Int32,Int32,Int32) XtermSharp.Buffer.Reflow(Int32,Int32) XtermSharp.Buffer.Resize(Int32,Int32) XtermSharp.BufferSet.Resize(Int32,Int32) XtermSharp.Terminal.Resize(Int32,Int32) ``` --- Tests/BufferTests/ReflowNarrowerTest.cs | 25 +++++++++++++++++++++++++ Tests/Tests.csproj | 1 + XtermSharp/ReflowNarrower.cs | 6 ++++++ XtermSharp/TerminalOptions.cs | 4 ++-- 4 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 Tests/BufferTests/ReflowNarrowerTest.cs diff --git a/Tests/BufferTests/ReflowNarrowerTest.cs b/Tests/BufferTests/ReflowNarrowerTest.cs new file mode 100644 index 0000000..f438268 --- /dev/null +++ b/Tests/BufferTests/ReflowNarrowerTest.cs @@ -0,0 +1,25 @@ +using System; +using XtermSharp; +using Xunit; + +namespace XtermSharp.Tests.BufferTests { + + public class ReflowNarrowerTests { + [Fact] + public void DoesNotCrashWhenReflowingToTinyWidth () + { + var options = new TerminalOptions () { Cols = 10, Rows = 10 }; + options.Scrollback = 1; + var terminal = new Terminal (null, options); + + terminal.Feed ("1234567890\r\n"); + terminal.Feed ("ABCDEFGH\r\n"); + terminal.Feed ("abcdefghijklmnopqrstxxx\r\n"); + terminal.Feed ("\r\n"); + + // if we resize to a small column width, content is pushed back up and out the top + // of the buffer. Ensure that this does not crash + terminal.Resize (3, 10); + } + } +} diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj index a7b4ced..d09a7be 100644 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -17,5 +17,6 @@ + diff --git a/XtermSharp/ReflowNarrower.cs b/XtermSharp/ReflowNarrower.cs index fcc0783..1245ee8 100644 --- a/XtermSharp/ReflowNarrower.cs +++ b/XtermSharp/ReflowNarrower.cs @@ -162,6 +162,12 @@ void Rearrange (List toInsert, int countToInsert) if (!nextToInsert.IsNull && nextToInsert.Start > originalLineIndex + countInsertedSoFar) { // Insert extra lines here, adjusting i as needed for (int nextI = nextToInsert.Lines.Length - 1; nextI >= 0; nextI--) { + if (i < 0) { + // if we reflow and the content has to be scrolled back past the beginning + // of the buffer then we end up loosing those lines + break; + } + Buffer.Lines [i--] = nextToInsert.Lines [nextI]; } diff --git a/XtermSharp/TerminalOptions.cs b/XtermSharp/TerminalOptions.cs index 2824d59..dbe08a8 100644 --- a/XtermSharp/TerminalOptions.cs +++ b/XtermSharp/TerminalOptions.cs @@ -10,8 +10,8 @@ public class TerminalOptions { public string TermName; public CursorStyle CursorStyle; public bool ScreenReaderMode; - public int? Scrollback { get; } - public int? TabStopWidth { get; } + public int? Scrollback { get; set; } + public int? TabStopWidth { get; set; } public TerminalOptions () { From b602c14efe12a08a0f665f55c90dc4d46f10172f Mon Sep 17 00:00:00 2001 From: Greg Munn Date: Fri, 17 Apr 2020 10:46:31 -0400 Subject: [PATCH 02/34] Backport of DL fixes esctest_DL tests pass --- XtermSharp/BufferLine.cs | 20 ++++++++ XtermSharp/InputHandlers/InputHandler.cs | 28 +---------- .../TerminalCommandExtensions.cs | 13 ++++++ XtermSharp/Terminal.cs | 46 +++++++++++++++++++ 4 files changed, 80 insertions(+), 27 deletions(-) diff --git a/XtermSharp/BufferLine.cs b/XtermSharp/BufferLine.cs index 9257e9d..6bcc839 100644 --- a/XtermSharp/BufferLine.cs +++ b/XtermSharp/BufferLine.cs @@ -124,6 +124,9 @@ public void Resize (int cols, CharData fillCharData) } } + /// + /// Fills the line with fillCharData values + /// public void Fill (CharData fillCharData) { var len = Length; @@ -131,6 +134,15 @@ public void Fill (CharData fillCharData) data [i] = fillCharData; } + /// + /// Fills the line with len fillCharData values from the given atCol + /// + public void Fill (CharData fillCharData, int atCol, int len) + { + for (int i = 0; i < len; i++) + data [atCol + i] = fillCharData; + } + public void CopyFrom (BufferLine line) { if (data.Length != line.Length) @@ -141,6 +153,14 @@ public void CopyFrom (BufferLine line) IsWrapped = line.IsWrapped; } + /// + /// Copies a subrange the given source line into the current line + /// + public void CopyFrom (BufferLine source, int sourceCol, int destCol, int len) + { + Array.Copy (source.data, sourceCol, data, destCol, len); + } + public int GetTrimmedLength () { for (int i = data.Length - 1; i >= 0; --i) diff --git a/XtermSharp/InputHandlers/InputHandler.cs b/XtermSharp/InputHandlers/InputHandler.cs index 61409fc..d127e8d 100644 --- a/XtermSharp/InputHandlers/InputHandler.cs +++ b/XtermSharp/InputHandlers/InputHandler.cs @@ -60,7 +60,7 @@ public InputHandler (Terminal terminal) parser.SetCsiHandler ('J', (pars, collect) => EraseInDisplay (pars)); parser.SetCsiHandler ('K', (pars, collect) => EraseInLine (pars)); parser.SetCsiHandler ('L', (pars, collect) => InsertLines (pars)); - parser.SetCsiHandler ('M', (pars, collect) => DeleteLines (pars)); + parser.SetCsiHandler ('M', (pars, collect) => terminal.csiDL (pars)); parser.SetCsiHandler ('P', (pars, collect) => terminal.csiDCH (pars)); parser.SetCsiHandler ('S', (pars, collect) => ScrollUp (pars)); parser.SetCsiHandler ('T', (pars, collect) => ScrollDown (pars)); @@ -995,32 +995,6 @@ void ScrollUp (int [] pars) terminal.UpdateRange (buffer.ScrollBottom); } - // - // CSI Ps M - // Delete Ps Line(s) (default = 1) (DL). - // - void DeleteLines (int [] pars) - { - var p = Math.Max (pars.Length == 0 ? 1 : pars [0], 1); - var buffer = terminal.Buffer; - var row = buffer.Y + buffer.YBase; - int j; - j = terminal.Rows - 1 - buffer.ScrollBottom; - j = terminal.Rows - 1 + buffer.YBase - j; - var eraseAttr = terminal.EraseAttr (); - while (p-- != 0) { - // test: echo -e '\e[44m\e[1M\e[0m' - // blankLine(true) - xterm/linux behavior - buffer.Lines.Splice (row, 1); - buffer.Lines.Splice (j, 0, buffer.GetBlankLine (eraseAttr)); - } - - // this.maxRange(); - terminal.UpdateRange (buffer.Y); - terminal.UpdateRange (buffer.ScrollBottom); - - } - // // CSI Ps K Erase in Line (EL). // Ps = 0 -> Erase to Right (default). diff --git a/XtermSharp/InputHandlers/TerminalCommandExtensions.cs b/XtermSharp/InputHandlers/TerminalCommandExtensions.cs index 309d030..8315966 100644 --- a/XtermSharp/InputHandlers/TerminalCommandExtensions.cs +++ b/XtermSharp/InputHandlers/TerminalCommandExtensions.cs @@ -81,6 +81,19 @@ public static void csiCUP (this Terminal terminal, params int [] pars) terminal.SetCursor (col, row); } + /// + /// Deletes lines + /// + /// + // CSI Ps M + // Delete Ps Line(s) (default = 1) (DL). + /// + public static void csiDL (this Terminal terminal, int [] pars) + { + var p = Math.Max (pars.Length == 0 ? 1 : pars [0], 1); + terminal.DeleteLines (p); + } + /// /// CSI Ps P /// Delete Ps Character(s) (default = 1) (DCH). diff --git a/XtermSharp/Terminal.cs b/XtermSharp/Terminal.cs index 9bbaa02..ed199ca 100644 --- a/XtermSharp/Terminal.cs +++ b/XtermSharp/Terminal.cs @@ -1017,6 +1017,52 @@ public void DeleteChars (int charsToDelete) UpdateRange (buffer.Y); } + /// + /// Deletes lines + /// + public void DeleteLines (int rowsToDelete) + { + RestrictCursor (); + var buffer = Buffer; + var row = buffer.Y + buffer.YBase; + + int j; + j = buffer.Rows - 1 - buffer.ScrollBottom; + j = buffer.Rows - 1 + buffer.YBase - j; + + var eraseAttr = EraseAttr (); + + if (MarginMode) { + if (buffer.X >= buffer.MarginLeft && buffer.X <= buffer.MarginRight) { + var columnCount = buffer.MarginRight - buffer.MarginLeft + 1; + var rowCount = buffer.ScrollBottom - buffer.ScrollTop; + while (rowsToDelete-- > 0) { + for (int i = 0; i < rowCount; i++) { + var src = buffer.Lines [row + i + 1]; + var dst = buffer.Lines [row + i]; + + if (src != null) { + dst.CopyFrom (src, buffer.MarginLeft, buffer.MarginLeft, columnCount); + } + } + + var last = buffer.Lines [row + rowCount]; + last?.Fill (new CharData (eraseAttr), atCol: buffer.MarginLeft, len: columnCount); + } + } + } else { + if (buffer.Y >= buffer.ScrollTop && buffer.Y <= buffer.ScrollBottom) { + while (rowsToDelete-- > 0) { + buffer.Lines.Splice (row, 1); + buffer.Lines.Splice (j, 0, buffer.GetBlankLine (eraseAttr)); + } + } + } + + UpdateRange (buffer.Y); + UpdateRange (buffer.ScrollBottom); + } + /// /// Inserts columns /// From 8a71f457c0ae4a3f859b4b71bb11cf042d154704 Mon Sep 17 00:00:00 2001 From: Greg Munn Date: Fri, 17 Apr 2020 20:57:16 -0400 Subject: [PATCH 03/34] Add DL unit tests --- Tests/EscTests/DL_Tests.cs | 373 ++++++++++++++++++ .../TerminalCommandExtensions.cs | 2 +- 2 files changed, 374 insertions(+), 1 deletion(-) create mode 100644 Tests/EscTests/DL_Tests.cs diff --git a/Tests/EscTests/DL_Tests.cs b/Tests/EscTests/DL_Tests.cs new file mode 100644 index 0000000..71c0905 --- /dev/null +++ b/Tests/EscTests/DL_Tests.cs @@ -0,0 +1,373 @@ + +using Xunit; +using XtermSharp.CommandExtensions; +using XtermSharp.CsiCommandExtensions; +using System.Text; + +namespace XtermSharp.Tests.EscTests { + /// + /// DL (Delete Line) tests + /// + public class DL_Tests : BaseTerminalTest { + [Fact] + public void DL_DefaultParam() { + //"""DL with no parameter should delete a single line.""" + //# Set up the screen with 0001, 0002, ..., height + //Prepare(); + + //# Delete the second line, moving subsequent lines up. + //esccmd.DL() + + //# Build an array of 0001, 0003, 0004, ..., height + //height = GetScreenSize().height() + //y = 1 + //expected_lines = [] + //for i in xrange(height): + //if y != 2: + //expected_lines.append("%04d" % y) + //y += 1 + + //# The last line should be blank + //expected_lines.append(empty() * 4) + //AssertScreenCharsInRectEqual(Rect(1, 1, 4, height), expected_lines) + var height = Prepare (); + Terminal.csiDL (); + + var sb = new StringBuilder (); + for (int y = 1; y <= height; y++) { + if (y != 2) { + sb.Append ($"{y:d4}"); + } + } + sb.Append (" "); + AssertScreenCharsInRectEqual ((1, 1, 4, height), sb.ToString()); + } + + [Fact] + public void DL_ExplicitParam() { + //"""DL should delete the given number of lines.""" + //# Set up the screen with 0001, 0002, ..., height + //Prepare(); + + //# Delete two lines starting at the second line, moving subsequent lines up. + //esccmd.DL(2) + + //# Build an array of 0001, 0004, ..., height + //height = GetScreenSize().height() + //y = 1 + //expected_lines = [] + //for i in xrange(height): + //if y < 2 or y > 3: + //expected_lines.append("%04d" % y) + //y += 1 + + //# The last two lines should be blank + //expected_lines.append(empty() * 4) + //expected_lines.append(empty() * 4) + + //AssertScreenCharsInRectEqual(Rect(1, 1, 4, height), expected_lines) + var height = Prepare (); + Terminal.csiDL (2); + + var sb = new StringBuilder (); + for (int y = 1; y <= height; y++) { + if (y != 2 && y != 3) { + sb.Append ($"{y:d4}"); + } + } + sb.Append (" "); + sb.Append (" "); + AssertScreenCharsInRectEqual ((1, 1, 4, height), sb.ToString ()); + } + + [Fact] + public void DL_DeleteMoreThanVisible() { + //"""Test passing a too-big parameter to DL.""" + //# Set up the screen with 0001, 0002, ..., height + //Prepare(); + + //# Delete more than the height of the screen. + //height = GetScreenSize().height() + //esccmd.DL(height * 2) + + //# Build an array of 0001 followed by height-1 empty lines. + //y = 1 + //expected_lines = ["0001"] + //for i in xrange(height - 1): + //expected_lines.append(empty() * 4) + + //AssertScreenCharsInRectEqual(Rect(1, 1, 4, height), expected_lines) + var height = Prepare (); + Terminal.csiDL (height * 2); + + var sb = new StringBuilder (); + sb.Append ("0001"); + for (int y = 1; y <= height - 1; y++) { + sb.Append (" "); + } + AssertScreenCharsInRectEqual ((1, 1, 4, height), sb.ToString ()); + } + + [Fact] + public void DL_InScrollRegion() { + //"""Test that DL does the right thing when the cursor is inside the scroll + //region.""" + //PrepareForRegion(); + //esccmd.DECSTBM(2, 4) + //esccmd.CUP(Point(3, 2)) + //esccmd.DL() + //esccmd.DECSTBM() + + //expected_lines = ["abcde", + // "klmno", + // "pqrst", + // empty() * 5, + // "uvwxy"] + //AssertScreenCharsInRectEqual(Rect(1, 1, 5, 5), expected_lines) + PrepareForRegion (); + Terminal.csiDECSTBM (2, 4); + Terminal.csiCUP ((3, 2)); + Terminal.csiDL (); + Terminal.csiDECSTBM (); + AssertScreenCharsInRectEqual ((1, 1, 5, 5), + "abcde" + + "klmno" + + "pqrst" + + " " + + "uvwxy"); + } + + [Fact] + public void DL_OutsideScrollRegion() { + //"""Test that DL does nothing when the cursor is outside the scroll + //region.""" + //PrepareForRegion(); + //esccmd.DECSTBM(2, 4) + //esccmd.CUP(Point(3, 1)) + //esccmd.DL() + //esccmd.DECSTBM() + + //expected_lines = ["abcde", + // "fghij", + // "klmno", + // "pqrst", + // "uvwxy"] + + //AssertScreenCharsInRectEqual(Rect(1, 1, 5, 5), expected_lines) + PrepareForRegion (); + Terminal.csiDECSTBM (2, 4); + Terminal.csiCUP ((3, 1)); + Terminal.csiDL (); + Terminal.csiDECSTBM (); + AssertScreenCharsInRectEqual ((1, 1, 5, 5), + "abcde" + + "fghij" + + "klmno" + + "pqrst" + + "uvwxy"); + } + + [Fact] + public void DL_InLeftRightScrollRegion() { + //"""Test that DL respects left-right margins.""" + //PrepareForRegion(); + //esccmd.DECSET(esccmd.DECLRMM) + //esccmd.DECSLRM(2, 4) + //esccmd.CUP(Point(3, 2)) + //esccmd.DL() + //esccmd.DECRESET(esccmd.DECLRMM) + + //expected_lines = ["abcde", + // "flmnj", + // "kqrso", + // "pvwxt", + // "u" + empty() * 3 + "y"] + + //AssertScreenCharsInRectEqual(Rect(1, 1, 5, 5), expected_lines) + PrepareForRegion (); + Terminal.csiDECSET (CsiCommandCodes.DECLRMM); + Terminal.csiDECSLRM (2, 4); + Terminal.csiCUP ((3, 2)); + Terminal.csiDL (); + Terminal.csiDECRESET (CsiCommandCodes.DECLRMM); + AssertScreenCharsInRectEqual ((1, 1, 5, 5), + "abcde" + + "flmnj" + + "kqrso" + + "pvwxt" + + "u y"); + } + + [Fact] + public void DL_OutsideLeftRightScrollRegion() { + //"""Test that DL does nothing outside a left-right margin.""" + //PrepareForRegion(); + //esccmd.DECSET(esccmd.DECLRMM) + //esccmd.DECSLRM(2, 4) + //esccmd.CUP(Point(1, 2)) + //esccmd.DL() + //esccmd.DECRESET(esccmd.DECLRMM) + + //expected_lines = ["abcde", + // "fghij", + // "klmno", + // "pqrst", + // "uvwxy"] + + //AssertScreenCharsInRectEqual(Rect(1, 1, 5, 5), expected_lines) + PrepareForRegion (); + Terminal.csiDECSET (CsiCommandCodes.DECLRMM); + Terminal.csiDECSLRM (2, 4); + Terminal.csiCUP ((1, 2)); + Terminal.csiDL (); + Terminal.csiDECRESET (CsiCommandCodes.DECLRMM); + AssertScreenCharsInRectEqual ((1, 1, 5, 5), + "abcde" + + "fghij" + + "klmno" + + "pqrst" + + "uvwxy"); + } + + [Fact] + public void DL_InLeftRightAndTopBottomScrollRegion() { + //"""Test that DL respects left-right margins together with top-bottom.""" + //PrepareForRegion(); + //esccmd.DECSET(esccmd.DECLRMM) + //esccmd.DECSLRM(2, 4) + //esccmd.DECSTBM(2, 4) + //esccmd.CUP(Point(3, 2)) + //esccmd.DL() + //esccmd.DECRESET(esccmd.DECLRMM) + //esccmd.DECSTBM() + + //expected_lines = ["abcde", + // "flmnj", + // "kqrso", + // "p" + empty() * 3 + "t", + // "uvwxy"] + + //AssertScreenCharsInRectEqual(Rect(1, 1, 5, 5), expected_lines) + PrepareForRegion (); + Terminal.csiDECSET (CsiCommandCodes.DECLRMM); + Terminal.csiDECSLRM (2, 4); + Terminal.csiDECSTBM (2, 4); + Terminal.csiCUP ((3, 2)); + Terminal.csiDL (); + Terminal.csiDECRESET (CsiCommandCodes.DECLRMM); + Terminal.csiDECSTBM (); + AssertScreenCharsInRectEqual ((1, 1, 5, 5), + "abcde" + + "flmnj" + + "kqrso" + + "p t" + + "uvwxy"); + } + + [Fact] + public void DL_ClearOutLeftRightAndTopBottomScrollRegion() { + //"""Erase the whole scroll region with both kinds of margins.""" + //PrepareForRegion(); + //esccmd.DECSET(esccmd.DECLRMM) + //esccmd.DECSLRM(2, 4) + //esccmd.DECSTBM(2, 4) + //esccmd.CUP(Point(3, 2)) + //esccmd.DL(99) + //esccmd.DECRESET(esccmd.DECLRMM) + //esccmd.DECSTBM() + + //expected_lines = ["abcde", + // "f" + empty() * 3 + "j", + // "k" + empty() * 3 + "o", + // "p" + empty() * 3 + "t", + // "uvwxy"] + + //AssertScreenCharsInRectEqual(Rect(1, 1, 5, 5), expected_lines) + PrepareForRegion (); + Terminal.csiDECSET (CsiCommandCodes.DECLRMM); + Terminal.csiDECSLRM (2, 4); + Terminal.csiDECSTBM (2, 4); + Terminal.csiCUP ((3, 2)); + Terminal.csiDL (99); + Terminal.csiDECRESET (CsiCommandCodes.DECLRMM); + Terminal.csiDECSTBM (); + AssertScreenCharsInRectEqual ((1, 1, 5, 5), + "abcde" + + "f j" + + "k o" + + "p t" + + "uvwxy"); + } + + [Fact] + public void DL_OutsideLeftRightAndTopBottomScrollRegion() { + //"""Test that DL does nothing outside left-right margins together with top-bottom.""" + //PrepareForRegion(); + //esccmd.DECSET(esccmd.DECLRMM) + //esccmd.DECSLRM(2, 4) + //esccmd.DECSTBM(2, 4) + //esccmd.CUP(Point(1, 1)) + //esccmd.DL() + //esccmd.DECRESET(esccmd.DECLRMM) + //esccmd.DECSTBM() + //expected_lines = ["abcde", + // "fghij", + // "klmno", + // "pqrst", + // "uvwxy"] + //AssertScreenCharsInRectEqual(Rect(1, 1, 5, 5), expected_lines) + PrepareForRegion (); + Terminal.csiDECSET (CsiCommandCodes.DECLRMM); + Terminal.csiDECSLRM (2, 4); + Terminal.csiDECSTBM (2, 4); + Terminal.csiCUP ((1, 1)); + Terminal.csiDL (); + Terminal.csiDECRESET (CsiCommandCodes.DECLRMM); + Terminal.csiDECSTBM (); + AssertScreenCharsInRectEqual ((1, 1, 5, 5), + "abcde" + + "fghij" + + "klmno" + + "pqrst" + + "uvwxy"); + } + + int Prepare() { + //"""Fills the screen with 4-char line numbers (0001, 0002, ...) down to the + //last line and puts the cursor on the start of the second line.""" + var height = Terminal.GetScreenSize ().rows; + for (int i = 0; i < height; i++) { + Terminal.csiCUP ((1, i + 1)); + Terminal.Feed ($"{i + 1:d4}"); + } + + Terminal.csiCUP ((1, 2)); + return height; + } + + void PrepareForRegion () + { + //"""Sets the screen up as + //abcde + //fghij + //klmno + //pqrst + //uvwxy + + //With the cursor on the 'h'.""" + + string [] lines = {"abcde", + "fghij", + "klmno", + "pqrst", + "uvwxy" }; + + for (int i = 0; i < lines.Length; i++) { + Terminal.csiCUP ((1, i + 1)); + Terminal.Feed (lines[i]); + } + + Terminal.csiCUP ((3, 2)); + } + } +} diff --git a/XtermSharp/InputHandlers/TerminalCommandExtensions.cs b/XtermSharp/InputHandlers/TerminalCommandExtensions.cs index 8315966..1926703 100644 --- a/XtermSharp/InputHandlers/TerminalCommandExtensions.cs +++ b/XtermSharp/InputHandlers/TerminalCommandExtensions.cs @@ -88,7 +88,7 @@ public static void csiCUP (this Terminal terminal, params int [] pars) // CSI Ps M // Delete Ps Line(s) (default = 1) (DL). /// - public static void csiDL (this Terminal terminal, int [] pars) + public static void csiDL (this Terminal terminal, params int [] pars) { var p = Math.Max (pars.Length == 0 ? 1 : pars [0], 1); terminal.DeleteLines (p); From 3afe79823e09def5b7eb596c5208f7513fe6ca56 Mon Sep 17 00:00:00 2001 From: Greg Munn Date: Fri, 17 Apr 2020 21:36:33 -0400 Subject: [PATCH 04/34] Move InternalsVisibleTo definition to fix build issues on CI --- XtermSharp/AssemblyInfo.cs | 1 + XtermSharp/XtermSharp.csproj | 6 ------ 2 files changed, 1 insertion(+), 6 deletions(-) create mode 100644 XtermSharp/AssemblyInfo.cs diff --git a/XtermSharp/AssemblyInfo.cs b/XtermSharp/AssemblyInfo.cs new file mode 100644 index 0000000..1a8a6e8 --- /dev/null +++ b/XtermSharp/AssemblyInfo.cs @@ -0,0 +1 @@ +[assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute ("Tests")] diff --git a/XtermSharp/XtermSharp.csproj b/XtermSharp/XtermSharp.csproj index 131e9e0..d1e4e40 100644 --- a/XtermSharp/XtermSharp.csproj +++ b/XtermSharp/XtermSharp.csproj @@ -21,10 +21,4 @@ - - - - <_Parameter1>Tests - - From 259b57b1c92cec13e38eaa6f87535f5c67481cbf Mon Sep 17 00:00:00 2001 From: Greg Munn Date: Fri, 17 Apr 2020 21:44:57 -0400 Subject: [PATCH 05/34] Fix build errors --- GuiCsHost/TerminalView.cs | 13 +++++++++---- SimpleTester/SimpleTester.csproj | 4 +--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/GuiCsHost/TerminalView.cs b/GuiCsHost/TerminalView.cs index 03400c8..4b8a3f5 100644 --- a/GuiCsHost/TerminalView.cs +++ b/GuiCsHost/TerminalView.cs @@ -263,7 +263,7 @@ public override void Redraw (Rect region) public override bool MouseEvent (MouseEvent mouseEvent) { - if (terminal.MouseEvents) { + if (terminal.MouseMode.SendMotionEvent()) { var f = mouseEvent.Flags; int button = -1; if (f.HasFlag (MouseFlags.Button1Clicked)) @@ -274,10 +274,10 @@ public override bool MouseEvent (MouseEvent mouseEvent) button = 2; if (button != -1){ - var e = terminal.EncodeButton (button, release: false, shift: false, meta: false, control: false); + var e = terminal.EncodeMouseButton (button, release: false, shift: false, meta: false, control: false); terminal.SendEvent (e, mouseEvent.X, mouseEvent.Y); - if (terminal.MouseSendsRelease) { - e = terminal.EncodeButton (button, release: true, shift: false, meta: false, control: false); + if (terminal.MouseMode.SendButtonRelease()) { + e = terminal.EncodeMouseButton (button, release: true, shift: false, meta: false, control: false); terminal.SendEvent (e, mouseEvent.X, mouseEvent.Y); } return true; @@ -325,6 +325,11 @@ public string WindowCommand (XtermSharp.Terminal source, WindowManipulationComma return null; } + bool ITerminalDelegate.IsProcessTrusted () + { + return true; + } + public override void PositionCursor () { Move (terminal.Buffer.X, terminal.Buffer.Y); diff --git a/SimpleTester/SimpleTester.csproj b/SimpleTester/SimpleTester.csproj index 11c5ab4..d466aed 100644 --- a/SimpleTester/SimpleTester.csproj +++ b/SimpleTester/SimpleTester.csproj @@ -28,9 +28,7 @@ - - ..\packages\NStack.Core.0.11.0\lib\netstandard1.5\NStack.dll - + From 9d76fe8692e59fe225bc0cd3a412a9e219621407 Mon Sep 17 00:00:00 2001 From: Greg Munn Date: Fri, 17 Apr 2020 22:11:17 -0400 Subject: [PATCH 06/34] Move to package reference to fix build errors on CI --- GuiCsHost/GuiCsHost.csproj | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/GuiCsHost/GuiCsHost.csproj b/GuiCsHost/GuiCsHost.csproj index b65f866..6b445be 100644 --- a/GuiCsHost/GuiCsHost.csproj +++ b/GuiCsHost/GuiCsHost.csproj @@ -31,17 +31,10 @@ - - ..\packages\NStack.Core.0.12.0\lib\netstandard1.5\NStack.dll - - - ..\packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll - - - - ..\packages\Terminal.Gui.0.19.0\lib\net461\Terminal.Gui.dll - + + + From 0e2fd2facd223e5809abc10ccddb2b533ddb3e52 Mon Sep 17 00:00:00 2001 From: Greg Munn Date: Fri, 17 Apr 2020 22:21:28 -0400 Subject: [PATCH 07/34] Bump tests targetframework for CI --- Tests/Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj index d09a7be..ebe5a95 100644 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -1,7 +1,7 @@ - netcoreapp2.1 + netcoreapp3.1 false From af2a292a276f8850cb863a6df57bf1fdfea7d88d Mon Sep 17 00:00:00 2001 From: Greg Munn Date: Fri, 17 Apr 2020 22:32:56 -0400 Subject: [PATCH 08/34] Add yaml to build and test --- .github/workflows/build-and-test.yml | 39 ++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 .github/workflows/build-and-test.yml diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml new file mode 100644 index 0000000..ace9b2a --- /dev/null +++ b/.github/workflows/build-and-test.yml @@ -0,0 +1,39 @@ +name: CI + +# Controls when the action will run. Triggers the workflow on push or pull request +# events but only for the master branch +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + # This workflow contains a single job called "build" + build: + # The type of runner that the job will run on + runs-on: macos-latest + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v2 + + # Build helper + - name: Build ptylib + run: | + cd helper + cc -shared pty.c -shared -o libpty.dylib + + # Restore packages + - name: Restore + run: msbuild /t:restore + + # Build + - name: Build + run: msbuild /t:build + + # Test + - name: Test + run: dotnet test Tests/Tests.csproj From d023043d02f42ea69319a7fbba3e29fcf9f9e0fe Mon Sep 17 00:00:00 2001 From: Miguel de Icaza Date: Mon, 20 Apr 2020 11:00:30 -0400 Subject: [PATCH 09/34] Use 10.14 instead of 10.13 --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 319fcb7..86b2e7b 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -2,7 +2,7 @@ jobs: - job: macOS pool: - vmImage: 'macOS-10.13' + vmImage: 'macOS-10.14' steps: - script: | /bin/bash -c "sudo $AGENT_HOMEDIRECTORY/scripts/select-xamarin-sdk.sh 5_16_0_0" From 397264ef82eeced975310ed9ec1bbc9dea5332c5 Mon Sep 17 00:00:00 2001 From: Miguel de Icaza Date: Mon, 20 Apr 2020 11:12:23 -0400 Subject: [PATCH 10/34] Attempt to install .NET Core 3.1 --- azure-pipelines.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 86b2e7b..6d6f0e3 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -2,8 +2,12 @@ jobs: - job: macOS pool: - vmImage: 'macOS-10.14' + vmImage: 'macOS-10.15' steps: + - task: UseDotNet@2 + inputs: + version: '3.1.x' + - script: | /bin/bash -c "sudo $AGENT_HOMEDIRECTORY/scripts/select-xamarin-sdk.sh 5_16_0_0" SYMLINK=5_16_0_0 From 5d8ecbe338d9237488f884d246c896ed2b1e5178 Mon Sep 17 00:00:00 2001 From: Miguel de Icaza Date: Mon, 20 Apr 2020 15:04:49 -0400 Subject: [PATCH 11/34] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 6d6f0e3..aedc5d9 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -18,7 +18,7 @@ jobs: - template: templates/steps.yml # Template reference - script: | - msbuild MacTerminal/MacTerminal.csproj + /Library/Frameworks/Mono.framework/Version/Current/Commands/msbuild MacTerminal/MacTerminal.csproj displayName: Build managed code and Mac Library - script: | cd helper From e16ecac2b889d1a04ebbff48da93c968458f923f Mon Sep 17 00:00:00 2001 From: Greg Munn Date: Tue, 28 Apr 2020 15:19:23 -0400 Subject: [PATCH 12/34] Add Github CI badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b7cc25a..bb0ddcf 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ XtermSharp ---------- +![Build Status](https://github.com/migueldeicaza/XtermSharp/workflows/build-and-test/badge.svg) [![Build Status](https://migueldeicaza.visualstudio.com/XtermSharp/_apis/build/status/XtermSharp-Mac-CI?branchName=master)](https://migueldeicaza.visualstudio.com/XtermSharp/_build/latest?definitionId=9&branchName=master) XtermSharpGuiXtermSharpMac From 6773620177516ec928d569ca2921db6d05779019 Mon Sep 17 00:00:00 2001 From: Greg Munn Date: Tue, 28 Apr 2020 15:22:29 -0400 Subject: [PATCH 13/34] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bb0ddcf..ee5d5ca 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ XtermSharp ---------- -![Build Status](https://github.com/migueldeicaza/XtermSharp/workflows/build-and-test/badge.svg) +![Build Status](https://github.com/migueldeicaza/XtermSharp/workflows/CI/badge.svg) [![Build Status](https://migueldeicaza.visualstudio.com/XtermSharp/_apis/build/status/XtermSharp-Mac-CI?branchName=master)](https://migueldeicaza.visualstudio.com/XtermSharp/_build/latest?definitionId=9&branchName=master) XtermSharpGuiXtermSharpMac From 252509147b41ead3b9b9acc2f14225e52a3f0d8f Mon Sep 17 00:00:00 2001 From: Greg Munn Date: Wed, 29 Apr 2020 10:50:16 -0400 Subject: [PATCH 14/34] Fixes a crash when selecting word or expression XtermSharp.Buffer.GetChar(Int32,Int32) XtermSharp.SelectionService.SelectWordOrExpression(Int32,Int32) XtermSharp.Mac.TerminalView.MouseUp(NSEvent) --- Tests/BufferTests/SelectionTests.cs | 32 +++++++++++++++++++++++++++++ XtermSharp/Buffer.cs | 3 +++ XtermSharp/SelectionService.cs | 7 ++++++- 3 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 Tests/BufferTests/SelectionTests.cs diff --git a/Tests/BufferTests/SelectionTests.cs b/Tests/BufferTests/SelectionTests.cs new file mode 100644 index 0000000..3260ef1 --- /dev/null +++ b/Tests/BufferTests/SelectionTests.cs @@ -0,0 +1,32 @@ +using Xunit; + +namespace XtermSharp.Tests.BufferTests { + public class SelectionTests { + [Fact] + public void DoesNotCrashWhenSelectingWordOrExpressionOutsideColumnRange () + { + var terminal = new Terminal (null, new TerminalOptions { Rows = 10, Cols = 10 }); + var selection = new SelectionService (terminal); + + terminal.Feed ("1234567890"); + + // depending on the size of terminal view, there might be a space near the margin where the user + // clicks which might result in a col or row outside the bounds of terminal, + selection.SelectWordOrExpression (-1, 0); + selection.SelectWordOrExpression (11, 0); + } + + [Fact] + public void DoesNotCrashWhenSelectingWordOrExpressionOutsideRowRange () + { + var terminal = new Terminal (null, new TerminalOptions { Rows = 10, Cols = 10, Scrollback = 0 }); + var selection = new SelectionService (terminal); + + terminal.Feed ("1234567890"); + + // depending on the size of terminal view, there might be a space near the margin where the user + // clicks which might result in a col or row outside the bounds of terminal, + selection.SelectWordOrExpression (0, -1); + } + } +} diff --git a/XtermSharp/Buffer.cs b/XtermSharp/Buffer.cs index d624bae..9bea516 100644 --- a/XtermSharp/Buffer.cs +++ b/XtermSharp/Buffer.cs @@ -124,6 +124,9 @@ public CharData GetChar (int col, int row) return CharData.Null; } + if (col >= bufferRow.Length || col < 0) + return CharData.Null; + return bufferRow [col]; } diff --git a/XtermSharp/SelectionService.cs b/XtermSharp/SelectionService.cs index 32b5300..6be6eea 100644 --- a/XtermSharp/SelectionService.cs +++ b/XtermSharp/SelectionService.cs @@ -166,9 +166,14 @@ public void SelectRow (int row) /// public void SelectWordOrExpression(int col, int row) { - row += terminal.Buffer.YDisp; var buffer = terminal.Buffer; + // ensure the bounds are inside the terminal. + row = Math.Max (row, 0); + col = Math.Max (Math.Min (col, terminal.Buffer.Cols), 0); + + row += buffer.YDisp; + Func isLetterOrChar = (cd) => { if (cd.IsNullChar ()) return false; From 635cffcf94006d1d71be30c9cb513a96730a766c Mon Sep 17 00:00:00 2001 From: Greg Munn Date: Wed, 29 Apr 2020 13:23:44 -0400 Subject: [PATCH 15/34] Cols are user visible based, not zero based --- XtermSharp/SelectionService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/XtermSharp/SelectionService.cs b/XtermSharp/SelectionService.cs index 6be6eea..b928923 100644 --- a/XtermSharp/SelectionService.cs +++ b/XtermSharp/SelectionService.cs @@ -170,7 +170,7 @@ public void SelectWordOrExpression(int col, int row) // ensure the bounds are inside the terminal. row = Math.Max (row, 0); - col = Math.Max (Math.Min (col, terminal.Buffer.Cols), 0); + col = Math.Max (Math.Min (col, terminal.Buffer.Cols - 1), 0); row += buffer.YDisp; From cab6deca9292a42ee401216d58d74a17d4f15998 Mon Sep 17 00:00:00 2001 From: Matt Ward Date: Mon, 18 May 2020 16:29:49 +0100 Subject: [PATCH 16/34] Fix IndexOutOfRangeException processing input Backport code changes made to SwiftTerm which prevent accessing an array index outside its range when processing escape codes for foreground and background colours. Code backported: https://github.com/migueldeicaza/SwiftTerm/blob/d5ab249b56c5a95ba4c505dec8f652ff436e5124/Sources/SwiftTerm/Terminal.swift#L2715-L2791 Error: XtermSharp.InputHandler.CharAttributes(Int32[]) XtermSharp.InputHandler.<>c__XXX.<.ctor>b__XXX(Int32[],String) XtermSharp.EscapeSequenceParser.Parse(Byte*,Int32) XtermSharp.InputHandler.Parse(Byte[],Int32) XtermSharp.Terminal.Feed(Byte[],Int32) XtermSharp.Mac.TerminalView.Feed(Byte[],Int32) XtermSharp.Mac.ProcessTerminalView.ProcessOnData(Byte[]) XtermSharp.Mac.Process.SendOnData(Byte[]) XtermSharp.Mac.LocalProcess.ChildProcessRead(DispatchData,Int32) CoreFoundation.DispatchIO.Trampoline_DispatchReadWriteHandler(IntPtr,IntPtr,Int32) --- XtermSharp/InputHandlers/InputHandler.cs | 86 +++++++++++++++--------- 1 file changed, 56 insertions(+), 30 deletions(-) diff --git a/XtermSharp/InputHandlers/InputHandler.cs b/XtermSharp/InputHandlers/InputHandler.cs index d127e8d..20e3736 100644 --- a/XtermSharp/InputHandlers/InputHandler.cs +++ b/XtermSharp/InputHandlers/InputHandler.cs @@ -669,13 +669,13 @@ void CharAttributes (int [] pars) return; } - var l = pars.Length; + var parCount = pars.Length; var flags = (FLAGS)(terminal.CurAttr >> 18); var fg = (terminal.CurAttr >> 9) & 0x1ff; var bg = terminal.CurAttr & 0x1ff; var def = CharData.DefaultAttr; - for (var i = 0; i < l; i++) { + for (var i = 0; i < parCount; i++) { int p = pars [i]; if (p >= 30 && p <= 37) { // fg color 8 @@ -748,36 +748,62 @@ void CharAttributes (int [] pars) // reset bg bg = CharData.DefaultAttr & 0x1ff; } else if (p == 38) { - // fg color 256 - if (pars [i + 1] == 2) { - i += 2; - fg = terminal.MatchColor ( - pars [i] & 0xff, - pars [i + 1] & 0xff, - pars [i + 2] & 0xff); - if (fg == -1) - fg = 0x1ff; - i += 2; - } else if (pars [i + 1] == 5) { - i += 2; - p = pars [i] & 0xff; - fg = p; + if (i + 1 < parCount) { + // fg color 256 + if (pars [i + 1] == 2) { + // Well this is a problem, if there are 3 arguments, expect R/G/B, if there are + // more than 3, skip the first that would be the colorspace + if (i + 5 < parCount) { + i += 1; + } + if (i + 4 < parCount) { + fg = terminal.MatchColor ( + pars [i + 2] & 0xff, + pars [i + 3] & 0xff, + pars [i + 4] & 0xff); + if (fg == -1) + fg = 0x1ff; + } + // Given the historical disagreement that was caused by an ambiguous spec, + // we eat all the remaining parameters. + i = parCount; + } else if (pars [i + 1] == 5) { + if (i + 2 < parCount) { + p = pars [i + 2] & 0xff; + fg = p; + i += 1; + } + i += 1; + } } } else if (p == 48) { - // bg color 256 - if (pars [i + 1] == 2) { - i += 2; - bg = terminal.MatchColor ( - pars [i] & 0xff, - pars [i + 1] & 0xff, - pars [i + 2] & 0xff); - if (bg == -1) - bg = 0x1ff; - i += 2; - } else if (pars [i + 1] == 5) { - i += 2; - p = pars [i] & 0xff; - bg = p; + if (i + 1 < parCount) { + // bg color 256 + if (pars [i + 1] == 2) { + // Well this is a problem, if there are 3 arguments, expect R/G/B, if there are + // more than 3, skip the first that would be the colorspace + if (i + 5 < parCount) { + i += 1; + } + if (i + 4 < parCount) { + bg = terminal.MatchColor ( + pars [i + 2] & 0xff, + pars [i + 3] & 0xff, + pars [i + 4] & 0xff); + if (bg == -1) + bg = 0x1ff; + } + // Given the historical disagreement that was caused by an ambiguous spec, + // we eat all the remaining parameters. + i = parCount; + } else if (pars [i + 1] == 5) { + if (i + 2 < parCount) { + p = pars [i + 2] & 0xff; + bg = p; + i += 1; + } + i += 1; + } } } else if (p == 100) { // reset fg/bg From 4fcc03f8f34b0df6d48d6dc8c1c59d6898a2955b Mon Sep 17 00:00:00 2001 From: nosami Date: Mon, 24 May 2021 08:45:11 +0100 Subject: [PATCH 17/34] Use custom Point struct to remove System.Drawing The System.Drawing reference is problematic in Unified Mac apps. As we are only using this reference for the Point struct, it seemed prudent to just reimplement this. --- XtermSharp.Mac/TerminalView.cs | 2 +- XtermSharp/Point.cs | 28 ++++++++++++++++++++++++++++ XtermSharp/SearchService.cs | 1 - XtermSharp/SelectionService.cs | 1 - 4 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 XtermSharp/Point.cs diff --git a/XtermSharp.Mac/TerminalView.cs b/XtermSharp.Mac/TerminalView.cs index c287dc6..ff7a2af 100644 --- a/XtermSharp.Mac/TerminalView.cs +++ b/XtermSharp.Mac/TerminalView.cs @@ -410,7 +410,7 @@ void FullBufferUpdate () void UpdateCursorPosition () { - caret.Pos = new System.Drawing.Point (terminal.Buffer.X, terminal.Buffer.Y - terminal.Buffer.YDisp + terminal.Buffer.YBase); + caret.Pos = new Point (terminal.Buffer.X, terminal.Buffer.Y - terminal.Buffer.YDisp + terminal.Buffer.YBase); } void UpdateDisplay () diff --git a/XtermSharp/Point.cs b/XtermSharp/Point.cs new file mode 100644 index 0000000..672fee7 --- /dev/null +++ b/XtermSharp/Point.cs @@ -0,0 +1,28 @@ +namespace XtermSharp +{ + public struct Point + { + public static readonly Point Empty; + + public Point(int x, int y) + { + X = x; + Y = y; + } + + public int X { get; set; } + public int Y { get; set; } + + public bool IsEmpty => X == 0 && Y == 0; + + public static bool operator ==(Point left, Point right) => left.X == right.X && left.Y == right.Y; + + public static bool operator !=(Point left, Point right) => !(left == right); + + public override bool Equals(object obj) => obj is Point point && point.X == X && point.Y == Y; + + public override int GetHashCode() => X.GetHashCode() * 327 + Y.GetHashCode(); + + public override string ToString() => "{X=" + X.ToString() + ",Y=" + Y.ToString() + "}"; + } +} diff --git a/XtermSharp/SearchService.cs b/XtermSharp/SearchService.cs index 337c5fb..3fff122 100644 --- a/XtermSharp/SearchService.cs +++ b/XtermSharp/SearchService.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Drawing; using System.Text; namespace XtermSharp { diff --git a/XtermSharp/SelectionService.cs b/XtermSharp/SelectionService.cs index b928923..29a5253 100644 --- a/XtermSharp/SelectionService.cs +++ b/XtermSharp/SelectionService.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Drawing; using System.Text; namespace XtermSharp { From 7d159efd8fc70d01f45c81f25bdc4d39f814a20b Mon Sep 17 00:00:00 2001 From: Lluis Sanchez Date: Mon, 6 Sep 2021 13:14:09 +0200 Subject: [PATCH 18/34] Build libtpy as a fat library So that it can be loaded on an M1 Mac --- helper/Makefile | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/helper/Makefile b/helper/Makefile index 5c89054..ee44b25 100644 --- a/helper/Makefile +++ b/helper/Makefile @@ -1,8 +1,17 @@ all: libtpy.dylib -libpty.dylib: pty.c - cc -shared pty.c -shared -o libpty.dylib +ifeq ($(OS),Windows_NT) + UNAME := Windows +else + UNAME := $(shell uname -s) +endif +libpty.dylib: pty.c + @if [[ "$(UNAME)" == "Darwin" ]]; then \ + cc -arch arm64 -arch x86_64 -shared pty.c -shared -o libpty.dylib; \ + else \ + cc -shared pty.c -shared -o libpty.dylib; \ + fi clean: rm *dylib From c2825d6d5ad6e85c5e1d83392d1f1c17dfafd3d7 Mon Sep 17 00:00:00 2001 From: Greg Munn Date: Thu, 7 Oct 2021 15:16:02 -0400 Subject: [PATCH 19/34] Convert test project to net6 --- MacTerminal/Info.plist | 2 +- MacTerminal/MacTerminal.csproj | 100 ++++------------------ XtermSharp.Mac/Properties/AssemblyInfo.cs | 2 +- XtermSharp.Mac/XtermSharp.Mac.csproj | 69 ++------------- 4 files changed, 24 insertions(+), 149 deletions(-) diff --git a/MacTerminal/Info.plist b/MacTerminal/Info.plist index af3a79f..f318e06 100644 --- a/MacTerminal/Info.plist +++ b/MacTerminal/Info.plist @@ -11,7 +11,7 @@ CFBundleVersion 1 LSMinimumSystemVersion - 10.12 + 10.14 CFBundleDevelopmentRegion en CFBundleInfoDictionaryVersion diff --git a/MacTerminal/MacTerminal.csproj b/MacTerminal/MacTerminal.csproj index d09208e..202cb30 100644 --- a/MacTerminal/MacTerminal.csproj +++ b/MacTerminal/MacTerminal.csproj @@ -1,91 +1,21 @@ - - + - Debug - AnyCPU - {1F7FAA67-E10C-4C71-8DA9-91A2A08E5F46} - {A3F8F2AB-B479-4A4A-A458-A89E7DC349F1};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + net6.0-macos Exe - MacTerminal - MacTerminal - v2.0 - Xamarin.Mac - Resources - - - true - full - false - bin\Debug - DEBUG; - prompt - 4 - false - Mac Developer - false - false - false - true - true - true - - - - - - pdbonly - true - bin\Release - - prompt - 4 - false - true - false - true - true - true + osx-x64 + false SdkOnly - - + + 10.14 + Resources + true + MacTerminal + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ViewController.cs - - - - - + {218EB800-B5D6-46C3-8113-8DB312AE0C64} @@ -96,8 +26,8 @@ XtermSharp.Mac - - - + + + - \ No newline at end of file + diff --git a/XtermSharp.Mac/Properties/AssemblyInfo.cs b/XtermSharp.Mac/Properties/AssemblyInfo.cs index a36e50c..4c202f1 100644 --- a/XtermSharp.Mac/Properties/AssemblyInfo.cs +++ b/XtermSharp.Mac/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ // The form "{Major}.{Minor}.*" will automatically update the build and revision, // and "{Major}.{Minor}.{Build}.*" will update just the revision. -[assembly: AssemblyVersion ("1.0.*")] +[assembly: AssemblyVersion ("1.0.0.0")] // The following attributes are used to specify the signing key for the assembly, // if desired. See the Mono documentation for more information about signing. diff --git a/XtermSharp.Mac/XtermSharp.Mac.csproj b/XtermSharp.Mac/XtermSharp.Mac.csproj index 7cc7ea8..524a85f 100644 --- a/XtermSharp.Mac/XtermSharp.Mac.csproj +++ b/XtermSharp.Mac/XtermSharp.Mac.csproj @@ -1,76 +1,21 @@ - - + - Debug - AnyCPU - {1058A595-AA72-4BF3-B6F1-06F9F4FED6DC} - {A3F8F2AB-B479-4A4A-A458-A89E7DC349F1};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + net6.0-macos Library - XtermSharp.Mac - XtermSharp.Mac - v2.0 - Xamarin.Mac + Resources - - - true - full - false - bin\Debug - DEBUG; - prompt - 4 - false - false - false - false - false - - - - None - true - - - true - bin\Release - - prompt - 4 - false - false - false - false - false - - - - None true + XtermSharp.Mac + - - - - - - - - - - - - - - - + {218EB800-B5D6-46C3-8113-8DB312AE0C64} XtermSharp - - + \ No newline at end of file From 93019c5645e82e4332b0042f74e6c78e30f7e928 Mon Sep 17 00:00:00 2001 From: Greg Munn Date: Wed, 12 Jan 2022 10:37:15 -0500 Subject: [PATCH 20/34] Fix --- XtermSharp.Mac/Properties/AssemblyInfo.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/XtermSharp.Mac/Properties/AssemblyInfo.cs b/XtermSharp.Mac/Properties/AssemblyInfo.cs index 4c202f1..569dff9 100644 --- a/XtermSharp.Mac/Properties/AssemblyInfo.cs +++ b/XtermSharp.Mac/Properties/AssemblyInfo.cs @@ -4,20 +4,20 @@ // Information about this assembly is defined by the following attributes. // Change them to the values specific to your project. -[assembly: AssemblyTitle ("XtermSharp.Mac")] -[assembly: AssemblyDescription ("")] -[assembly: AssemblyConfiguration ("")] -[assembly: AssemblyCompany ("")] -[assembly: AssemblyProduct ("")] -[assembly: AssemblyCopyright ("${AuthorCopyright}")] -[assembly: AssemblyTrademark ("")] -[assembly: AssemblyCulture ("")] +//[assembly: AssemblyTitle ("XtermSharp.Mac")] +//[assembly: AssemblyDescription ("")] +//[assembly: AssemblyConfiguration ("")] +//[assembly: AssemblyCompany ("")] +//[assembly: AssemblyProduct ("")] +//[assembly: AssemblyCopyright ("${AuthorCopyright}")] +//[assembly: AssemblyTrademark ("")] +//[assembly: AssemblyCulture ("")] // The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". // The form "{Major}.{Minor}.*" will automatically update the build and revision, // and "{Major}.{Minor}.{Build}.*" will update just the revision. -[assembly: AssemblyVersion ("1.0.0.0")] +//[assembly: AssemblyVersion ("1.0.0.0")] // The following attributes are used to specify the signing key for the assembly, // if desired. See the Mono documentation for more information about signing. From 861e649149f114e1b0838cd7f227d0f92b067c8e Mon Sep 17 00:00:00 2001 From: Greg Munn Date: Wed, 12 Jan 2022 10:55:41 -0500 Subject: [PATCH 21/34] arm --- MacTerminal/MacTerminal.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MacTerminal/MacTerminal.csproj b/MacTerminal/MacTerminal.csproj index 202cb30..802ce1f 100644 --- a/MacTerminal/MacTerminal.csproj +++ b/MacTerminal/MacTerminal.csproj @@ -2,7 +2,7 @@ net6.0-macos Exe - osx-x64 + osx-arm64 false SdkOnly From 83186d0ad9b60c6f6a9867560332b7524f97a6f4 Mon Sep 17 00:00:00 2001 From: Greg Munn Date: Wed, 12 Jan 2022 15:12:46 -0500 Subject: [PATCH 22/34] Fix passing arg and env arguments to the native helper in .NET6 --- XtermSharp/Pty.cs | 67 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 65 insertions(+), 2 deletions(-) diff --git a/XtermSharp/Pty.cs b/XtermSharp/Pty.cs index 053d3bd..dcbb432 100644 --- a/XtermSharp/Pty.cs +++ b/XtermSharp/Pty.cs @@ -1,6 +1,8 @@ using System; using System.Runtime.InteropServices; using System.IO; +using System.Diagnostics; +using System.Text; namespace XtermSharp { [StructLayout(LayoutKind.Sequential)] @@ -19,7 +21,7 @@ public class Pty { extern static int execve (string process, string [] args, string [] env); [DllImport ("libpty.dylib", EntryPoint="fork_and_exec")] - extern static int HeavyFork (string process, string [] args, string [] env, out int master, UnixWindowSize winSize); + extern static unsafe int HeavyFork (string process, byte** args, byte** env, out int master, UnixWindowSize winSize); static bool HeavyDuty = true; /// @@ -34,7 +36,7 @@ public class Pty { public static int ForkAndExec (string programName, string [] args, string [] env, out int master, UnixWindowSize winSize) { if (HeavyDuty) { - return HeavyFork (programName, args, env, out master, winSize); + return DoHeavyFork (programName, args, env, out master, winSize); } else { var pid = forkpty (out master, IntPtr.Zero, IntPtr.Zero, ref winSize); if (pid < 0) @@ -47,6 +49,67 @@ public static int ForkAndExec (string programName, string [] args, string [] env } } + + static unsafe int DoHeavyFork (string programName, string [] args, string [] env, out int master, UnixWindowSize winSize) + { + byte** argvPtr = null, envpPtr = null; + int result = -1; + try { + AllocNullTerminatedArray (args, ref argvPtr); + AllocNullTerminatedArray (env, ref envpPtr); + result = HeavyFork (programName, argvPtr, envpPtr, out master, winSize); + return result == 0 ? 0 : Marshal.GetLastWin32Error (); + } finally { + FreeArray (argvPtr, args.Length); + FreeArray (envpPtr, env.Length); + } + } + + private static unsafe void AllocNullTerminatedArray (string [] arr, ref byte** arrPtr) + { + int arrLength = arr.Length + 1; // +1 is for null termination + + // Allocate the unmanaged array to hold each string pointer. + // It needs to have an extra element to null terminate the array. + arrPtr = (byte**)Marshal.AllocHGlobal (sizeof (IntPtr) * arrLength); + Debug.Assert (arrPtr != null); + + // Zero the memory so that if any of the individual string allocations fails, + // we can loop through the array to free any that succeeded. + // The last element will remain null. + for (int i = 0; i < arrLength; i++) { + arrPtr [i] = null; + } + + // Now copy each string to unmanaged memory referenced from the array. + // We need the data to be an unmanaged, null-terminated array of UTF8-encoded bytes. + for (int i = 0; i < arr.Length; i++) { + byte [] byteArr = Encoding.UTF8.GetBytes (arr [i]); + + arrPtr [i] = (byte*)Marshal.AllocHGlobal (byteArr.Length + 1); //+1 for null termination + Debug.Assert (arrPtr [i] != null); + + Marshal.Copy (byteArr, 0, (IntPtr)arrPtr [i], byteArr.Length); // copy over the data from the managed byte array + arrPtr [i] [byteArr.Length] = (byte)'\0'; // null terminate + } + } + + private static unsafe void FreeArray (byte** arr, int length) + { + if (arr != null) { + // Free each element of the array + for (int i = 0; i < length; i++) { + if (arr [i] != null) { + Marshal.FreeHGlobal ((IntPtr)arr [i]); + arr [i] = null; + } + } + + // And then the array itself + Marshal.FreeHGlobal ((IntPtr)arr); + } + } + [DllImport ("libc", SetLastError = true)] extern static int ioctl (int fd, long cmd, ref UnixWindowSize WinSz); From f1aebb4369bbf684ab161bdf41ae8731064069d5 Mon Sep 17 00:00:00 2001 From: Greg Munn Date: Wed, 12 Jan 2022 15:13:36 -0500 Subject: [PATCH 23/34] If execve fails, call _exit() Calling exit() will disable the CLR diagnostic pipes --- helper/pty.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helper/pty.c b/helper/pty.c index 81dcea8..f8fdfb2 100644 --- a/helper/pty.c +++ b/helper/pty.c @@ -15,7 +15,7 @@ fork_and_exec (const char *name, char *const args[], char *const env[], int *mas if (pid == 0) { execve (name, args, env); - exit (1); + _exit (1); } return pid; } From 8e15e7194509d68871659bede0e99e292669ddc2 Mon Sep 17 00:00:00 2001 From: Greg Munn Date: Fri, 14 Jan 2022 09:44:58 -0500 Subject: [PATCH 24/34] Fix make all target --- helper/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helper/Makefile b/helper/Makefile index ee44b25..cb73765 100644 --- a/helper/Makefile +++ b/helper/Makefile @@ -1,4 +1,4 @@ -all: libtpy.dylib +all: libpty.dylib ifeq ($(OS),Windows_NT) UNAME := Windows From 74409ccdd6c9e8fb807e655eb4a31dab4a98d232 Mon Sep 17 00:00:00 2001 From: Greg Munn Date: Fri, 14 Jan 2022 09:46:33 -0500 Subject: [PATCH 25/34] Clean up marshaling of winsize On arm64, MAC_TIOCSWINSZ gets padded out and corrupts the winsize values and we end up with incorrect columns and rows. --- XtermSharp.Mac/LocalProcess.cs | 4 ++-- XtermSharp/Pty.cs | 22 ++++++++++++---------- XtermSharp/Terminal.cs | 2 +- helper/pty.c | 9 ++++++++- 4 files changed, 23 insertions(+), 14 deletions(-) diff --git a/XtermSharp.Mac/LocalProcess.cs b/XtermSharp.Mac/LocalProcess.cs index bb95176..5240033 100644 --- a/XtermSharp.Mac/LocalProcess.cs +++ b/XtermSharp.Mac/LocalProcess.cs @@ -28,7 +28,7 @@ public class LocalProcess : Process { /// /// Launches the shell /// - public virtual void Start (string shellPath = "/bin/bash", string [] args = null, string [] env = null) + public virtual void Start (string shellPath = "/bin/zsh", string [] args = null, string [] env = null) { OnStart (); @@ -36,7 +36,7 @@ public virtual void Start (string shellPath = "/bin/bash", string [] args = null shellArgs [0] = shellPath; args?.CopyTo (shellArgs, 1); - ProcessId = Pty.ForkAndExec (shellPath, shellArgs, env ?? Terminal.GetEnvironmentVariables (), out shellFileDescriptor, initialSize); + ProcessId = Pty.ForkAndExec (shellPath, shellArgs, env ?? Terminal.GetEnvironmentVariables (), ref shellFileDescriptor, initialSize); DispatchIO.Read (shellFileDescriptor, (nuint)readBuffer.Length, DispatchQueue.CurrentQueue, ChildProcessRead); } diff --git a/XtermSharp/Pty.cs b/XtermSharp/Pty.cs index dcbb432..bc112fa 100644 --- a/XtermSharp/Pty.cs +++ b/XtermSharp/Pty.cs @@ -12,7 +12,7 @@ public struct UnixWindowSize { public class Pty { [DllImport ("util")] - extern static int forkpty (out int master, IntPtr dataReturn, IntPtr termios, ref UnixWindowSize WinSz); + extern static int forkpty (ref int master, IntPtr dataReturn, IntPtr termios, ref UnixWindowSize WinSz); [DllImport ("libc")] extern static int execv (string process, string [] args); @@ -21,7 +21,7 @@ public class Pty { extern static int execve (string process, string [] args, string [] env); [DllImport ("libpty.dylib", EntryPoint="fork_and_exec")] - extern static unsafe int HeavyFork (string process, byte** args, byte** env, out int master, UnixWindowSize winSize); + extern static unsafe int HeavyFork (string process, byte** args, byte** env, ref int master, ref UnixWindowSize winSize); static bool HeavyDuty = true; /// @@ -33,12 +33,12 @@ public class Pty { /// The file descriptor connected to the input and output of the child process /// Desired window size /// - public static int ForkAndExec (string programName, string [] args, string [] env, out int master, UnixWindowSize winSize) + public static int ForkAndExec (string programName, string [] args, string [] env, ref int master, UnixWindowSize winSize) { if (HeavyDuty) { - return DoHeavyFork (programName, args, env, out master, winSize); + return DoHeavyFork (programName, args, env, ref master, ref winSize); } else { - var pid = forkpty (out master, IntPtr.Zero, IntPtr.Zero, ref winSize); + var pid = forkpty (ref master, IntPtr.Zero, IntPtr.Zero, ref winSize); if (pid < 0) throw new Exception ("Could not create Pty"); @@ -50,14 +50,14 @@ public static int ForkAndExec (string programName, string [] args, string [] env } - static unsafe int DoHeavyFork (string programName, string [] args, string [] env, out int master, UnixWindowSize winSize) + static unsafe int DoHeavyFork (string programName, string [] args, string [] env, ref int master, ref UnixWindowSize winSize) { byte** argvPtr = null, envpPtr = null; int result = -1; try { AllocNullTerminatedArray (args, ref argvPtr); AllocNullTerminatedArray (env, ref envpPtr); - result = HeavyFork (programName, argvPtr, envpPtr, out master, winSize); + result = HeavyFork (programName, argvPtr, envpPtr, ref master, ref winSize); return result == 0 ? 0 : Marshal.GetLastWin32Error (); } finally { FreeArray (argvPtr, args.Length); @@ -111,7 +111,10 @@ private static unsafe void FreeArray (byte** arr, int length) } [DllImport ("libc", SetLastError = true)] - extern static int ioctl (int fd, long cmd, ref UnixWindowSize WinSz); + extern static int ioctl (int fd, ulong cmd, ref UnixWindowSize WinSz); + + [DllImport ("libpty.dylib", EntryPoint = "set_window_size")] + extern static unsafe int set_window_size (int master, ref UnixWindowSize winSize); /// /// Sends a request to the pseudo terminal to set the size to the specified one @@ -121,8 +124,7 @@ private static unsafe void FreeArray (byte** arr, int length) /// public static int SetWinSize (int fd, ref UnixWindowSize winSize) { - const long MAC_TIOCSWINSZ = 0x80087467; - var r = ioctl (fd, MAC_TIOCSWINSZ, ref winSize); + var r = set_window_size (fd, ref winSize); if (r == -1) { var lastErr = Marshal.GetLastWin32Error (); Console.WriteLine (lastErr); diff --git a/XtermSharp/Terminal.cs b/XtermSharp/Terminal.cs index ed199ca..b490df1 100644 --- a/XtermSharp/Terminal.cs +++ b/XtermSharp/Terminal.cs @@ -156,7 +156,7 @@ public static string [] GetEnvironmentVariables (string termName = null) // Without this, tools like "vi" produce sequences that are not UTF-8 friendly l.Add ("LANG=en_US.UTF-8"); var env = Environment.GetEnvironmentVariables (); - foreach (var x in new [] { "LOGNAME", "USER", "DISPLAY", "LC_TYPE", "USER", "HOME", "PATH" }) + foreach (var x in new [] { "LOGNAME", "DISPLAY", "LC_TYPE", "USER", "HOME", "PATH" }) if (env.Contains (x)) l.Add ($"{x}={env [x]}"); return l.ToArray (); diff --git a/helper/pty.c b/helper/pty.c index f8fdfb2..2fda732 100644 --- a/helper/pty.c +++ b/helper/pty.c @@ -5,10 +5,12 @@ #include #include #include +#include +#include pid_t fork_and_exec (const char *name, char *const args[], char *const env[], int *master, struct winsize *size) -{ +{ pid_t pid = forkpty (master, NULL, NULL, size); if (pid < 0) return pid; @@ -19,3 +21,8 @@ fork_and_exec (const char *name, char *const args[], char *const env[], int *mas } return pid; } + +int set_window_size (int fd, struct winsize *size) +{ + return ioctl(fd, TIOCSWINSZ, size); +} From 08eea4a2e5350144dfebaaf3766f3fe6cab6ecab Mon Sep 17 00:00:00 2001 From: nosami Date: Thu, 27 Jan 2022 17:36:41 +0000 Subject: [PATCH 26/34] Return pid of shell process --- XtermSharp/Pty.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/XtermSharp/Pty.cs b/XtermSharp/Pty.cs index bc112fa..eb2d74e 100644 --- a/XtermSharp/Pty.cs +++ b/XtermSharp/Pty.cs @@ -57,8 +57,7 @@ static unsafe int DoHeavyFork (string programName, string [] args, string [] env try { AllocNullTerminatedArray (args, ref argvPtr); AllocNullTerminatedArray (env, ref envpPtr); - result = HeavyFork (programName, argvPtr, envpPtr, ref master, ref winSize); - return result == 0 ? 0 : Marshal.GetLastWin32Error (); + return HeavyFork (programName, argvPtr, envpPtr, ref master, ref winSize); } finally { FreeArray (argvPtr, args.Length); FreeArray (envpPtr, env.Length); From 7cc33ed099ea3df51dbb2491209f3d164e7e828d Mon Sep 17 00:00:00 2001 From: nosami Date: Thu, 27 Jan 2022 17:56:32 +0000 Subject: [PATCH 27/34] throw if we don't have a PID --- XtermSharp/Pty.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/XtermSharp/Pty.cs b/XtermSharp/Pty.cs index eb2d74e..007ed56 100644 --- a/XtermSharp/Pty.cs +++ b/XtermSharp/Pty.cs @@ -53,11 +53,18 @@ public static int ForkAndExec (string programName, string [] args, string [] env static unsafe int DoHeavyFork (string programName, string [] args, string [] env, ref int master, ref UnixWindowSize winSize) { byte** argvPtr = null, envpPtr = null; - int result = -1; + int result; try { AllocNullTerminatedArray (args, ref argvPtr); AllocNullTerminatedArray (env, ref envpPtr); - return HeavyFork (programName, argvPtr, envpPtr, ref master, ref winSize); + result = HeavyFork (programName, argvPtr, envpPtr, ref master, ref winSize); + + if (result < 0) + { + throw new ArgumentException($"Invalid PID. Last error { Marshal.GetLastWin32Error() }"); + } + + return result; } finally { FreeArray (argvPtr, args.Length); FreeArray (envpPtr, env.Length); From e4a3e8b5e35d13098c3763cc860c6aeeb71f6a16 Mon Sep 17 00:00:00 2001 From: nosami Date: Thu, 27 Jan 2022 18:01:56 +0000 Subject: [PATCH 28/34] Set SetLastError=true on HeavyFork --- XtermSharp/Pty.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/XtermSharp/Pty.cs b/XtermSharp/Pty.cs index 007ed56..680e13a 100644 --- a/XtermSharp/Pty.cs +++ b/XtermSharp/Pty.cs @@ -20,7 +20,7 @@ public class Pty { [DllImport ("libc")] extern static int execve (string process, string [] args, string [] env); - [DllImport ("libpty.dylib", EntryPoint="fork_and_exec")] + [DllImport ("libpty.dylib", SetLastError = true, EntryPoint="fork_and_exec")] extern static unsafe int HeavyFork (string process, byte** args, byte** env, ref int master, ref UnixWindowSize winSize); static bool HeavyDuty = true; From 5b49decd016784fa49c6e825ca6457893d2af189 Mon Sep 17 00:00:00 2001 From: Sandy Armstrong Date: Thu, 3 Mar 2022 08:18:22 -0800 Subject: [PATCH 29/34] Missed an IntPtr->NativeHandle --- MacTerminal/ViewController.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MacTerminal/ViewController.cs b/MacTerminal/ViewController.cs index 322393a..c309282 100644 --- a/MacTerminal/ViewController.cs +++ b/MacTerminal/ViewController.cs @@ -5,6 +5,7 @@ using System; using AppKit; using Foundation; +using ObjCRuntime; using XtermSharp.Mac; namespace MacTerminal { @@ -12,7 +13,7 @@ public partial class ViewController : NSViewController { ProcessTerminalView terminalControl; LocalProcess process; - public ViewController (IntPtr handle) : base (handle) + public ViewController (NativeHandle handle) : base (handle) { } From d297f5db1cf2e54e82c6c299e7adc08cece786e6 Mon Sep 17 00:00:00 2001 From: nosami Date: Wed, 6 Apr 2022 05:56:21 +0100 Subject: [PATCH 30/34] Fix wide character support Fixes https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1501927 --- XtermSharp/BufferLine.cs | 4 +- XtermSharp/CharData.cs | 6 +- XtermSharp/CharWidth.cs | 2 +- XtermSharp/InputHandlers/InputHandler.cs | 95 +++++++++++-------- ...inalBufferManipulationCommandExtensions.cs | 2 +- XtermSharp/Terminal.cs | 2 +- 6 files changed, 67 insertions(+), 44 deletions(-) diff --git a/XtermSharp/BufferLine.cs b/XtermSharp/BufferLine.cs index 6bcc839..02d22dc 100644 --- a/XtermSharp/BufferLine.cs +++ b/XtermSharp/BufferLine.cs @@ -45,7 +45,7 @@ public CharData this [int idx] { * from real empty cells. * */ // TODO: not sue this is completely right - public bool HasContent (int index) => data [index].Code != 0 || data [index].Attribute != CharData.DefaultAttr; + public bool HasContent (int index) => !data [index].IsNullChar() || data [index].Attribute != CharData.DefaultAttr; public bool HasAnyContent() { @@ -164,7 +164,7 @@ public void CopyFrom (BufferLine source, int sourceCol, int destCol, int len) public int GetTrimmedLength () { for (int i = data.Length - 1; i >= 0; --i) - if (data [i].Code != 0) { + if (!data [i].IsNullChar()) { int width = 0; for (int j = 0; j <= i; j++) width += data [i].Width; diff --git a/XtermSharp/CharData.cs b/XtermSharp/CharData.cs index bc5c936..8db879c 100644 --- a/XtermSharp/CharData.cs +++ b/XtermSharp/CharData.cs @@ -27,7 +27,11 @@ public struct CharData { public static CharData RightParenthesis = new CharData (DefaultAttr, ')', 1, 41); public static CharData Period = new CharData (DefaultAttr, '.', 1, 46); - public CharData (int attribute, Rune rune, int width, int code) + public CharData (int attribute, char rune, int width) : this(attribute, rune, width, rune) + { + } + + public CharData (int attribute, char rune, int width, int code) { Attribute = attribute; Rune = rune; diff --git a/XtermSharp/CharWidth.cs b/XtermSharp/CharWidth.cs index 698675b..1fd1345 100644 --- a/XtermSharp/CharWidth.cs +++ b/XtermSharp/CharWidth.cs @@ -86,7 +86,7 @@ public static int ConsoleWidth (this uint rune) if (rune >= 0x7f && rune <= 0xa0) return 0; /* binary search in table of non-spacing characters */ - if (bisearch (rune, combining, combining.GetLength (0)) != 0) + if (bisearch (rune, combining, combining.GetLength (0) - 1) != 0) return 0; /* if we arrive here, ucs is not a combining or C0/C1 control character */ return 1 + diff --git a/XtermSharp/InputHandlers/InputHandler.cs b/XtermSharp/InputHandlers/InputHandler.cs index 20e3736..d3102a0 100644 --- a/XtermSharp/InputHandlers/InputHandler.cs +++ b/XtermSharp/InputHandlers/InputHandler.cs @@ -1200,56 +1200,75 @@ unsafe void Print (byte* data, int start, int end) var curAttr = terminal.CurAttr; var bufferRow = buffer.Lines [buffer.Y + buffer.YBase]; - terminal.UpdateRange (buffer.Y); - while (readingBuffer.HasNext ()) { - int code; - byte bufferValue = readingBuffer.GetNext (); - var n = RuneExt.ExpectedSizeFromFirstByte (bufferValue); - if (n == -1) { - // Invalid UTF-8 sequence, client sent us some junk, happens if we run with the wrong locale set - // for example if LANG=en - code = (int)((uint)bufferValue); - } else if (n == 1) { - code = bufferValue; - } else { - var bytesRemaining = readingBuffer.BytesLeft (); - if (readingBuffer.BytesLeft () >= (n - 1)) { - var x = new byte [n]; - x [0] = bufferValue; - for (int j = 1; j < n; j++) - x [j] = readingBuffer.GetNext (); - - (var r, var size) = Rune.DecodeRune (x); - code = (int)(uint)r; - } else { - readingBuffer.Putback (bufferValue); - return; + while (readingBuffer.HasNext()) { + var ch = ' '; + var chWidth = 0; + int code = 32; + byte bufferValue = readingBuffer.GetNext(); + var n = RuneExt.ExpectedSizeFromFirstByte(bufferValue); + if (n == -1 || n == 1) { + var chSet = false; + if (bufferValue < 127 && charset != null) { + var str = charset[bufferValue]; + if (!string.IsNullOrEmpty(str)) { + ch = str[0]; + // Every single mapping in the charset only takes one slot + chWidth = 1; + chSet = true; + } } - } - // MIGUEL-TODO: I suspect this needs to be a stirng in C# to cope with Grapheme clusters - var ch = code; + if (!chSet) { + (var rune, var size) = Rune.DecodeRune(new byte[] { bufferValue }); + chWidth = size; + ch = rune.ToString()[0]; + } - // calculate print space - // expensive call, therefore we save width in line buffer + } + // Invalid UTF-8 sequence, client sent us some junk, happens if we run with the wrong locale set + // for example if LANG=en + else if (readingBuffer.BytesLeft() >= (n - 1)) { + var bytes = new byte[n]; + bytes[0] = bufferValue; + for (int j = 1; j < n; j++) + bytes[j] = readingBuffer.GetNext(); + + string s = Encoding.UTF8.GetString(bytes, 0, bytes.Length); + if (s.Length > 0) { + ch = s[0]; + } + else { + ch = ' '; + } - // TODO: This is wrong, we only have one byte at this point, we do not have a full rune. - // The correct fix includes the upper parser tracking the "pending" data across invocations - // until a valid UTF-8 string comes in, and *then* we can call this method - // var chWidth = Rune.ColumnWidth ((Rune)code); + // Now the challenge is that we have a character, not a rune, and we want to compute + // the width of it. + if (s.Length == 1) { + chWidth = RuneHelper.ConsoleWidth(s[0]); + } + else { + chWidth = 0; - // 1 until we get a fixed NStack - var chWidth = 1; + foreach (var scalar in s) { + chWidth = Math.Max(chWidth, RuneHelper.ConsoleWidth(scalar)); + } + } + + } + else { + readingBuffer.Putback(bufferValue); + return; + } // get charset replacement character // charset are only defined for ASCII, therefore we only // search for an replacement char if code < 127 - if (code < 127 && charset != null) { + if (bufferValue < 127 && charset != null) { // MIGUEL-FIXME - this is broken for dutch cahrset that returns two letters "ij", need to figure out what to do - if (charset.TryGetValue ((byte)code, out var str)) { + if (charset.TryGetValue (bufferValue, out var str)) { ch = str [0]; code = ch; } @@ -1336,7 +1355,7 @@ unsafe void Print (byte* data, int start, int end) } // write current char to buffer and advance cursor - var charData = new CharData (curAttr, (uint)code, chWidth, ch); + var charData = new CharData (curAttr, ch, chWidth); bufferRow [buffer.X++] = charData; // fullwidth char - also set next cell to placeholder stub and advance cursor diff --git a/XtermSharp/InputHandlers/TerminalBufferManipulationCommandExtensions.cs b/XtermSharp/InputHandlers/TerminalBufferManipulationCommandExtensions.cs index 6d58a71..4777223 100644 --- a/XtermSharp/InputHandlers/TerminalBufferManipulationCommandExtensions.cs +++ b/XtermSharp/InputHandlers/TerminalBufferManipulationCommandExtensions.cs @@ -171,7 +171,7 @@ public static void csiDECRQCRA (this Terminal terminal, params int [] pars) //for (scalar in ch.unicodeScalars) { // checksum += scalar.value; //} - checksum += cd.Code == 0 ? 32 : cd.Code; + checksum += cd.IsNullChar() ? 32 : cd.Code; } } diff --git a/XtermSharp/Terminal.cs b/XtermSharp/Terminal.cs index b490df1..a9366cd 100644 --- a/XtermSharp/Terminal.cs +++ b/XtermSharp/Terminal.cs @@ -985,7 +985,7 @@ public void Backspace () } } } else { - if (buffer.X < left) { + if (buffer.X < left && buffer.X > 0) { // This compensates for the scenario where backspace is supposed to move one step // backwards if the "x" position is behind the left margin. // Test BS_MovesLeftWhenLeftOfLeftMargin From ef9803b4a427ab4f7e0a4363b53922cfbebae4be Mon Sep 17 00:00:00 2001 From: Sandy Armstrong Date: Thu, 12 May 2022 12:34:20 -0700 Subject: [PATCH 31/34] Update for macos workload RC3 API changes --- XtermSharp.Mac/CaretView.cs | 2 +- XtermSharp.Mac/TerminalView.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/XtermSharp.Mac/CaretView.cs b/XtermSharp.Mac/CaretView.cs index 05e1f2b..92d421d 100644 --- a/XtermSharp.Mac/CaretView.cs +++ b/XtermSharp.Mac/CaretView.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Drawing; using AppKit; using CoreAnimation; diff --git a/XtermSharp.Mac/TerminalView.cs b/XtermSharp.Mac/TerminalView.cs index ff7a2af..d634582 100644 --- a/XtermSharp.Mac/TerminalView.cs +++ b/XtermSharp.Mac/TerminalView.cs @@ -445,8 +445,8 @@ void UpdateDisplay (bool notifyAccessibility) if (notifyAccessibility) { accessibility.Invalidate (); - NSAccessibility.PostNotification (this, NSAccessibilityNotifications.ValueChangedNotification); - NSAccessibility.PostNotification (this, NSAccessibilityNotifications.SelectedTextChangedNotification); + NSAccessibility.PostNotification (this, NSView.ValueChangedNotification); + NSAccessibility.PostNotification (this, NSView.SelectedTextChangedNotification); } } @@ -1419,7 +1419,7 @@ void HandleSelectionChanged () } accessibility.Invalidate (); - NSAccessibility.PostNotification (this, NSAccessibilityNotifications.SelectedTextChangedNotification); + NSAccessibility.PostNotification (this, NSView.SelectedTextChangedNotification); } void SelectSearchResult(SearchSnapshot.SearchResult searchResult) From 17dfff50f0be1084ddb3a438d596cb4f2740d9f2 Mon Sep 17 00:00:00 2001 From: nosami Date: Thu, 15 Sep 2022 15:07:50 +0100 Subject: [PATCH 32/34] Add null checking to SelectionService It seems that it is somehow possible to hit a NRE when selecting text. See https://devdiv.visualstudio.com/DevDiv/_queries/edit/1582556 --- XtermSharp/SelectionService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/XtermSharp/SelectionService.cs b/XtermSharp/SelectionService.cs index 29a5253..05360ab 100644 --- a/XtermSharp/SelectionService.cs +++ b/XtermSharp/SelectionService.cs @@ -283,7 +283,7 @@ Line [] GetSelectedLines (Point start, Point end) // get the first line BufferLine bufferLine = buffer.Lines [start.Y]; - if (bufferLine.HasAnyContent ()) { + if (bufferLine != null && bufferLine.HasAnyContent ()) { str = TranslateBufferLineToString (buffer, start.Y, start.X, start.Y < end.Y ? -1 : end.X); var fragment = new LineFragment (str, start.Y, start.X); @@ -334,7 +334,7 @@ Line [] GetSelectedLines (Point start, Point end) // get the last row if (end.Y != start.Y) { bufferLine = buffer.Lines [end.Y]; - if (bufferLine.HasAnyContent ()) { + if (bufferLine != null && bufferLine.HasAnyContent ()) { addBlanks (); isWrapped = bufferLine?.IsWrapped ?? false; From 7feb668cbbd9d62244655a2cf2a16db117f18f59 Mon Sep 17 00:00:00 2001 From: Marius Ungureanu Date: Wed, 21 Sep 2022 20:31:07 +0300 Subject: [PATCH 33/34] Set minimum OS version for the dylib --- helper/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helper/Makefile b/helper/Makefile index cb73765..f26879c 100644 --- a/helper/Makefile +++ b/helper/Makefile @@ -8,7 +8,7 @@ endif libpty.dylib: pty.c @if [[ "$(UNAME)" == "Darwin" ]]; then \ - cc -arch arm64 -arch x86_64 -shared pty.c -shared -o libpty.dylib; \ + cc -mmacosx-version-min=10.14 -arch arm64 -arch x86_64 -shared pty.c -shared -o libpty.dylib; \ else \ cc -shared pty.c -shared -o libpty.dylib; \ fi From ed9d770220c574a58e810a038de2d4c233839e59 Mon Sep 17 00:00:00 2001 From: nosami Date: Tue, 8 Nov 2022 15:30:05 +0000 Subject: [PATCH 34/34] Set spacing characters in East Asian Ambiguous (A) * The following functions are the same as mk_wcwidth() and * mk_wcswidth(), except that spacing characters in the East Asian * Ambiguous (A) category as defined in Unicode Technical Report No.11 * have a column width of 2. This variant might be useful for users of * CJK legacy encodings who want to migrate to UCS without changing * the traditional terminal character-width behaviour. It is not * otherwise recommended for general use. --- XtermSharp/CharWidth.cs | 71 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/XtermSharp/CharWidth.cs b/XtermSharp/CharWidth.cs index 1fd1345..3ba0ba1 100644 --- a/XtermSharp/CharWidth.cs +++ b/XtermSharp/CharWidth.cs @@ -54,6 +54,73 @@ public static class RuneHelper { { 0xE0100, 0xE01EF } }; + /* + * The following functions are the same as mk_wcwidth() and + * mk_wcswidth(), except that spacing characters in the East Asian + * Ambiguous (A) category as defined in Unicode Technical Report #11 + * have a column width of 2. This variant might be useful for users of + * CJK legacy encodings who want to migrate to UCS without changing + * the traditional terminal character-width behaviour. It is not + * otherwise recommended for general use. + */ + + // sorted list of non-overlapping intervals of East Asian Ambiguous + // characters, generated by "uniset +WIDTH-A -cat=Me -cat=Mn -cat=Cf c" + static uint[,] ambiguous = new uint[,] { + { 0x00A1, 0x00A1 }, { 0x00A4, 0x00A4 }, { 0x00A7, 0x00A8 }, + { 0x00AA, 0x00AA }, { 0x00AE, 0x00AE }, { 0x00B0, 0x00B4 }, + { 0x00B6, 0x00BA }, { 0x00BC, 0x00BF }, { 0x00C6, 0x00C6 }, + { 0x00D0, 0x00D0 }, { 0x00D7, 0x00D8 }, { 0x00DE, 0x00E1 }, + { 0x00E6, 0x00E6 }, { 0x00E8, 0x00EA }, { 0x00EC, 0x00ED }, + { 0x00F0, 0x00F0 }, { 0x00F2, 0x00F3 }, { 0x00F7, 0x00FA }, + { 0x00FC, 0x00FC }, { 0x00FE, 0x00FE }, { 0x0101, 0x0101 }, + { 0x0111, 0x0111 }, { 0x0113, 0x0113 }, { 0x011B, 0x011B }, + { 0x0126, 0x0127 }, { 0x012B, 0x012B }, { 0x0131, 0x0133 }, + { 0x0138, 0x0138 }, { 0x013F, 0x0142 }, { 0x0144, 0x0144 }, + { 0x0148, 0x014B }, { 0x014D, 0x014D }, { 0x0152, 0x0153 }, + { 0x0166, 0x0167 }, { 0x016B, 0x016B }, { 0x01CE, 0x01CE }, + { 0x01D0, 0x01D0 }, { 0x01D2, 0x01D2 }, { 0x01D4, 0x01D4 }, + { 0x01D6, 0x01D6 }, { 0x01D8, 0x01D8 }, { 0x01DA, 0x01DA }, + { 0x01DC, 0x01DC }, { 0x0251, 0x0251 }, { 0x0261, 0x0261 }, + { 0x02C4, 0x02C4 }, { 0x02C7, 0x02C7 }, { 0x02C9, 0x02CB }, + { 0x02CD, 0x02CD }, { 0x02D0, 0x02D0 }, { 0x02D8, 0x02DB }, + { 0x02DD, 0x02DD }, { 0x02DF, 0x02DF }, { 0x0391, 0x03A1 }, + { 0x03A3, 0x03A9 }, { 0x03B1, 0x03C1 }, { 0x03C3, 0x03C9 }, + { 0x0401, 0x0401 }, { 0x0410, 0x044F }, { 0x0451, 0x0451 }, + { 0x2010, 0x2010 }, { 0x2013, 0x2016 }, { 0x2018, 0x2019 }, + { 0x201C, 0x201D }, { 0x2020, 0x2022 }, { 0x2024, 0x2027 }, + { 0x2030, 0x2030 }, { 0x2032, 0x2033 }, { 0x2035, 0x2035 }, + { 0x203B, 0x203B }, { 0x203E, 0x203E }, { 0x2074, 0x2074 }, + { 0x207F, 0x207F }, { 0x2081, 0x2084 }, { 0x20AC, 0x20AC }, + { 0x2103, 0x2103 }, { 0x2105, 0x2105 }, { 0x2109, 0x2109 }, + { 0x2113, 0x2113 }, { 0x2116, 0x2116 }, { 0x2121, 0x2122 }, + { 0x2126, 0x2126 }, { 0x212B, 0x212B }, { 0x2153, 0x2154 }, + { 0x215B, 0x215E }, { 0x2160, 0x216B }, { 0x2170, 0x2179 }, + { 0x2190, 0x2199 }, { 0x21B8, 0x21B9 }, { 0x21D2, 0x21D2 }, + { 0x21D4, 0x21D4 }, { 0x21E7, 0x21E7 }, { 0x2200, 0x2200 }, + { 0x2202, 0x2203 }, { 0x2207, 0x2208 }, { 0x220B, 0x220B }, + { 0x220F, 0x220F }, { 0x2211, 0x2211 }, { 0x2215, 0x2215 }, + { 0x221A, 0x221A }, { 0x221D, 0x2220 }, { 0x2223, 0x2223 }, + { 0x2225, 0x2225 }, { 0x2227, 0x222C }, { 0x222E, 0x222E }, + { 0x2234, 0x2237 }, { 0x223C, 0x223D }, { 0x2248, 0x2248 }, + { 0x224C, 0x224C }, { 0x2252, 0x2252 }, { 0x2260, 0x2261 }, + { 0x2264, 0x2267 }, { 0x226A, 0x226B }, { 0x226E, 0x226F }, + { 0x2282, 0x2283 }, { 0x2286, 0x2287 }, { 0x2295, 0x2295 }, + { 0x2299, 0x2299 }, { 0x22A5, 0x22A5 }, { 0x22BF, 0x22BF }, + { 0x2312, 0x2312 }, { 0x2460, 0x24E9 }, { 0x24EB, 0x254B }, + { 0x2550, 0x2573 }, { 0x2580, 0x258F }, { 0x2592, 0x2595 }, + { 0x25A0, 0x25A1 }, { 0x25A3, 0x25A9 }, { 0x25B2, 0x25B3 }, + { 0x25B6, 0x25B7 }, { 0x25BC, 0x25BD }, { 0x25C0, 0x25C1 }, + { 0x25C6, 0x25C8 }, { 0x25CB, 0x25CB }, { 0x25CE, 0x25D1 }, + { 0x25E2, 0x25E5 }, { 0x25EF, 0x25EF }, { 0x2605, 0x2606 }, + { 0x2609, 0x2609 }, { 0x260E, 0x260F }, { 0x2614, 0x2615 }, + { 0x261C, 0x261C }, { 0x261E, 0x261E }, { 0x2640, 0x2640 }, + { 0x2642, 0x2642 }, { 0x2660, 0x2661 }, { 0x2663, 0x2665 }, + { 0x2667, 0x266A }, { 0x266C, 0x266D }, { 0x266F, 0x266F }, + { 0x273D, 0x273D }, { 0x2776, 0x277F }, { 0xE000, 0xF8FF }, + { 0xFFFD, 0xFFFD }, { 0xF0000, 0xFFFFD }, { 0x100000, 0x10FFFD } + }; + static int bisearch (uint rune, uint [,] table, int max) { int min = 0; @@ -88,6 +155,10 @@ public static int ConsoleWidth (this uint rune) /* binary search in table of non-spacing characters */ if (bisearch (rune, combining, combining.GetLength (0) - 1) != 0) return 0; + + if (bisearch(rune, ambiguous, ambiguous.GetLength(0) - 1) != 0) + return 2; + /* if we arrive here, ucs is not a combining or C0/C1 control character */ return 1 + ((rune >= 0x1100 &&