Ver Fonte

Merge branch 'develop' of tig:gui-cs/Terminal.Gui into develop

Tig Kindel há 3 anos atrás
pai
commit
4791e7e8d2

+ 1 - 1
ReactiveExample/ReactiveExample.csproj

@@ -12,7 +12,7 @@
   </PropertyGroup>
   <ItemGroup>
     <PackageReference Include="ReactiveUI.Fody" Version="18.0.10" />
-    <PackageReference Include="ReactiveUI" Version="18.2.5" />
+    <PackageReference Include="ReactiveUI" Version="18.3.1" />
     <PackageReference Include="ReactiveMarbles.ObservableEvents.SourceGenerator" Version="1.1.4" PrivateAssets="all" />
   </ItemGroup>
   <ItemGroup>

+ 99 - 23
Terminal.Gui/Views/TextView.cs

@@ -770,7 +770,7 @@ namespace Terminal.Gui {
 		}
 
 		public TextModel WrapModel (int width, out int nRow, out int nCol, out int nStartRow, out int nStartCol,
-			int row = 0, int col = 0, int startRow = 0, int startCol = 0, int tabWidth = 0)
+			int row = 0, int col = 0, int startRow = 0, int startCol = 0, int tabWidth = 0, bool preserveTrailingSpaces = true)
 		{
 			frameWidth = width;
 
@@ -791,8 +791,7 @@ namespace Terminal.Gui {
 			for (int i = 0; i < Model.Count; i++) {
 				var line = Model.GetLine (i);
 				var wrappedLines = ToListRune (
-					TextFormatter.Format (ustring.Make (line), width,
-					TextAlignment.Left, true, true, tabWidth));
+					TextFormatter.Format (ustring.Make (line), width, TextAlignment.Left, true, preserveTrailingSpaces, tabWidth));
 				int sumColWidth = 0;
 				for (int j = 0; j < wrappedLines.Count; j++) {
 					var wrapLine = wrappedLines [j];
@@ -920,12 +919,13 @@ namespace Terminal.Gui {
 			var line = GetCurrentLine (modelRow);
 			var modelCol = GetModelColFromWrappedLines (row, col);
 
-			if (modelCol >= line.Count) {
+			if (modelCol > line.Count) {
 				Model.RemoveLine (modelRow);
 				RemoveAt (row, 0);
 				return false;
 			}
-			line.RemoveAt (modelCol);
+			if (modelCol < line.Count)
+				line.RemoveAt (modelCol);
 			if (line.Count > frameWidth || (row + 1 < wrappedModelLines.Count
 				&& wrappedModelLines [row + 1].ModelLine == modelRow)) {
 				return true;
@@ -997,13 +997,43 @@ namespace Terminal.Gui {
 		}
 
 		public void UpdateModel (TextModel model, out int nRow, out int nCol, out int nStartRow, out int nStartCol,
-			int row, int col, int startRow, int startCol)
+			int row, int col, int startRow, int startCol, bool preserveTrailingSpaces)
 		{
 			isWrapModelRefreshing = true;
 			Model = model;
-			WrapModel (frameWidth, out nRow, out nCol, out nStartRow, out nStartCol, row, col, startRow, startCol);
+			WrapModel (frameWidth, out nRow, out nCol, out nStartRow, out nStartCol, row, col, startRow, startCol, tabWidth: 0, preserveTrailingSpaces);
 			isWrapModelRefreshing = false;
 		}
+
+		public int GetWrappedLineColWidth (int line, int col, WordWrapManager wrapManager)
+		{
+			if (wrappedModelLines?.Count == 0)
+				return 0;
+
+			var wModelLines = wrapManager.wrappedModelLines;
+			var modelLine = GetModelLineFromWrappedLines (line);
+			var firstLine = wrappedModelLines.IndexOf (r => r.ModelLine == modelLine);
+			int modelCol = 0;
+			int colWidthOffset = 0;
+			int i = firstLine;
+
+			while (modelCol < col) {
+				var wLine = wrappedModelLines [i];
+				var wLineToCompare = wModelLines [i];
+
+				if (wLine.ModelLine != modelLine || wLineToCompare.ModelLine != modelLine)
+					break;
+
+				modelCol += Math.Max (wLine.ColWidth, wLineToCompare.ColWidth);
+				colWidthOffset += wLine.ColWidth - wLineToCompare.ColWidth;
+				if (modelCol > col) {
+					modelCol += col - modelCol;
+				}
+				i++;
+			}
+
+			return modelCol - colWidthOffset;
+		}
 	}
 
 	/// <summary>
@@ -1462,7 +1492,7 @@ namespace Terminal.Gui {
 				model.LoadString (value);
 				if (wordWrap) {
 					wrapManager = new WordWrapManager (model);
-					model = wrapManager.WrapModel (Math.Max (Frame.Width - 2, 0), out _, out _, out _, out _);
+					model = wrapManager.WrapModel (frameWidth, out _, out _, out _, out _);
 				}
 				TextChanged?.Invoke ();
 				SetNeedsDisplay ();
@@ -1484,12 +1514,12 @@ namespace Terminal.Gui {
 		void WrapTextModel ()
 		{
 			if (wordWrap && wrapManager != null) {
-				model = wrapManager.WrapModel (Math.Max (Frame.Width - 2, 0),
+				model = wrapManager.WrapModel (frameWidth,
 					out int nRow, out int nCol,
 					out int nStartRow, out int nStartCol,
 					currentRow, currentColumn,
 					selectionStartRow, selectionStartColumn,
-					tabWidth);
+					tabWidth, preserveTrailingSpaces: !ReadOnly);
 				currentRow = nRow;
 				currentColumn = nCol;
 				selectionStartRow = nStartRow;
@@ -1498,6 +1528,8 @@ namespace Terminal.Gui {
 			}
 		}
 
+		int frameWidth => Math.Max (Frame.Width - (RightOffset != 0 ? 2 : 1), 0);
+
 		/// <summary>
 		/// Gets or sets the top row.
 		/// </summary>
@@ -1506,7 +1538,14 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// Gets or sets the left column.
 		/// </summary>
-		public int LeftColumn { get => leftColumn; set => leftColumn = Math.Max (Math.Min (value, Maxlength - 1), 0); }
+		public int LeftColumn {
+			get => leftColumn;
+			set {
+				if (value > 0 && wordWrap)
+					return;
+				leftColumn = Math.Max (Math.Min (value, Maxlength - 1), 0);
+			}
+		}
 
 		/// <summary>
 		/// Gets the maximum visible length line.
@@ -1571,7 +1610,10 @@ namespace Terminal.Gui {
 				}
 
 				SetWrapModel ();
+				var savedCurrentColumn = CurrentColumn;
+				currentColumn = GetCurrentColumnReadOnyWrapModel (true);
 				var sel = GetRegion ();
+				currentColumn = savedCurrentColumn;
 				UpdateWrapModel ();
 				Adjust ();
 
@@ -1607,7 +1649,7 @@ namespace Terminal.Gui {
 				ResetPosition ();
 				if (wordWrap) {
 					wrapManager = new WordWrapManager (model);
-					model = wrapManager.WrapModel (Math.Max (Frame.Width - 2, 0), out _, out _, out _, out _);
+					model = wrapManager.WrapModel (frameWidth, out _, out _, out _, out _);
 				} else if (!wordWrap && wrapManager != null) {
 					model = wrapManager.Model;
 				}
@@ -1637,7 +1679,7 @@ namespace Terminal.Gui {
 		public int RightOffset {
 			get => rightOffset;
 			set {
-				if (currentColumn == GetCurrentLine ().Count && rightOffset > 0 && value == 0) {
+				if (!wordWrap && currentColumn == GetCurrentLine ().Count && rightOffset > 0 && value == 0) {
 					leftColumn = Math.Max (leftColumn - rightOffset, 0);
 				}
 				rightOffset = value;
@@ -1794,9 +1836,18 @@ namespace Terminal.Gui {
 		/// <param name="path">Path to the file to load.</param>
 		public bool LoadFile (string path)
 		{
-			var res = model.LoadFile (path);
-			ResetPosition ();
-			SetNeedsDisplay ();
+			bool res;
+			try {
+				SetWrapModel ();
+				res = model.LoadFile (path);
+				ResetPosition ();
+			} catch (Exception) {
+				throw;
+			} finally {
+				UpdateWrapModel ();
+				SetNeedsDisplay ();
+				Adjust ();
+			}
 			return res;
 		}
 
@@ -1939,8 +1990,15 @@ namespace Terminal.Gui {
 		public bool ReadOnly {
 			get => isReadOnly;
 			set {
-				isReadOnly = value;
-				SetNeedsDisplay ();
+				if (value != isReadOnly) {
+					isReadOnly = value;
+
+					SetWrapModel ();
+					currentColumn = GetCurrentColumnReadOnyWrapModel ();
+					UpdateWrapModel ();
+					SetNeedsDisplay ();
+					Adjust ();
+				}
 			}
 		}
 
@@ -2258,7 +2316,7 @@ namespace Terminal.Gui {
 				wrapManager.UpdateModel (model, out int nRow, out int nCol,
 					out int nStartRow, out int nStartCol,
 					currentRow, currentColumn,
-					selectionStartRow, selectionStartColumn);
+					selectionStartRow, selectionStartColumn, preserveTrailingSpaces: !ReadOnly);
 				currentRow = nRow;
 				currentColumn = nCol;
 				selectionStartRow = nStartRow;
@@ -2269,6 +2327,21 @@ namespace Terminal.Gui {
 				throw new InvalidOperationException ($"WordWrap settings was changed after the {currentCaller} call.");
 		}
 
+		int GetCurrentColumnReadOnyWrapModel (bool forcePreserveTrailingSpaces = false)
+		{
+			if (wordWrap) {
+				var wManager = new WordWrapManager (wrapManager.Model);
+				if (ReadOnly && !forcePreserveTrailingSpaces) {
+					wManager.WrapModel (frameWidth, out _, out _, out _, out _, preserveTrailingSpaces: false);
+				} else {
+					wManager.WrapModel (frameWidth, out _, out _, out _, out _, preserveTrailingSpaces: true);
+				}
+				var currentLine = wrapManager.GetWrappedLineColWidth (CurrentRow, CurrentColumn, wManager);
+				return currentLine;
+			}
+			return currentColumn;
+		}
+
 		///<inheritdoc/>
 		public override void Redraw (Rect bounds)
 		{
@@ -2542,8 +2615,8 @@ namespace Terminal.Gui {
 				leftColumn = TextModel.CalculateLeftColumn (line, leftColumn, currentColumn,
 					Frame.Width + offB.width - RightOffset, TabWidth);
 				need = true;
-			} else if (dSize.size + RightOffset < Frame.Width + offB.width
-				&& tSize.size + RightOffset < Frame.Width + offB.width) {
+			} else if ((wordWrap && leftColumn > 0) || (dSize.size + RightOffset < Frame.Width + offB.width
+				&& tSize.size + RightOffset < Frame.Width + offB.width)) {
 				leftColumn = 0;
 				need = true;
 			}
@@ -2597,7 +2670,7 @@ namespace Terminal.Gui {
 				topRow = Math.Max (idx > model.Count - 1 ? model.Count - 1 : idx, 0);
 			} else if (!wordWrap) {
 				var maxlength = model.GetMaxVisibleLine (topRow, topRow + Frame.Height + RightOffset, TabWidth);
-				leftColumn = Math.Max (idx > maxlength - 1 ? maxlength - 1 : idx, 0);
+				leftColumn = Math.Max (!wordWrap && idx > maxlength - 1 ? maxlength - 1 : idx, 0);
 			}
 			SetNeedsDisplay ();
 		}
@@ -3730,6 +3803,8 @@ namespace Terminal.Gui {
 		public void Copy ()
 		{
 			SetWrapModel ();
+			var savedCurrentColumn = CurrentColumn;
+			currentColumn = GetCurrentColumnReadOnyWrapModel (true);
 			if (selecting) {
 				SetClipboard (GetRegion ());
 				copyWithoutSelection = false;
@@ -3738,6 +3813,7 @@ namespace Terminal.Gui {
 				SetClipboard (ustring.Make (currentLine));
 				copyWithoutSelection = true;
 			}
+			currentColumn = savedCurrentColumn;
 			UpdateWrapModel ();
 			DoNeededAction ();
 		}
@@ -4292,7 +4368,7 @@ namespace Terminal.Gui {
 		/// </summary>
 		public void ClearHistoryChanges ()
 		{
-			historyText.Clear (Text);
+			historyText?.Clear (Text);
 		}
 	}
 

+ 3 - 1
Terminal.Gui/Windows/Wizard.cs

@@ -203,7 +203,9 @@ namespace Terminal.Gui {
 
 				base.Add (contentView);
 
-				helpTextView.ColorScheme = Colors.TopLevel;
+				helpTextView.ColorScheme = new ColorScheme () {  
+					Normal = new Attribute(Color.Gray, Color.DarkGray)
+				};
 				helpTextView.ReadOnly = true;
 				helpTextView.WordWrap = true;
 				base.Add (helpTextView);

+ 0 - 2
UICatalog/Scenarios/Editor.cs

@@ -524,11 +524,9 @@ namespace UICatalog.Scenarios {
 			item.Action += () => {
 				_textView.WordWrap = item.Checked = !item.Checked;
 				if (_textView.WordWrap) {
-					_scrollBar.AutoHideScrollBars = false;
 					_scrollBar.OtherScrollBarView.ShowScrollIndicator = false;
 					_textView.BottomOffset = 0;
 				} else {
-					_scrollBar.AutoHideScrollBars = true;
 					_textView.BottomOffset = 1;
 				}
 			};

+ 31 - 3
UnitTests/ScrollBarViewTests.cs

@@ -709,6 +709,7 @@ namespace Terminal.Gui.Views {
 			Assert.True (scrollBar.AutoHideScrollBars);
 			Assert.Equal (5, textView.Lines);
 			Assert.Equal (42, textView.Maxlength);
+			Assert.Equal (0, textView.LeftColumn);
 			Assert.Equal (0, scrollBar.Position);
 			Assert.Equal (0, scrollBar.OtherScrollBarView.Position);
 			var expected = @"
@@ -744,7 +745,8 @@ namespace Terminal.Gui.Views {
 			Assert.True (textView.WordWrap);
 			Assert.True (scrollBar.AutoHideScrollBars);
 			Assert.Equal (7, textView.Lines);
-			Assert.Equal (22, textView.Maxlength);
+			Assert.Equal (23, textView.Maxlength);
+			Assert.Equal (0, textView.LeftColumn);
 			Assert.Equal (0, scrollBar.Position);
 			Assert.Equal (0, scrollBar.OtherScrollBarView.Position);
 			expected = @"
@@ -752,8 +754,8 @@ namespace Terminal.Gui.Views {
 │This is the help text   │
 │for the Second Step.    │
 │                        │
-│Press the button to    
-│see a message box.      │
+│Press the button to see
+│ a message box.   
 │                        │
 │Enter name too.         │
 │                        │
@@ -772,6 +774,32 @@ namespace Terminal.Gui.Views {
 
 			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 26, 20), pos);
+
+			((FakeDriver)Application.Driver).SetBufferSize (10, 10);
+			Application.Refresh ();
+
+			Assert.True (textView.WordWrap);
+			Assert.True (scrollBar.AutoHideScrollBars);
+			Assert.Equal (19, textView.Lines);
+			Assert.Equal (7, textView.Maxlength);
+			Assert.Equal (0, textView.LeftColumn);
+			Assert.Equal (0, scrollBar.Position);
+			Assert.Equal (0, scrollBar.OtherScrollBarView.Position);
+			expected = @"
+┌ Test ──┐
+│This is▲│
+│ the   ┬│
+│help   ││
+│text   ┴│
+│for the░│
+│ Second░│
+│ Step. ░│
+│       ▼│
+└────────┘
+";
+
+			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 10, 10), pos);
 		}
 	}
 }

+ 115 - 10
UnitTests/TextViewTests.cs

@@ -1850,22 +1850,28 @@ namespace Terminal.Gui.Views {
 		[Fact]
 		public void LoadFile_Throws_If_File_Is_Null ()
 		{
+			var result = false;
 			var tv = new TextView ();
-			Assert.Throws<ArgumentNullException> (() => tv.LoadFile (null));
+			Assert.Throws<ArgumentNullException> (() => result = tv.LoadFile (null));
+			Assert.False (result);
 		}
 
 		[Fact]
 		public void LoadFile_Throws_If_File_Is_Empty ()
 		{
+			var result = false;
 			var tv = new TextView ();
-			Assert.Throws<ArgumentException> (() => tv.LoadFile (""));
+			Assert.Throws<ArgumentException> (() => result = tv.LoadFile (""));
+			Assert.False (result);
 		}
 
 		[Fact]
 		public void LoadFile_Throws_If_File_Not_Exist ()
 		{
+			var result = false;
 			var tv = new TextView ();
-			Assert.Throws<System.IO.FileNotFoundException> (() => tv.LoadFile ("blabla"));
+			Assert.Throws<System.IO.FileNotFoundException> (() => result = tv.LoadFile ("blabla"));
+			Assert.False (result);
 		}
 
 		[Fact]
@@ -1962,19 +1968,118 @@ namespace Terminal.Gui.Views {
 			tv.Redraw (tv.Bounds);
 
 			string expected = @"
-This is 
-the 
-first 
-line.
-This is 
-the 
-second 
+This is
+the first
+ line.
+This is
+the
+second
 line.
 ";
 
 			GraphViewTests.AssertDriverContentsAre (expected, output);
 		}
 
+		[Fact]
+		[AutoInitShutdown]
+		public void WordWrap_Deleting_Backwards ()
+		{
+			var tv = new TextView () {
+				Width = 5,
+				Height = 2,
+				WordWrap = true,
+				Text = "aaaa"
+			};
+			Application.Top.Add (tv);
+			Application.Begin (Application.Top);
+
+			Assert.Equal (new Point (0, 0), tv.CursorPosition);
+			Assert.Equal (0, tv.LeftColumn);
+			GraphViewTests.AssertDriverContentsAre (@"
+aaaa
+", output);
+
+			tv.CursorPosition = new Point (5, 0);
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ())));
+			Application.Refresh ();
+			Assert.Equal (0, tv.LeftColumn);
+			GraphViewTests.AssertDriverContentsAre (@"
+aaa
+", output);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ())));
+			Application.Refresh ();
+			Assert.Equal (0, tv.LeftColumn);
+			GraphViewTests.AssertDriverContentsAre (@"
+aa
+", output);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ())));
+			Application.Refresh ();
+			Assert.Equal (0, tv.LeftColumn);
+			GraphViewTests.AssertDriverContentsAre (@"
+a
+", output);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ())));
+			Application.Refresh ();
+			Assert.Equal (0, tv.LeftColumn);
+			GraphViewTests.AssertDriverContentsAre (@"
+
+", output);
+
+			Assert.True (tv.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ())));
+			Application.Refresh ();
+			Assert.Equal (0, tv.LeftColumn);
+			GraphViewTests.AssertDriverContentsAre (@"
+
+", output);
+		}
+
+		[Fact]
+		[InitShutdown]
+		public void WordWrap_ReadOnly_CursorPosition_SelectedText_Copy ()
+		{
+			//          0123456789
+			var text = "This is the first line.\nThis is the second line.\n";
+			var tv = new TextView () { Width = 11, Height = 9 };
+			tv.Text = text;
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}", tv.Text);
+			tv.WordWrap = true;
+
+			Application.Top.Add (tv);
+
+			tv.Redraw (tv.Bounds);
+			GraphViewTests.AssertDriverContentsAre (@"
+This is
+the first
+line.
+This is
+the second
+ line.
+", output);
+
+			tv.ReadOnly = true;
+			tv.CursorPosition = new Point (6, 2);
+			Assert.Equal (new Point (5, 2), tv.CursorPosition);
+			tv.Redraw (tv.Bounds);
+			GraphViewTests.AssertDriverContentsAre (@"
+This is
+the first
+line.
+This is
+the second
+line.
+", output);
+
+			tv.SelectionStartRow = 0;
+			tv.SelectionStartColumn = 0;
+			Assert.Equal ("This is the first line.", tv.SelectedText);
+
+			tv.Copy ();
+			Assert.Equal ("This is the first line.", Clipboard.Contents);
+		}
+
 		[Fact]
 		public void Internal_Tests ()
 		{