Browse Source

Merge pull request #1364 from BDisp/textview-load-file

Fixes #1314. TextView now exposes file exceptions from callers.
Charlie Kindel 4 years ago
parent
commit
dcc126942a
3 changed files with 337 additions and 129 deletions
  1. 38 57
      Terminal.Gui/Views/TextView.cs
  2. 55 19
      UICatalog/Scenarios/Editor.cs
  3. 244 53
      UnitTests/TextViewTests.cs

+ 38 - 57
Terminal.Gui/Views/TextView.cs

@@ -38,15 +38,10 @@ namespace Terminal.Gui {
 
 		public bool LoadFile (string file)
 		{
-			if (file == null)
-				throw new ArgumentNullException (nameof (file));
-			try {
-				FilePath = file;
-				var stream = File.OpenRead (file);
-			} catch {
-				return false;
-			}
-			LoadStream (File.OpenRead (file));
+			FilePath = file ?? throw new ArgumentNullException (nameof (file));
+
+			var stream = File.OpenRead (file);
+			LoadStream (stream);
 			return true;
 		}
 
@@ -54,12 +49,9 @@ namespace Terminal.Gui {
 		{
 			if (FilePath == null)
 				throw new ArgumentNullException (nameof (FilePath));
-			try {
-				FilePath = null;
-				lines = new List<List<Rune>> ();
-			} catch {
-				return false;
-			}
+
+			FilePath = null;
+			lines = new List<List<Rune>> ();
 			return true;
 		}
 
@@ -79,14 +71,21 @@ namespace Terminal.Gui {
 		{
 			var lines = new List<List<Rune>> ();
 			int start = 0, i = 0;
+			var hasCR = false;
+			// ASCII code 13 = Carriage Return.
 			// ASCII code 10 = Line Feed.
 			for (; i < content.Length; i++) {
+				if (content [i] == 13) {
+					hasCR = true;
+					continue;
+				}
 				if (content [i] == 10) {
 					if (i - start > 0)
-						lines.Add (ToRunes (content [start, i]));
+						lines.Add (ToRunes (content [start, hasCR ? i - 1 : i]));
 					else
 						lines.Add (ToRunes (ustring.Empty));
 					start = i + 1;
+					hasCR = false;
 				}
 			}
 			if (i - start >= 0)
@@ -109,16 +108,23 @@ namespace Terminal.Gui {
 			var buff = new BufferedStream (input);
 			int v;
 			var line = new List<byte> ();
+			var wasNewLine = false;
 			while ((v = buff.ReadByte ()) != -1) {
+				if (v == 13) {
+					continue;
+				}
 				if (v == 10) {
 					Append (line);
 					line.Clear ();
+					wasNewLine = true;
 					continue;
 				}
 				line.Add ((byte)v);
+				wasNewLine = false;
 			}
-			if (line.Count > 0)
+			if (line.Count > 0 || wasNewLine)
 				Append (line);
+			buff.Dispose ();
 		}
 
 		public void LoadString (ustring content)
@@ -200,7 +206,7 @@ namespace Terminal.Gui {
 			last = last < lines.Count ? last : lines.Count;
 			for (int i = first; i < last; i++) {
 				var line = GetLine (i);
-				var tabSum = line.Sum (r => r == '\t' ? tabWidth - 1 : 0);
+				var tabSum = line.Sum (r => r == '\t' ? Math.Max (tabWidth - 1, 0) : 0);
 				var l = line.Count + tabSum;
 				if (l > maxLength) {
 					maxLength = l;
@@ -230,7 +236,7 @@ namespace Terminal.Gui {
 			for (int i = start; i < t.Count; i++) {
 				var r = t [i];
 				size += Rune.ColumnWidth (r);
-				if (r == '\t' && tabWidth > 0) {
+				if (r == '\t') {
 					size += tabWidth + 1;
 				}
 				if (i == pX || (size > pX)) {
@@ -255,7 +261,7 @@ namespace Terminal.Gui {
 				var rune = t [i];
 				size += Rune.ColumnWidth (rune);
 				len += Rune.RuneLen (rune);
-				if (rune == '\t' && tabWidth > 0) {
+				if (rune == '\t') {
 					size += tabWidth + 1;
 					len += tabWidth - 1;
 				}
@@ -270,7 +276,7 @@ namespace Terminal.Gui {
 			{
 				s = Rune.ColumnWidth (r);
 				l = Rune.RuneLen (r);
-				if (r == '\t' && tWidth > 0) {
+				if (r == '\t') {
 					s += tWidth + 1;
 					l += tWidth - 1;
 				}
@@ -1126,6 +1132,7 @@ namespace Terminal.Gui {
 					Multiline = false;
 					AllowsTab = false;
 				}
+				SetNeedsDisplay ();
 			}
 		}
 
@@ -1143,12 +1150,10 @@ namespace Terminal.Gui {
 				if (allowsTab && !multiline) {
 					Multiline = true;
 				}
-				if (!allowsTab && multiline) {
-					Multiline = false;
-				}
 				if (!allowsTab && tabWidth > 0) {
 					tabWidth = 0;
 				}
+				SetNeedsDisplay ();
 			}
 		}
 
@@ -1159,12 +1164,10 @@ namespace Terminal.Gui {
 			get => tabWidth;
 			set {
 				tabWidth = Math.Max (value, 0);
-				if (tabWidth == 0 && AllowsTab) {
-					AllowsTab = false;
-				}
 				if (tabWidth > 0 && !AllowsTab) {
 					AllowsTab = true;
 				}
+				SetNeedsDisplay ();
 			}
 		}
 
@@ -1241,10 +1244,8 @@ namespace Terminal.Gui {
 		/// <param name="path">Path to the file to load.</param>
 		public bool LoadFile (string path)
 		{
-			if (path == null)
-				throw new ArgumentNullException (nameof (path));
-			ResetPosition ();
 			var res = model.LoadFile (path);
+			ResetPosition ();
 			SetNeedsDisplay ();
 			return res;
 		}
@@ -1256,10 +1257,8 @@ namespace Terminal.Gui {
 		/// <param name="stream">Stream to load the contents from.</param>
 		public void LoadStream (Stream stream)
 		{
-			if (stream == null)
-				throw new ArgumentNullException (nameof (stream));
-			ResetPosition ();
 			model.LoadStream (stream);
+			ResetPosition ();
 			SetNeedsDisplay ();
 		}
 
@@ -1269,8 +1268,8 @@ namespace Terminal.Gui {
 		/// <returns><c>true</c>, if stream was closed, <c>false</c> otherwise.</returns>
 		public bool CloseFile ()
 		{
-			ResetPosition ();
 			var res = model.CloseFile ();
+			ResetPosition ();
 			SetNeedsDisplay ();
 			return res;
 		}
@@ -1298,23 +1297,18 @@ namespace Terminal.Gui {
 				SetNeedsDisplay (new Rect (0, minRow, Frame.Width, maxRow));
 			}
 			var line = model.GetLine (currentRow);
-			var retreat = 0;
 			var col = 0;
 			if (line.Count > 0) {
-				retreat = Math.Max (SpecialRune (line [Math.Min (Math.Max (currentColumn - leftColumn - 1, 0), line.Count - 1)])
-					? 1 : 0, 0);
-
 				for (int idx = leftColumn; idx < line.Count; idx++) {
 					if (idx >= currentColumn)
 						break;
 					var cols = Rune.ColumnWidth (line [idx]);
-					if (line [idx] == '\t' && TabWidth > 0) {
+					if (line [idx] == '\t') {
 						cols += TabWidth + 1;
 					}
 					TextModel.SetCol (ref col, Frame.Width, cols);
 				}
 			}
-			col += retreat;
 			if ((col >= leftColumn || col < Frame.Width)
 				&& topRow <= currentRow && currentRow - topRow + BottomOffset < Frame.Height) {
 				ResetCursorVisibility ();
@@ -1701,10 +1695,10 @@ namespace Terminal.Gui {
 						&& HasFocus && idxCol < lineRuneCount) {
 						ColorUsed (line, idxCol);
 					} else {
-						ColorNormal (line,idxCol);
+						ColorNormal (line, idxCol);
 					}
 
-					if (rune == '\t' && TabWidth > 0) {
+					if (rune == '\t') {
 						cols += TabWidth + 1;
 						if (col + cols > right) {
 							cols = right - col;
@@ -1714,10 +1708,8 @@ namespace Terminal.Gui {
 								AddRune (col + i, row, ' ');
 							}
 						}
-					} else if (!SpecialRune (rune)) {
-						AddRune (col, row, rune);
 					} else {
-						col++;
+						AddRune (col, row, rune);
 					}
 					if (!TextModel.SetCol (ref col, bounds.Right, cols)) {
 						break;
@@ -1740,17 +1732,6 @@ namespace Terminal.Gui {
 			PositionCursor ();
 		}
 
-		bool SpecialRune (Rune rune)
-		{
-			switch (rune) {
-			case (uint)Key.Enter:
-			case 0xd:
-				return true;
-			default:
-				return false;
-			}
-		}
-
 		///<inheritdoc/>
 		public override bool CanFocus {
 			get => base.CanFocus;
@@ -1974,7 +1955,7 @@ namespace Terminal.Gui {
 			List<Rune> rest;
 
 			// if the user presses Left (without any control keys) and they are at the start of the text
-			if(kb.Key == Key.CursorLeft && currentColumn == 0 && currentRow == 0) {
+			if (kb.Key == Key.CursorLeft && currentColumn == 0 && currentRow == 0) {
 				// do not respond (this lets the key press fall through to navigation system - which usually changes focus backward)
 				return false;
 			}

+ 55 - 19
UICatalog/Scenarios/Editor.cs

@@ -35,6 +35,7 @@ namespace UICatalog {
 					new MenuItem ("_Open", "", () => Open()),
 					new MenuItem ("_Save", "", () => Save()),
 					new MenuItem ("_Save As", "", () => SaveAs()),
+					new MenuItem ("_Close", "", () => CloseFile()),
 					null,
 					new MenuItem ("_Quit", "", () => Quit()),
 				}),
@@ -67,7 +68,10 @@ namespace UICatalog {
 					new MenuItem ("  B_ox Fix", "", () => SetCursor(CursorVisibility.BoxFix)),
 					new MenuItem ("  U_nderline Fix","", () => SetCursor(CursorVisibility.UnderlineFix))
 				}),
-				new MenuBarItem ("Forma_t", CreateWrapChecked ())
+				new MenuBarItem ("Forma_t", new MenuItem [] {
+					CreateWrapChecked (),
+					CreateAllowsTabChecked ()
+				})
 			});
 			Top.Add (menu);
 
@@ -134,8 +138,11 @@ namespace UICatalog {
 
 			Win.KeyPress += (e) => {
 				if (winDialog != null && (e.KeyEvent.Key == Key.Esc
-					|| e.KeyEvent.Key.HasFlag (Key.Q | Key.CtrlMask))) {
+					|| e.KeyEvent.Key == (Key.Q | Key.CtrlMask))) {
 					DisposeWinDialog ();
+				} else if (e.KeyEvent.Key == (Key.Q | Key.CtrlMask)) {
+					Quit ();
+					e.Handled = true;
 				}
 			};
 		}
@@ -151,9 +158,9 @@ namespace UICatalog {
 		{
 		}
 
-		private void New ()
+		private void New (bool checkChanges = true)
 		{
-			if (!CanCloseFile ()) {
+			if (checkChanges && !CanCloseFile ()) {
 				return;
 			}
 
@@ -166,9 +173,9 @@ namespace UICatalog {
 		private void LoadFile ()
 		{
 			if (_fileName != null) {
-				// BUGBUG: #452 TextView.LoadFile keeps file open and provides no way of closing it
-				//_textView.LoadFile(_fileName);
-				_textView.Text = System.IO.File.ReadAllText (_fileName);
+				// FIXED: BUGBUG: #452 TextView.LoadFile keeps file open and provides no way of closing it
+				_textView.LoadFile (_fileName);
+				//_textView.Text = System.IO.File.ReadAllText (_fileName);
 				_originalText = _textView.Text.ToByteArray ();
 				Win.Title = _fileName;
 				_saved = true;
@@ -312,7 +319,7 @@ namespace UICatalog {
 			var d = new OpenDialog ("Open", "Open a file") { AllowsMultipleSelection = false };
 			Application.Run (d);
 
-			if (!d.Canceled) {
+			if (!d.Canceled && d.FilePaths.Count > 0) {
 				_fileName = d.FilePaths [0];
 				LoadFile ();
 			}
@@ -321,7 +328,7 @@ namespace UICatalog {
 		private bool Save ()
 		{
 			if (_fileName != null) {
-				// BUGBUG: #279 TextView does not know how to deal with \r\n, only \r 
+				// FIXED: BUGBUG: #279 TextView does not know how to deal with \r\n, only \r 
 				// As a result files saved on Windows and then read back will show invalid chars.
 				return SaveFile (Win.Title.ToString (), _fileName);
 			} else {
@@ -359,6 +366,7 @@ namespace UICatalog {
 				Win.Title = title;
 				_fileName = file;
 				System.IO.File.WriteAllText (_fileName, _textView.Text.ToString ());
+				_originalText = _textView.Text.ToByteArray ();
 				_saved = true;
 				MessageBox.Query ("Save File", "File was successfully saved.", "Ok");
 
@@ -370,15 +378,33 @@ namespace UICatalog {
 			return true;
 		}
 
+		private void CloseFile ()
+		{
+			if (!CanCloseFile ()) {
+				return;
+			}
+
+			try {
+				_textView.CloseFile ();
+				New (false);
+			} catch (Exception ex) {
+				MessageBox.ErrorQuery ("Error", ex.Message, "Ok");
+			}
+		}
+
 		private void Quit ()
 		{
+			if (!CanCloseFile ()) {
+				return;
+			}
+
 			Application.RequestStop ();
 		}
 
 		private void CreateDemoFile (string fileName)
 		{
 			var sb = new StringBuilder ();
-			// BUGBUG: #279 TextView does not know how to deal with \r\n, only \r
+			// FIXED: BUGBUG: #279 TextView does not know how to deal with \r\n, only \r
 			sb.Append ("Hello world.\n");
 			sb.Append ("This is a test of the Emergency Broadcast System.\n");
 
@@ -401,10 +427,11 @@ namespace UICatalog {
 			return new MenuItem [] { item };
 		}
 
-		private MenuItem [] CreateWrapChecked ()
+		private MenuItem CreateWrapChecked ()
 		{
-			var item = new MenuItem ();
-			item.Title = "Word Wrap";
+			var item = new MenuItem {
+				Title = "Word Wrap"
+			};
 			item.CheckType |= MenuItemCheckStyle.Checked;
 			item.Checked = false;
 			item.Action += () => {
@@ -419,7 +446,21 @@ namespace UICatalog {
 				}
 			};
 
-			return new MenuItem [] { item };
+			return item;
+		}
+
+		private MenuItem CreateAllowsTabChecked ()
+		{
+			var item = new MenuItem {
+				Title = "Allows Tab"
+			};
+			item.CheckType |= MenuItemCheckStyle.Checked;
+			item.Checked = true;
+			item.Action += () => {
+				_textView.AllowsTab = item.Checked = !item.Checked;
+			};
+
+			return item;
 		}
 
 		private void CreateFindReplace (bool isFind = true)
@@ -674,10 +715,5 @@ namespace UICatalog {
 
 			return d;
 		}
-
-		public override void Run ()
-		{
-			base.Run ();
-		}
 	}
 }

+ 244 - 53
UnitTests/TextViewTests.cs

@@ -1,13 +1,19 @@
 using System;
-using System.Linq;
 using System.Reflection;
 using Xunit;
+using Xunit.Abstractions;
 
 namespace Terminal.Gui.Views {
 	public class TextViewTests {
 		private static TextView _textView;
+		readonly ITestOutputHelper output;
 
-		// This class enables test functions annoated with the [InitShutdown] attribute
+		public TextViewTests (ITestOutputHelper output)
+		{
+			this.output = output;
+		}
+
+		// This class enables test functions annotated with the [InitShutdown] attribute
 		// to have a function called before the test function is called and after.
 		// 
 		// This is necessary because a) Application is a singleton and Init/Shutdown must be called
@@ -42,7 +48,8 @@ namespace Terminal.Gui.Views {
 			}
 		}
 
-		[Fact][InitShutdown]
+		[Fact]
+		[InitShutdown]
 		public void Changing_Selection_Or_CursorPosition_Update_SelectedLength_And_SelectedText ()
 		{
 			_textView.SelectionStartColumn = 2;
@@ -58,7 +65,8 @@ namespace Terminal.Gui.Views {
 			Assert.Equal ("B to jump between ", _textView.SelectedText);
 		}
 
-		[Fact][InitShutdown]
+		[Fact]
+		[InitShutdown]
 		public void Selection_With_Value_Less_Than_Zero_Changes_To_Zero ()
 		{
 			_textView.SelectionStartColumn = -2;
@@ -69,7 +77,8 @@ namespace Terminal.Gui.Views {
 			Assert.Equal ("", _textView.SelectedText);
 		}
 
-		[Fact][InitShutdown]
+		[Fact]
+		[InitShutdown]
 		public void Selection_With_Value_Greater_Than_Text_Length_Changes_To_Text_Length ()
 		{
 			_textView.CursorPosition = new Point (2, 0);
@@ -81,7 +90,8 @@ namespace Terminal.Gui.Views {
 			Assert.Equal ("B to jump between text fields.", _textView.SelectedText);
 		}
 
-		[Fact][InitShutdown]
+		[Fact]
+		[InitShutdown]
 		public void Selection_With_Empty_Text ()
 		{
 			_textView = new TextView ();
@@ -94,7 +104,8 @@ namespace Terminal.Gui.Views {
 			Assert.Equal ("", _textView.SelectedText);
 		}
 
-		[Fact][InitShutdown]
+		[Fact]
+		[InitShutdown]
 		public void Selection_And_CursorPosition_With_Value_Greater_Than_Text_Length_Changes_Both_To_Text_Length ()
 		{
 			_textView.CursorPosition = new Point (33, 2);
@@ -108,7 +119,8 @@ namespace Terminal.Gui.Views {
 			Assert.Equal ("", _textView.SelectedText);
 		}
 
-		[Fact][InitShutdown]
+		[Fact]
+		[InitShutdown]
 		public void CursorPosition_With_Value_Less_Than_Zero_Changes_To_Zero ()
 		{
 			_textView.CursorPosition = new Point (-1, -1);
@@ -118,7 +130,8 @@ namespace Terminal.Gui.Views {
 			Assert.Equal ("", _textView.SelectedText);
 		}
 
-		[Fact][InitShutdown]
+		[Fact]
+		[InitShutdown]
 		public void CursorPosition_With_Value_Greater_Than_Text_Length_Changes_To_Text_Length ()
 		{
 			_textView.CursorPosition = new Point (33, 1);
@@ -128,7 +141,8 @@ namespace Terminal.Gui.Views {
 			Assert.Equal ("", _textView.SelectedText);
 		}
 
-		[Fact][InitShutdown]
+		[Fact]
+		[InitShutdown]
 		public void WordForward_With_No_Selection ()
 		{
 			_textView.CursorPosition = new Point (0, 0);
@@ -190,7 +204,8 @@ namespace Terminal.Gui.Views {
 			}
 		}
 
-		[Fact][InitShutdown]
+		[Fact]
+		[InitShutdown]
 		public void WordBackward_With_No_Selection ()
 		{
 			_textView.CursorPosition = new Point (_textView.Text.Length, 0);
@@ -252,7 +267,8 @@ namespace Terminal.Gui.Views {
 			}
 		}
 
-		[Fact][InitShutdown]
+		[Fact]
+		[InitShutdown]
 		public void WordForward_With_Selection ()
 		{
 			_textView.CursorPosition = new Point (0, 0);
@@ -316,7 +332,8 @@ namespace Terminal.Gui.Views {
 			}
 		}
 
-		[Fact][InitShutdown]
+		[Fact]
+		[InitShutdown]
 		public void WordBackward_With_Selection ()
 		{
 			_textView.CursorPosition = new Point (_textView.Text.Length, 0);
@@ -380,7 +397,8 @@ namespace Terminal.Gui.Views {
 			}
 		}
 
-		[Fact][InitShutdown]
+		[Fact]
+		[InitShutdown]
 		public void WordForward_With_The_Same_Values_For_SelectedStart_And_CursorPosition_And_Not_Starting_At_Beginning_Of_The_Text ()
 		{
 			_textView.CursorPosition = new Point (10, 0);
@@ -428,7 +446,8 @@ namespace Terminal.Gui.Views {
 			}
 		}
 
-		[Fact][InitShutdown]
+		[Fact]
+		[InitShutdown]
 		public void WordBackward_With_The_Same_Values_For_SelectedStart_And_CursorPosition_And_Not_Starting_At_Beginning_Of_The_Text ()
 		{
 			_textView.CursorPosition = new Point (10, 0);
@@ -468,7 +487,8 @@ namespace Terminal.Gui.Views {
 			}
 		}
 
-		[Fact][InitShutdown]
+		[Fact]
+		[InitShutdown]
 		public void WordForward_With_No_Selection_And_With_More_Than_Only_One_Whitespace_And_With_Only_One_Letter ()
 		{
 			//                          1         2         3         4         5    
@@ -565,7 +585,8 @@ namespace Terminal.Gui.Views {
 			}
 		}
 
-		[Fact][InitShutdown]
+		[Fact]
+		[InitShutdown]
 		public void WordBackward_With_No_Selection_And_With_More_Than_Only_One_Whitespace_And_With_Only_One_Letter ()
 		{
 			//                          1         2         3         4         5    
@@ -670,7 +691,8 @@ namespace Terminal.Gui.Views {
 			}
 		}
 
-		[Fact][InitShutdown]
+		[Fact]
+		[InitShutdown]
 		public void WordBackward_Multiline_With_Selection ()
 		{
 			//		          4         3          2         1
@@ -784,7 +806,8 @@ namespace Terminal.Gui.Views {
 			}
 		}
 
-		[Fact][InitShutdown]
+		[Fact]
+		[InitShutdown]
 		public void WordForward_Multiline_With_Selection ()
 		{
 			//			    1         2          3         4
@@ -897,7 +920,8 @@ namespace Terminal.Gui.Views {
 			}
 		}
 
-		[Fact][InitShutdown]
+		[Fact]
+		[InitShutdown]
 		public void Kill_To_End_Delete_Forwards_And_Copy_To_The_Clipboard ()
 		{
 			_textView.Text = "This is the first line.\nThis is the second line.";
@@ -932,7 +956,8 @@ namespace Terminal.Gui.Views {
 			}
 		}
 
-		[Fact][InitShutdown]
+		[Fact]
+		[InitShutdown]
 		public void Kill_To_Start_Delete_Backwards_And_Copy_To_The_Clipboard ()
 		{
 			_textView.Text = "This is the first line.\nThis is the second line.";
@@ -968,7 +993,8 @@ namespace Terminal.Gui.Views {
 			}
 		}
 
-		[Fact][InitShutdown]
+		[Fact]
+		[InitShutdown]
 		public void Kill_Delete_WordForward ()
 		{
 			_textView.Text = "This is the first line.";
@@ -1011,7 +1037,8 @@ namespace Terminal.Gui.Views {
 			}
 		}
 
-		[Fact][InitShutdown]
+		[Fact]
+		[InitShutdown]
 		public void Kill_Delete_WordBackward ()
 		{
 			_textView.Text = "This is the first line.";
@@ -1055,7 +1082,8 @@ namespace Terminal.Gui.Views {
 			}
 		}
 
-		[Fact][InitShutdown]
+		[Fact]
+		[InitShutdown]
 		public void Kill_Delete_WordForward_Multiline ()
 		{
 			_textView.Text = "This is the first line.\nThis is the second line.";
@@ -1134,7 +1162,8 @@ namespace Terminal.Gui.Views {
 			}
 		}
 
-		[Fact][InitShutdown]
+		[Fact]
+		[InitShutdown]
 		public void Kill_Delete_WordBackward_Multiline ()
 		{
 			_textView.Text = "This is the first line.\nThis is the second line.";
@@ -1213,7 +1242,8 @@ namespace Terminal.Gui.Views {
 			}
 		}
 
-		[Fact][InitShutdown]
+		[Fact]
+		[InitShutdown]
 		public void Copy_Or_Cut_Null_If_No_Selection ()
 		{
 			_textView.SelectionStartColumn = 0;
@@ -1224,7 +1254,8 @@ namespace Terminal.Gui.Views {
 			Assert.Equal ("", _textView.SelectedText);
 		}
 
-		[Fact][InitShutdown]
+		[Fact]
+		[InitShutdown]
 		public void Copy_Or_Cut_Not_Null_If_Has_Selection ()
 		{
 			_textView.SelectionStartColumn = 20;
@@ -1236,7 +1267,8 @@ namespace Terminal.Gui.Views {
 			Assert.Equal ("", _textView.SelectedText);
 		}
 
-		[Fact][InitShutdown]
+		[Fact]
+		[InitShutdown]
 		public void Copy_Or_Cut_And_Paste_With_Selection ()
 		{
 			_textView.SelectionStartColumn = 20;
@@ -1254,7 +1286,8 @@ namespace Terminal.Gui.Views {
 			Assert.Equal ("TAB to jump between text fields.", _textView.Text);
 		}
 
-		[Fact][InitShutdown]
+		[Fact]
+		[InitShutdown]
 		public void Copy_Or_Cut_And_Paste_With_No_Selection ()
 		{
 			_textView.SelectionStartColumn = 20;
@@ -1282,7 +1315,8 @@ namespace Terminal.Gui.Views {
 			Assert.Equal ("TAB to jump between texttext fields.", _textView.Text);
 		}
 
-		[Fact][InitShutdown]
+		[Fact]
+		[InitShutdown]
 		public void Cut_Not_Allowed_If_ReadOnly_Is_True ()
 		{
 			_textView.ReadOnly = true;
@@ -1302,7 +1336,8 @@ namespace Terminal.Gui.Views {
 			Assert.Equal ("", _textView.SelectedText);
 		}
 
-		[Fact][InitShutdown]
+		[Fact]
+		[InitShutdown]
 		public void Paste_Always_Clear_The_SelectedText ()
 		{
 			_textView.SelectionStartColumn = 20;
@@ -1314,7 +1349,8 @@ namespace Terminal.Gui.Views {
 			Assert.Equal ("", _textView.SelectedText);
 		}
 
-		[Fact][InitShutdown]
+		[Fact]
+		[InitShutdown]
 		public void TextChanged_Event ()
 		{
 			_textView.TextChanged += () => {
@@ -1328,7 +1364,8 @@ namespace Terminal.Gui.Views {
 			Assert.Equal ("changed", _textView.Text);
 		}
 
-		[Fact][InitShutdown]
+		[Fact]
+		[InitShutdown]
 		public void Used_Is_True_By_Default ()
 		{
 			_textView.CursorPosition = new Point (10, 0);
@@ -1343,7 +1380,8 @@ namespace Terminal.Gui.Views {
 			Assert.Equal ("TAB to jumusedp between text fields.", _textView.Text);
 		}
 
-		[Fact][InitShutdown]
+		[Fact]
+		[InitShutdown]
 		public void Used_Is_False ()
 		{
 			_textView.Used = false;
@@ -1359,7 +1397,8 @@ namespace Terminal.Gui.Views {
 			Assert.Equal ("TAB to jumusedtween text fields.", _textView.Text);
 		}
 
-		[Fact][InitShutdown]
+		[Fact]
+		[InitShutdown]
 		public void Copy_Without_Selection ()
 		{
 			_textView.Text = "This is the first line.\nThis is the second line.\n";
@@ -1377,8 +1416,9 @@ namespace Terminal.Gui.Views {
 			Assert.Equal (new Point (3, 3), _textView.CursorPosition);
 		}
 
-		[Fact][InitShutdown]
-		public void TabWidth_Setting_To_Zero_Changes_AllowsTab_To_False_If_True ()
+		[Fact]
+		[InitShutdown]
+		public void TabWidth_Setting_To_Zero_Keeps_AllowsTab ()
 		{
 			Assert.Equal (4, _textView.TabWidth);
 			Assert.True (_textView.AllowsTab);
@@ -1386,23 +1426,24 @@ namespace Terminal.Gui.Views {
 			Assert.True (_textView.Multiline);
 			_textView.TabWidth = -1;
 			Assert.Equal (0, _textView.TabWidth);
-			Assert.False (_textView.AllowsTab);
-			Assert.False (_textView.AllowsReturn);
-			Assert.False (_textView.Multiline);
+			Assert.True (_textView.AllowsTab);
+			Assert.True (_textView.AllowsReturn);
+			Assert.True (_textView.Multiline);
 			_textView.ProcessKey (new KeyEvent (Key.Tab, new KeyModifiers ()));
-			Assert.Equal ("TAB to jump between text fields.", _textView.Text);
+			Assert.Equal ("\tTAB to jump between text fields.", _textView.Text);
 			_textView.ProcessKey (new KeyEvent (Key.BackTab, new KeyModifiers ()));
 			Assert.Equal ("TAB to jump between text fields.", _textView.Text);
 		}
 
-		[Fact][InitShutdown]
+		[Fact]
+		[InitShutdown]
 		public void AllowsTab_Setting_To_True_Changes_TabWidth_To_Default_If_It_Is_Zero ()
 		{
 			_textView.TabWidth = 0;
 			Assert.Equal (0, _textView.TabWidth);
-			Assert.False (_textView.AllowsTab);
-			Assert.False (_textView.AllowsReturn);
-			Assert.False (_textView.Multiline);
+			Assert.True (_textView.AllowsTab);
+			Assert.True (_textView.AllowsReturn);
+			Assert.True (_textView.Multiline);
 			_textView.AllowsTab = true;
 			Assert.True (_textView.AllowsTab);
 			Assert.Equal (4, _textView.TabWidth);
@@ -1410,7 +1451,8 @@ namespace Terminal.Gui.Views {
 			Assert.True (_textView.Multiline);
 		}
 
-		[Fact][InitShutdown]
+		[Fact]
+		[InitShutdown]
 		public void AllowsReturn_Setting_To_True_Changes_Multiline_To_True_If_It_Is_False ()
 		{
 			Assert.True (_textView.AllowsReturn);
@@ -1431,7 +1473,8 @@ namespace Terminal.Gui.Views {
 				"TAB to jump between text fields.", _textView.Text);
 		}
 
-		[Fact][InitShutdown]
+		[Fact]
+		[InitShutdown]
 		public void Multiline_Setting_Changes_AllowsReturn_And_AllowsTab_And_Height ()
 		{
 			Assert.True (_textView.Multiline);
@@ -1458,7 +1501,8 @@ namespace Terminal.Gui.Views {
 			Assert.Equal ("Dim.Absolute(10)", _textView.Height.ToString ());
 		}
 
-		[Fact][InitShutdown]
+		[Fact]
+		[InitShutdown]
 		public void Tab_Test_Follow_By_BackTab ()
 		{
 			Application.Top.Add (_textView);
@@ -1493,7 +1537,8 @@ namespace Terminal.Gui.Views {
 			Application.Run ();
 		}
 
-		[Fact][InitShutdown]
+		[Fact]
+		[InitShutdown]
 		public void BackTab_Test_Follow_By_Tab ()
 		{
 			Application.Top.Add (_textView);
@@ -1535,7 +1580,8 @@ namespace Terminal.Gui.Views {
 			Application.Run ();
 		}
 
-		[Fact][InitShutdown]
+		[Fact]
+		[InitShutdown]
 		public void Tab_Test_Follow_By_CursorLeft_And_Then_Follow_By_CursorRight ()
 		{
 			Application.Top.Add (_textView);
@@ -1577,7 +1623,8 @@ namespace Terminal.Gui.Views {
 			Application.Run ();
 		}
 
-		[Fact][InitShutdown]
+		[Fact]
+		[InitShutdown]
 		public void Tab_Test_Follow_By_BackTab_With_Text ()
 		{
 			Application.Top.Add (_textView);
@@ -1612,7 +1659,8 @@ namespace Terminal.Gui.Views {
 			Application.Run ();
 		}
 
-		[Fact][InitShutdown]
+		[Fact]
+		[InitShutdown]
 		public void Tab_Test_Follow_By_Home_And_Then_Follow_By_End_And_Then_Follow_By_BackTab_With_Text ()
 		{
 			Application.Top.Add (_textView);
@@ -1669,7 +1717,8 @@ namespace Terminal.Gui.Views {
 			Application.Run ();
 		}
 
-		[Fact][InitShutdown]
+		[Fact]
+		[InitShutdown]
 		public void Tab_Test_Follow_By_CursorLeft_And_Then_Follow_By_CursorRight_With_Text ()
 		{
 			Application.Top.Add (_textView);
@@ -1712,6 +1761,20 @@ namespace Terminal.Gui.Views {
 			Application.Run ();
 		}
 
+		[Fact]
+		public void TextView_MultiLine_But_Without_Tabs ()
+		{
+			var view = new TextView ();
+
+			// the default for TextView
+			Assert.True (view.Multiline);
+
+			view.AllowsTab = false;
+			Assert.False (view.AllowsTab);
+
+			Assert.True (view.Multiline);
+		}
+
 		private int GetLeftCol (int start)
 		{
 			var lines = _textView.Text.Split (Environment.NewLine);
@@ -1751,5 +1814,133 @@ namespace Terminal.Gui.Views {
 
 			return col;
 		}
+
+		[Fact]
+		public void LoadFile_Throws_If_File_Is_Null ()
+		{
+			var tv = new TextView ();
+			Assert.Throws<ArgumentNullException> (() => tv.LoadFile (null));
+		}
+
+		[Fact]
+		public void LoadFile_Throws_If_File_Is_Empty ()
+		{
+			var tv = new TextView ();
+			Assert.Throws<ArgumentException> (() => tv.LoadFile (""));
+		}
+
+		[Fact]
+		public void LoadFile_Throws_If_File_Not_Exist ()
+		{
+			var tv = new TextView ();
+			Assert.Throws<System.IO.FileNotFoundException> (() => tv.LoadFile ("blabla"));
+		}
+
+		[Fact]
+		public void LoadStream_Throws_If_Stream_Is_Null ()
+		{
+			var tv = new TextView ();
+			Assert.Throws<ArgumentNullException> (() => tv.LoadStream (null));
+		}
+
+		[Fact]
+		public void LoadStream_Stream_Is_Empty ()
+		{
+			var tv = new TextView ();
+			tv.LoadStream (new System.IO.MemoryStream ());
+			Assert.Equal ("", tv.Text);
+		}
+
+		[Fact]
+		public void LoadStream_CRLF ()
+		{
+			var text = "This is the first line.\r\nThis is the second line.\r\n";
+			var tv = new TextView ();
+			tv.LoadStream (new System.IO.MemoryStream (System.Text.Encoding.ASCII.GetBytes (text)));
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}", tv.Text);
+		}
+
+		[Fact]
+		public void LoadStream_LF ()
+		{
+			var text = "This is the first line.\nThis is the second line.\n";
+			var tv = new TextView ();
+			tv.LoadStream (new System.IO.MemoryStream (System.Text.Encoding.ASCII.GetBytes (text)));
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}", tv.Text);
+		}
+
+		[Fact]
+		public void StringToRunes_Slipts_CRLF ()
+		{
+			var text = "This is the first line.\r\nThis is the second line.\r\n";
+			var tv = new TextView ();
+			tv.Text = text;
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}", tv.Text);
+		}
+
+		[Fact]
+		public void StringToRunes_Slipts_LF ()
+		{
+			var text = "This is the first line.\nThis is the second line.\n";
+			var tv = new TextView ();
+			tv.Text = text;
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}", tv.Text);
+		}
+
+		[Fact]
+		public void CloseFile_Throws_If_FilePath_Is_Null ()
+		{
+			var tv = new TextView ();
+			Assert.Throws<ArgumentNullException> (() => tv.CloseFile ());
+		}
+
+		[Fact]
+		public void WordWrap_Gets_Sets ()
+		{
+			var tv = new TextView () { WordWrap = true };
+			Assert.True (tv.WordWrap);
+			tv.WordWrap = false;
+			Assert.False (tv.WordWrap);
+		}
+
+		[Fact]
+		public void WordWrap_True_Text_Always_Returns_Unwrapped ()
+		{
+			var text = "This is the first line.\nThis is the second line.\n";
+			var tv = new TextView () { Width = 10 };
+			tv.Text = text;
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}", tv.Text);
+			tv.WordWrap = true;
+			Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}", tv.Text);
+		}
+
+		[Fact]
+		[InitShutdown]
+		public void WordWrap_WrapModel_Output ()
+		{
+			//          0123456789
+			var text = "This is the first line.\nThis is the second line.\n";
+			var tv = new TextView () { Width = 10, Height = 10 };
+			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);
+
+			string expected = @"
+This is 
+the 
+first 
+line.
+This is 
+the 
+second 
+line.
+";
+
+			GraphViewTests.AssertDriverContentsAre (expected, output);
+		}
 	}
 }