浏览代码

Merge branch 'v1_develop' into v1_release

Tig 1 年之前
父节点
当前提交
3fbab50a08

+ 3 - 1
.github/workflows/api-docs.yml

@@ -3,7 +3,9 @@ name: Build and publish API docs
 on:
   push:
   # only publish v2 (main or develop); v2 is published via the Terminal.GuiV2Docs repo
-    branches: [main, develop]
+    branches: [v1_release, v1_develop]
+    paths: 
+      - docfx/**
 
 permissions:
   id-token: write 

+ 1 - 1
.github/workflows/codeql-analysis.yml

@@ -25,7 +25,7 @@ on:
 
 jobs:
   CodeQL-Build:
-
+    if: github.repository == 'gui-cs/Terminal.Gui'|| github.event_name == 'schedule'
     runs-on: ubuntu-latest
 
     steps:

+ 1 - 0
.github/workflows/codeql.yml

@@ -29,6 +29,7 @@ jobs:
     # Consider using larger runners for possible analysis time improvements.
     runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
     timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }}
+    if: github.repository == 'gui-cs/Terminal.Gui'|| github.event_name == 'schedule'
     permissions:
       actions: read
       contents: read

+ 3 - 3
.github/workflows/dotnet-core.yml

@@ -2,18 +2,18 @@ name: .NET Core
 
 on:
   push:
-    branches: [ main, develop ]
+    branches: [ v1_release, v1_develop ]
     paths-ignore:
       - '**.md'
   pull_request:
-    branches: [ main, develop ]
+    branches: [ v1_release, v1_develop ]
     paths-ignore:
       - '**.md'
       
 jobs:
   build:
     runs-on: ubuntu-latest
-
+    timeout-minutes: 10
     steps:
     - uses: actions/checkout@v4
 

+ 1 - 1
.github/workflows/publish.yml

@@ -2,7 +2,7 @@ name: Publish Terminal.Gui
 
 on:
   push:
-    branches: [ main, develop, v2_release, v2_develop ]
+    branches: [ v1_release, v1_develop, v2_release, v2_develop ]
     tags:
       - v*
     paths-ignore:

+ 2 - 2
CONTRIBUTING.md

@@ -12,8 +12,8 @@ We welcome contributions from the community. See [Issues](https://github.com/gui
 
 Terminal.Gui uses the [GitFlow](https://nvie.com/posts/a-successful-git-branching-model/) branching model. 
 
-* The `main` branch is always stable, and always matches the most recently released Nuget package.
-* The `develop` branch is where bug-fixes to v1.x happens. It is the default branch.
+* The `v1_release` branch is always stable, and always matches the most recently released Nuget package.
+* The `v1__develop` branch is where bug-fixes to v1.x happens. It is the default branch.
 * The `v2_develop` branch is where development on v2.x happens. 
 
 ### Forking Terminal.Gui

+ 8 - 6
Example/Example.cs

@@ -7,22 +7,23 @@ using Terminal.Gui;
 
 Application.Run<ExampleWindow> ();
 
-System.Console.WriteLine ($"Username: {((ExampleWindow)Application.Top).usernameText.Text}");
-
 // Before the application exits, reset Terminal.Gui for clean shutdown
 Application.Shutdown ();
 
+System.Console.WriteLine ($@"Username: {ExampleWindow.Username}");
+
 // Defines a top-level window with border and title
 public class ExampleWindow : Window {
+	public static string Username { get; internal set; }
 	public TextField usernameText;
-	
+
 	public ExampleWindow ()
 	{
 		Title = "Example App (Ctrl+Q to quit)";
 
 		// Create input components and labels
-		var usernameLabel = new Label () { 
-			Text = "Username:" 
+		var usernameLabel = new Label () {
+			Text = "Username:"
 		};
 
 		usernameText = new TextField ("") {
@@ -50,7 +51,7 @@ public class ExampleWindow : Window {
 		// Create login button
 		var btnLogin = new Button () {
 			Text = "Login",
-			Y = Pos.Bottom(passwordLabel) + 1,
+			Y = Pos.Bottom (passwordLabel) + 1,
 			// center the login button horizontally
 			X = Pos.Center (),
 			IsDefault = true,
@@ -60,6 +61,7 @@ public class ExampleWindow : Window {
 		btnLogin.Clicked += () => {
 			if (usernameText.Text == "admin" && passwordText.Text == "password") {
 				MessageBox.Query ("Logging In", "Login Successful", "Ok");
+				Username = usernameText.Text.ToString ();
 				Application.RequestStop ();
 			} else {
 				MessageBox.ErrorQuery ("Logging In", "Incorrect username or password", "Ok");

+ 10 - 9
GitVersion.yml

@@ -2,25 +2,26 @@ mode: ContinuousDeployment
 tag-prefix: '[vV]'
 continuous-delivery-fallback-tag: pre
 branches:
-  develop:
+  v1_develop:
     mode: ContinuousDeployment
     tag: pre
-    regex: develop
+    regex: v1_develop
     source-branches:
-    - main
+    - v1_release
     pre-release-weight: 100
-  main:
+  v1_release:
     tag: rc
     increment: Patch
+    regex: v1_release
     source-branches:
-    - develop
-    - main
-  feature:
+    - v1_develop
+    - v1_release
+  v1_feature:
     tag: useBranchName
     regex: ^features?[/-]
     source-branches:
-    - develop
-    - main
+    - v1_develop
+    - v1_release
   pull-request:
     tag: PullRequest.{BranchName}
     increment: Inherit

+ 12 - 7
README.md

@@ -38,7 +38,7 @@ dotnet run
 * [Conceptual Documentation](https://gui-cs.github.io/Terminal.Gui/docs/index.html)
 * [API Documentation](https://gui-cs.github.io/Terminal.Gui/api/Terminal.Gui)
 
-_The Documentation matches the most recent Nuget release from the `main` branch ([![Version](https://img.shields.io/nuget/v/Terminal.Gui.svg)](https://www.nuget.org/packages/Terminal.Gui))_
+_The Documentation matches the most recent Nuget release from the `v1_release_` branch ([![Version](https://img.shields.io/nuget/v/Terminal.Gui.svg)](https://www.nuget.org/packages/Terminal.Gui))_
 
 See the [`Terminal.Gui/` README](https://github.com/gui-cs/Terminal.Gui/tree/master/Terminal.Gui) for an overview of how the library is structured. The [Conceptual Documentation](https://gui-cs.github.io/Terminal.Gui/docs/index.html) provides insight into core concepts.
 
@@ -64,28 +64,32 @@ The team is looking forward to seeing new amazing projects made by the community
 The following example shows a basic Terminal.Gui application in C#:
 
 ```csharp
+// This is a simple example application.  For the full range of functionality
+// see the UICatalog project
+
 // A simple Terminal.Gui example in C# - using C# 9.0 Top-level statements
 
 using Terminal.Gui;
 
 Application.Run<ExampleWindow> ();
 
-System.Console.WriteLine ($"Username: {((ExampleWindow)Application.Top).usernameText.Text}");
-
 // Before the application exits, reset Terminal.Gui for clean shutdown
 Application.Shutdown ();
 
+System.Console.WriteLine ($@"Username: {ExampleWindow.Username}");
+
 // Defines a top-level window with border and title
 public class ExampleWindow : Window {
+	public static string Username { get; internal set; }
 	public TextField usernameText;
-	
+
 	public ExampleWindow ()
 	{
 		Title = "Example App (Ctrl+Q to quit)";
 
 		// Create input components and labels
-		var usernameLabel = new Label () { 
-			Text = "Username:" 
+		var usernameLabel = new Label () {
+			Text = "Username:"
 		};
 
 		usernameText = new TextField ("") {
@@ -113,7 +117,7 @@ public class ExampleWindow : Window {
 		// Create login button
 		var btnLogin = new Button () {
 			Text = "Login",
-			Y = Pos.Bottom(passwordLabel) + 1,
+			Y = Pos.Bottom (passwordLabel) + 1,
 			// center the login button horizontally
 			X = Pos.Center (),
 			IsDefault = true,
@@ -123,6 +127,7 @@ public class ExampleWindow : Window {
 		btnLogin.Clicked += () => {
 			if (usernameText.Text == "admin" && passwordText.Text == "password") {
 				MessageBox.Query ("Logging In", "Login Successful", "Ok");
+				Username = usernameText.Text.ToString ();
 				Application.RequestStop ();
 			} else {
 				MessageBox.ErrorQuery ("Logging In", "Incorrect username or password", "Ok");

+ 3 - 3
Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

@@ -752,9 +752,9 @@ namespace Terminal.Gui {
 			contents = new int [Rows, Cols, 3];
 			for (int row = 0; row < Rows; row++) {
 				for (int col = 0; col < Cols; col++) {
-					//Curses.move (row, col);
-					//Curses.attrset (Colors.TopLevel.Normal);
-					//Curses.addch ((int)(uint)' ');
+					Curses.move (row, col);
+					Curses.attrset (Colors.TopLevel.Normal);
+					Curses.addch ((int)(uint)' ');
 					contents [row, col, 0] = ' ';
 					contents [row, col, 1] = Colors.TopLevel.Normal;
 					contents [row, col, 2] = 0;

+ 1 - 1
Terminal.Gui/ConsoleDrivers/CursesDriver/UnmanagedLibrary.cs

@@ -256,7 +256,7 @@ namespace Unix.Terminal {
 		/// to avoid the dependency on libc-dev Linux.
 		/// </summary>
 		static class CoreCLR {
-#if NET7_0
+#if NET6_0_OR_GREATER
 			// Custom resolver to support true single-file apps
 			// (those which run directly from bundle; in-memory).
 			//	 -1 on Unix means self-referencing binary (libcoreclr.so)

+ 83 - 41
Terminal.Gui/ConsoleDrivers/NetDriver.cs

@@ -113,7 +113,9 @@ namespace Terminal.Gui {
 		ConsoleDriver consoleDriver;
 		volatile ConsoleKeyInfo [] cki = null;
 		static volatile bool isEscSeq;
-		bool stopTasks;
+
+		internal CancellationTokenSource TokenSource = new CancellationTokenSource ();
+
 #if PROCESS_REQUEST
 		bool neededProcessRequest;
 #endif
@@ -125,21 +127,13 @@ namespace Terminal.Gui {
 				throw new ArgumentNullException ("Console driver instance must be provided.");
 			}
 			this.consoleDriver = consoleDriver;
-			Task.Run (ProcessInputResultQueue);
-			Task.Run (CheckWinChange);
-		}
-
-		internal void StopTasks ()
-		{
-			stopTasks = true;
+			Task.Run (ProcessInputResultQueue, TokenSource.Token);
+			Task.Run (CheckWinChange, TokenSource.Token);
 		}
 
 		public InputResult? ReadConsoleInput ()
 		{
-			while (true) {
-				if (stopTasks) {
-					return null;
-				}
+			while (!TokenSource.IsCancellationRequested) {
 				waitForStart.Set ();
 				winChange.Set ();
 
@@ -154,11 +148,13 @@ namespace Terminal.Gui {
 					return inputResultQueue.Dequeue ();
 				}
 			}
+
+			return null;
 		}
 
 		void ProcessInputResultQueue ()
 		{
-			while (true) {
+			while (!TokenSource.IsCancellationRequested) {
 				waitForStart.Wait ();
 				waitForStart.Reset ();
 
@@ -176,8 +172,23 @@ namespace Terminal.Gui {
 			ConsoleModifiers mod = 0;
 			ConsoleKeyInfo newConsoleKeyInfo = default;
 
-			while (true) {
-				ConsoleKeyInfo consoleKeyInfo = Console.ReadKey (true);
+			while (!TokenSource.IsCancellationRequested) {
+				ConsoleKeyInfo consoleKeyInfo = default;
+
+				try {
+					if (Console.KeyAvailable) {
+						consoleKeyInfo = Console.ReadKey (true);
+					} else {
+						Task.Delay (100, TokenSource.Token).Wait (TokenSource.Token);
+						if (Console.KeyAvailable) {
+							consoleKeyInfo = Console.ReadKey (true);
+						}
+					}
+				} catch (OperationCanceledException) {
+
+					return;
+				}
+
 				if ((consoleKeyInfo.KeyChar == (char)Key.Esc && !isEscSeq)
 					|| (consoleKeyInfo.KeyChar != (char)Key.Esc && isEscSeq)) {
 					if (cki == null && consoleKeyInfo.KeyChar != (char)Key.Esc && isEscSeq) {
@@ -201,18 +212,19 @@ namespace Terminal.Gui {
 					}
 					break;
 				} else {
-					GetConsoleInputType (consoleKeyInfo);
-					break;
+					if (consoleKeyInfo != default) {
+						GetConsoleInputType (consoleKeyInfo);
+						break;
+					}
 				}
+
+				TokenSource.Token.ThrowIfCancellationRequested ();
 			}
 		}
 
 		void CheckWinChange ()
 		{
-			while (true) {
-				if (stopTasks) {
-					return;
-				}
+			while (!TokenSource.IsCancellationRequested) {
 				winChange.Wait ();
 				winChange.Reset ();
 				WaitWinChange ();
@@ -222,13 +234,16 @@ namespace Terminal.Gui {
 
 		void WaitWinChange ()
 		{
-			while (true) {
-				// Wait for a while then check if screen has changed sizes
-				Task.Delay (500).Wait ();
+			while (!TokenSource.IsCancellationRequested) {
+				try {
+					// Wait for a while then check if screen has changed sizes
+					Task.Delay (500, TokenSource.Token).Wait (TokenSource.Token);
+
+				} catch (OperationCanceledException) {
 
-				if (stopTasks) {
 					return;
 				}
+
 				int buffHeight, buffWidth;
 				if (((NetDriver)consoleDriver).IsWinPlatform) {
 					buffHeight = Math.Max (Console.BufferHeight, 0);
@@ -691,7 +706,7 @@ namespace Terminal.Gui {
 
 		public override void End ()
 		{
-			mainLoop.netEvents.StopTasks ();
+			mainLoop.Dispose ();
 
 			if (IsWinPlatform) {
 				NetWinConsole.Cleanup ();
@@ -1019,8 +1034,28 @@ namespace Terminal.Gui {
 
 		public override void Suspend ()
 		{
-		}
+			if (Environment.OSVersion.Platform != PlatformID.Unix) {
+				return;
+			}
+
+			StopReportingMouseMoves ();
+			Console.ResetColor ();
+			Console.Clear ();
+
+			//Disable alternative screen buffer.
+			Console.Out.Write ("\x1b[?1049l");
+
+			//Set cursor key to cursor.
+			Console.Out.Write ("\x1b[?25h");
+
+			Platform.Suspend ();
+
+			//Enable alternative screen buffer.
+			Console.Out.Write ("\x1b[?1049h");
 
+			Application.Refresh ();
+			StartReportingMouseMoves ();
+		}
 
 		public override void SetAttribute (Attribute c)
 		{
@@ -1343,7 +1378,11 @@ namespace Terminal.Gui {
 		public override bool SetCursorVisibility (CursorVisibility visibility)
 		{
 			savedCursorVisibility = visibility;
-			return Console.CursorVisible = visibility == CursorVisibility.Default;
+			Console.Out.Write (visibility == CursorVisibility.Default
+				? "\x1b[?25h"
+				: "\x1b[?25l");
+
+			return visibility == CursorVisibility.Default;
 		}
 
 		/// <inheritdoc/>
@@ -1423,7 +1462,7 @@ namespace Terminal.Gui {
 	/// <remarks>
 	/// This implementation is used for NetDriver.
 	/// </remarks>
-	internal class NetMainLoop : IMainLoopDriver {
+	internal class NetMainLoop : IMainLoopDriver, IDisposable {
 		ManualResetEventSlim keyReady = new ManualResetEventSlim (false);
 		ManualResetEventSlim waitForProbe = new ManualResetEventSlim (false);
 		Queue<NetEvents.InputResult?> inputResult = new Queue<NetEvents.InputResult?> ();
@@ -1453,27 +1492,25 @@ namespace Terminal.Gui {
 
 		void NetInputHandler ()
 		{
-			while (true) {
+			while (!tokenSource.IsCancellationRequested) {
 				waitForProbe.Wait ();
 				waitForProbe.Reset ();
 				if (inputResult.Count == 0) {
 					inputResult.Enqueue (netEvents.ReadConsoleInput ());
 				}
-				try {
-					while (inputResult.Peek () == null) {
-						inputResult.Dequeue ();
-					}
-					if (inputResult.Count > 0) {
-						keyReady.Set ();
-					}
-				} catch (InvalidOperationException) { }
+				while (inputResult.Count > 0 && inputResult.Peek () == null) {
+					inputResult.Dequeue ();
+				}
+				if (inputResult.Count > 0) {
+					keyReady.Set ();
+				}
 			}
 		}
 
 		void IMainLoopDriver.Setup (MainLoop mainLoop)
 		{
 			this.mainLoop = mainLoop;
-			Task.Run (NetInputHandler);
+			Task.Run (NetInputHandler, tokenSource.Token);
 		}
 
 		void IMainLoopDriver.Wakeup ()
@@ -1503,8 +1540,7 @@ namespace Terminal.Gui {
 				return inputResult.Count > 0 || CheckTimers (wait, out _);
 			}
 
-			tokenSource.Dispose ();
-			tokenSource = new CancellationTokenSource ();
+			tokenSource.Token.ThrowIfCancellationRequested ();
 			return true;
 		}
 
@@ -1537,5 +1573,11 @@ namespace Terminal.Gui {
 				ProcessInput?.Invoke (inputResult.Dequeue ().Value);
 			}
 		}
+
+		public void Dispose ()
+		{
+			tokenSource.Cancel ();
+			netEvents.TokenSource.Cancel ();
+		}
 	}
 }

+ 3 - 2
Terminal.Gui/Core/Application.cs

@@ -53,7 +53,7 @@ namespace Terminal.Gui {
 	///   </para>
 	///   <para>
 	///     When invoked sets the SynchronizationContext to one that is tied
-	///     to the mainloop, allowing user code to use async/await.
+	///     to the MainLoop, allowing user code to use async/await.
 	///   </para>
 	/// </remarks>
 	public static class Application {
@@ -1037,6 +1037,7 @@ namespace Terminal.Gui {
 			toplevel.LayoutSubviews ();
 			toplevel.PositionToplevels ();
 			toplevel.WillPresent ();
+			EnsuresTopOnFront ();
 			if (refreshDriver) {
 				MdiTop?.OnChildLoaded (toplevel);
 				toplevel.OnLoaded ();
@@ -1133,9 +1134,9 @@ namespace Terminal.Gui {
 
 			// BUGBUG: MdiTop is not cleared here, but it should be?
 
-			MainLoop = null;
 			Driver?.End ();
 			Driver = null;
+			MainLoop = null;
 			Iteration = null;
 			RootMouseEvent = null;
 			RootKeyEvent = null;

+ 4 - 0
Terminal.Gui/Core/View.cs

@@ -394,6 +394,10 @@ namespace Terminal.Gui {
 								}
 							}
 						}
+
+						if (SuperView is Toplevel && Application.Current?.Focused != SuperView) {
+							Application.EnsuresTopOnFront ();
+						}
 					}
 					OnCanFocusChanged ();
 					SetNeedsDisplay ();

+ 1 - 1
Terminal.Gui/Core/Window.cs

@@ -104,7 +104,7 @@ namespace Terminal.Gui {
 
 			public override void OnCanFocusChanged ()
 			{
-				if (MostFocused == null && CanFocus && Visible) {
+				if (HasFocus && MostFocused == null && CanFocus && Visible) {
 					EnsureFocus ();
 				}
 

+ 23 - 23
Terminal.Gui/README.md

@@ -51,9 +51,9 @@ Doing so will update the `.csproj` files in your branch with version info, which
 
 The following actions will publish the Terminal.Gui package to Nuget:
 
-* A new version tag is defined and pushed to `main` - this is the normal release process.
-* A push to the `main` branch without a new version tag - this is a release-candidate build and will be of the form `1.2.3-rc.4`
-* A push to the `develop` branch - this is a pre-release build and will be of the form `1.2.3-pre.4`
+* A new version tag is defined and pushed to `v1_release` - this is the normal release process.
+* A push to the `v1_release` branch without a new version tag - this is a release-candidate build and will be of the form `1.2.3-rc.4`
+* A push to the `v1_develop` branch - this is a pre-release build and will be of the form `1.2.3-pre.4`
 
 ## Publishing a Release of Terminal.Gui
 
@@ -71,17 +71,17 @@ The `tag` must be of the form `v<major>.<minor>.<patch>`, e.g. `v1.2.3`.
 
 ### 1) Verify the `develop` branch is ready for release
 
-* Ensure everything is committed and pushed to the `develop` branch
-* Ensure your local `develop` branch is up-to-date with `upstream/develop`
+* Ensure everything is committed and pushed to the `v1_develop` branch
+* Ensure your local `v1_develop` branch is up-to-date with `upstream/v1_develop`
 
-### 2) Create a pull request for the release in the `develop` branch
+### 2) Create a pull request for the release in the `v1_develop` branch
 
 The PR title should be of the form "Release v1.2.3"
 
 ```powershell
-git checkout develop
-git pull upstream develop
-git checkout -b v2_3_4
+git checkout v1_develop
+git pull upstream v1_develop
+git checkout -b v1_2_3
 <touch a file>
 git add .
 git commit -m "Release v1.2.3"
@@ -90,36 +90,36 @@ git push
 
 Go to the link printed by `git push` and fill out the Pull Request.
 
-### 3) On github.com, verify the build action worked on your fork, then merge the PR to `develop`
+### 3) On github.com, verify the build action worked on your fork, then merge the PR to `v1_develop`
 
 * Merging the PR will trigger the publish action on `upstream` (the main repo) and publish the Nuget package as a pre-release (e.g. `1.2.3-pre.1`).
 
 ### 4) Pull the merged `develop` from `upstream`
 
 ```powershell
-git checkout develop
-git pull upstream develop
+git checkout v1_develop
+git pull upstream v1_develop
 ```
 
 ### 5) Merge `develop` into `main`
 
 ```powershell
-git checkout main
-git pull upstream main
-git merge develop
+git checkout v1_release
+git pull upstream v1_release
+git merge v1_develop
 ```
 
 Fix any merge errors.
 
-At this point, to release a release candidate, push the `main` branch `upstream` without a new tag.
+At this point, to release a release candidate, push the `v1_release` branch `upstream` without a new tag.
 
 ```powershell
-git push upstream main
+git push upstream v1_release
 ```
 
 This will publish `1.2.3-rc.1` to Nuget.
 
-### 6) Create a new annotated tag for the release on `main`
+### 6) Create a new annotated tag for the release on `v1_release`
 
 ```powershell
 git tag v1.2.3 -a -m "Release v1.2.3"
@@ -146,13 +146,13 @@ https://www.nuget.org/packages/Terminal.Gui
 
 Generate release notes with the list of PRs since the last release.
 
-### 11) Update the `develop` branch with the new version
+### 11) Update the `v1_develop` branch with the new version
 
 ```powershell
-git checkout develop
-git pull upstream develop
-git merge main
-git push upstream develop
+git checkout v1_develop
+git pull upstream v1_develop
+git merge v1_release
+git push upstream v1_develop
 ```
 
 ## Nuget

+ 1 - 1
Terminal.Gui/Terminal.Gui.csproj

@@ -20,7 +20,7 @@
     <DebugType>portable</DebugType>
   </PropertyGroup>
   <PropertyGroup>
-    <TargetFrameworks>net472;netstandard2.0;netstandard2.1;net7.0;net8.0</TargetFrameworks>
+    <TargetFrameworks>net472;netstandard2.0;netstandard2.1;net6.0;net7.0;net8.0</TargetFrameworks>
     <RootNamespace>Terminal.Gui</RootNamespace>
     <AssemblyName>Terminal.Gui</AssemblyName>
     <SuppressNETCoreSdkPreviewMessage>true</SuppressNETCoreSdkPreviewMessage>

+ 31 - 5
Terminal.Gui/Views/ListView.cs

@@ -601,10 +601,12 @@ namespace Terminal.Gui {
 		/// <returns></returns>
 		public virtual bool MoveEnd ()
 		{
-			if (source.Count > 0 && selected != source.Count - 1) {
+			if (source?.Count > 0 && selected != source.Count - 1) {
 				selected = source.Count - 1;
 				if (top + selected > Frame.Height - 1) {
-					top = selected;
+					top = selected < Frame.Height - 1
+						? Math.Max (Frame.Height - selected + 1, 0)
+						: Math.Max (selected - Frame.Height + 1, 0);
 				}
 				OnSelectedChanged ();
 				SetNeedsDisplay ();
@@ -749,6 +751,11 @@ namespace Terminal.Gui {
 		public void EnsureSelectedItemVisible ()
 		{
 			SuperView?.LayoutSubviews ();
+			// If last item is selected and is removed, ensures a valid selected item
+			if (Source != null && selected > Source.Count - 1) {
+				SelectedItem = Source.Count - 1;
+				SetNeedsDisplay ();
+			}
 			if (selected < top) {
 				top = selected;
 			} else if (Frame.Height > 0 && selected >= top + Frame.Height) {
@@ -831,11 +838,30 @@ namespace Terminal.Gui {
 		}
 
 		/// <inheritdoc/>
-		public int Count => src != null ? src.Count : 0;
+		public int Count {
+			get {
+				CheckAndResizeMarksIfRequired ();
+				return src?.Count ?? 0;
+			}
+		}
 
 		/// <inheritdoc/>
 		public int Length => len;
 
+		void CheckAndResizeMarksIfRequired ()
+		{
+			if (src != null && count != src.Count) {
+				count = src.Count;
+				BitArray newMarks = new BitArray (count);
+				for (var i = 0; i < Math.Min (marks.Length, newMarks.Length); i++) {
+					newMarks [i] = marks [i];
+				}
+				marks = newMarks;
+
+				len = GetMaxLengthItem ();
+			}
+		}
+
 		int GetMaxLengthItem ()
 		{
 			if (src == null || src?.Count == 0) {
@@ -896,7 +922,7 @@ namespace Terminal.Gui {
 		/// <inheritdoc/>
 		public bool IsMarked (int item)
 		{
-			if (item >= 0 && item < count)
+			if (item >= 0 && item < Count)
 				return marks [item];
 			return false;
 		}
@@ -904,7 +930,7 @@ namespace Terminal.Gui {
 		/// <inheritdoc/>
 		public void SetMark (int item, bool value)
 		{
-			if (item >= 0 && item < count)
+			if (item >= 0 && item < Count)
 				marks [item] = value;
 		}
 

+ 2 - 2
UICatalog/UICatalog.csproj

@@ -20,9 +20,9 @@
     <None Update="./Scenarios/Spinning_globe_dark_small.gif" CopyToOutputDirectory="PreserveNewest" />
   </ItemGroup>
   <ItemGroup>
-    <PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.20.1" />
+    <PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.0" />
     <PackageReference Include="SixLabors.ImageSharp" Version="3.1.4" />
-    <PackageReference Include="CsvHelper" Version="32.0.3" />
+    <PackageReference Include="CsvHelper" Version="33.0.1" />
     <PackageReference Include="Microsoft.DotNet.PlatformAbstractions" Version="3.1.6" />
   </ItemGroup>
   <ItemGroup>

+ 9 - 9
UnitTests/Application/ApplicationTests.cs

@@ -771,28 +771,28 @@ namespace Terminal.Gui.ApplicationTests {
 			Assert.True (win.HasFocus);
 			Assert.True (win2.CanFocus);
 			Assert.False (win2.HasFocus);
-			Assert.Equal ("win2", ((Window)top.Subviews [top.Subviews.Count - 1]).Title);
+			Assert.Equal ("win", ((Window)top.Subviews [^1]).Title);
 
 			top.ProcessKey (new KeyEvent (Key.CtrlMask | Key.Tab, new KeyModifiers ()));
 			Assert.True (win.CanFocus);
 			Assert.False (win.HasFocus);
 			Assert.True (win2.CanFocus);
 			Assert.True (win2.HasFocus);
-			Assert.Equal ("win2", ((Window)top.Subviews [top.Subviews.Count - 1]).Title);
+			Assert.Equal ("win2", ((Window)top.Subviews [^1]).Title);
 
 			top.ProcessKey (new KeyEvent (Key.CtrlMask | Key.Tab, new KeyModifiers ()));
 			Assert.True (win.CanFocus);
 			Assert.True (win.HasFocus);
 			Assert.True (win2.CanFocus);
 			Assert.False (win2.HasFocus);
-			Assert.Equal ("win", ((Window)top.Subviews [top.Subviews.Count - 1]).Title);
+			Assert.Equal ("win", ((Window)top.Subviews [^1]).Title);
 
 			win2.MouseEvent (new MouseEvent () { Flags = MouseFlags.Button1Pressed });
 			Assert.True (win.CanFocus);
 			Assert.False (win.HasFocus);
 			Assert.True (win2.CanFocus);
 			Assert.True (win2.HasFocus);
-			Assert.Equal ("win2", ((Window)top.Subviews [top.Subviews.Count - 1]).Title);
+			Assert.Equal ("win2", ((Window)top.Subviews [^1]).Title);
 			win2.MouseEvent (new MouseEvent () { Flags = MouseFlags.Button1Released });
 			Assert.Null (Toplevel.dragPosition);
 		}
@@ -816,35 +816,35 @@ namespace Terminal.Gui.ApplicationTests {
 			Assert.True (win.HasFocus);
 			Assert.True (win2.CanFocus);
 			Assert.False (win2.HasFocus);
-			Assert.Equal ("win2", ((Window)top.Subviews [top.Subviews.Count - 1]).Title);
+			Assert.Equal ("win", ((Window)top.Subviews [^1]).Title);
 
 			win.CanFocus = false;
 			Assert.False (win.CanFocus);
 			Assert.False (win.HasFocus);
 			Assert.True (win2.CanFocus);
 			Assert.True (win2.HasFocus);
-			Assert.Equal ("win2", ((Window)top.Subviews [top.Subviews.Count - 1]).Title);
+			Assert.Equal ("win2", ((Window)top.Subviews [^1]).Title);
 
 			top.ProcessKey (new KeyEvent (Key.CtrlMask | Key.Tab, new KeyModifiers ()));
 			Assert.True (win2.CanFocus);
 			Assert.False (win.HasFocus);
 			Assert.True (win2.CanFocus);
 			Assert.True (win2.HasFocus);
-			Assert.Equal ("win2", ((Window)top.Subviews [top.Subviews.Count - 1]).Title);
+			Assert.Equal ("win2", ((Window)top.Subviews [^1]).Title);
 
 			top.ProcessKey (new KeyEvent (Key.CtrlMask | Key.Tab, new KeyModifiers ()));
 			Assert.False (win.CanFocus);
 			Assert.False (win.HasFocus);
 			Assert.True (win2.CanFocus);
 			Assert.True (win2.HasFocus);
-			Assert.Equal ("win2", ((Window)top.Subviews [top.Subviews.Count - 1]).Title);
+			Assert.Equal ("win2", ((Window)top.Subviews [^1]).Title);
 
 			win.MouseEvent (new MouseEvent () { Flags = MouseFlags.Button1Pressed });
 			Assert.False (win.CanFocus);
 			Assert.False (win.HasFocus);
 			Assert.True (win2.CanFocus);
 			Assert.True (win2.HasFocus);
-			Assert.Equal ("win2", ((Window)top.Subviews [top.Subviews.Count - 1]).Title);
+			Assert.Equal ("win2", ((Window)top.Subviews [^1]).Title);
 			win2.MouseEvent (new MouseEvent () { Flags = MouseFlags.Button1Released });
 			Assert.Null (Toplevel.dragPosition);
 		}

+ 2 - 2
UnitTests/UnitTests.csproj

@@ -19,9 +19,9 @@
   </PropertyGroup>
   <ItemGroup>
     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
-    <PackageReference Include="ReportGenerator" Version="5.3.4" />
+    <PackageReference Include="ReportGenerator" Version="5.3.7" />
     <PackageReference Include="System.Collections" Version="4.3.0" />
-    <PackageReference Include="xunit" Version="2.8.1" />
+    <PackageReference Include="xunit" Version="2.9.0" />
     <PackageReference Include="xunit.runner.visualstudio" Version="2.8.1">
       <PrivateAssets>all</PrivateAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

+ 9 - 9
UnitTests/Views/ListViewTests.cs

@@ -300,16 +300,16 @@ namespace Terminal.Gui.ViewTests {
 			Assert.Equal (19, lv.SelectedItem);
 			TestHelpers.AssertDriverContentsWithFrameAre (@"
 ┌──────────┐
+│Line10    │
+│Line11    │
+│Line12    │
+│Line13    │
+│Line14    │
+│Line15    │
+│Line16    │
+│Line17    │
+│Line18    │
 │Line19    │
-│          │
-│          │
-│          │
-│          │
-│          │
-│          │
-│          │
-│          │
-│          │
 └──────────┘", output);
 
 			Assert.True (lv.ScrollUp (20));