Browse Source

Fixed FileDialog bounds warning

Tig Kindel 1 year ago
parent
commit
bb66759530
1 changed files with 1151 additions and 1190 deletions
  1. 1151 1190
      Terminal.Gui/Views/FileDialog.cs

+ 1151 - 1190
Terminal.Gui/Views/FileDialog.cs

@@ -1,1539 +1,1500 @@
 using System;
 using System.Collections.Generic;
-using System.Data;
 using System.IO;
 using System.IO.Abstractions;
 using System.Linq;
 using System.Text.RegularExpressions;
 using System.Threading;
 using System.Threading.Tasks;
-using System.Text;
 using Terminal.Gui.Resources;
-using static Terminal.Gui.ConfigurationManager;
 
-namespace Terminal.Gui {
+namespace Terminal.Gui; 
+
+/// <summary>
+/// Modal dialog for selecting files/directories. Has auto-complete and expandable
+/// navigation pane (Recent, Root drives etc).
+/// </summary>
+public class FileDialog : Dialog {
 	/// <summary>
-	/// Modal dialog for selecting files/directories. Has auto-complete and expandable
-	/// navigation pane (Recent, Root drives etc).
+	/// Gets the Path separators for the operating system
 	/// </summary>
-	public partial class FileDialog : Dialog {
+	internal static char [] Separators = {
+		System.IO.Path.AltDirectorySeparatorChar,
+		System.IO.Path.DirectorySeparatorChar
+	};
 
-		/// <summary>
-		/// Gets settings for controlling how visual elements behave.  Style changes should
-		/// be made before the <see cref="Dialog"/> is loaded and shown to the user for the
-		/// first time.
-		/// </summary>
-		public FileDialogStyle Style { get; }
+	/// <summary>
+	/// Characters to prevent entry into <see cref="tbPath"/>. Note that this is not using
+	/// <see cref="System.IO.Path.GetInvalidFileNameChars"/> because we do want to allow directory
+	/// separators, arrow keys etc.
+	/// </summary>
+	static readonly char [] badChars = {
+		'"', '<', '>', '|', '*', '?'
+	};
 
-		/// <summary>
-		/// The maximum number of results that will be collected
-		/// when searching before stopping.
-		/// </summary>
-		/// <remarks>
-		/// This prevents performance issues e.g. when searching
-		/// root of file system for a common letter (e.g. 'e').
-		/// </remarks>
-		[SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
-		public static int MaxSearchResults { get; set; } = 10000;
+	Dictionary<IDirectoryInfo, string> _treeRoots = new ();
+	MenuBarItem allowedTypeMenu;
+	MenuBar allowedTypeMenuBar;
+	MenuItem [] allowedTypeMenuItems;
+	readonly Button btnBack;
+	readonly Button btnCancel;
+	readonly Button btnForward;
+	readonly Button btnOk;
+	readonly Button btnToggleSplitterCollapse;
+	readonly Button btnUp;
 
-		/// <summary>
-		/// True if the file/folder must exist already to be selected.
-		/// This prevents user from entering the name of something that
-		/// doesn't exist. Defaults to false.
-		/// </summary>
-		public bool MustExist { get; set; }
+	int currentSortColumn;
 
-		/// <summary>
-		/// Gets the Path separators for the operating system
-		/// </summary>
-		internal static char [] Separators = new []
-		{
-			System.IO.Path.AltDirectorySeparatorChar,
-			System.IO.Path.DirectorySeparatorChar,
-		};
+	bool currentSortIsAsc = true;
 
-		/// <summary>
-		/// Characters to prevent entry into <see cref="tbPath"/>. Note that this is not using
-		/// <see cref="System.IO.Path.GetInvalidFileNameChars"/> because we do want to allow directory
-		/// separators, arrow keys etc.
-		/// </summary>
-		private static char [] badChars = new []
-		{
-			'"','<','>','|','*','?',
-		};
+	bool disposed;
+	string feedback;
+	readonly IFileSystem fileSystem;
 
-		/// <summary>
-		/// The UI selected <see cref="IAllowedType"/> from combo box. May be null.
-		/// </summary>
-		public IAllowedType CurrentFilter { get; private set; }
+	readonly FileDialogHistory history;
+	bool loaded;
 
-		private bool pushingState = false;
-		private bool loaded = false;
+	/// <summary>
+	/// Locking object for ensuring only a single <see cref="SearchState"/> executes at once.
+	/// </summary>
+	internal object onlyOneSearchLock = new ();
 
-		/// <summary>
-		/// Gets the currently open directory and known children presented in the dialog.
-		/// </summary>
-		internal FileDialogState State { get; private set; }
+	bool pushingState;
+	readonly SpinnerView spinnerView;
+	readonly TileView splitContainer;
 
-		/// <summary>
-		/// Locking object for ensuring only a single <see cref="SearchState"/> executes at once.
-		/// </summary>
-		internal object onlyOneSearchLock = new object ();
-
-		private bool disposed = false;
-		private IFileSystem fileSystem;
-		private TextField tbPath;
-
-		private FileDialogHistory history;
-
-		private TableView tableView;
-		private TreeView<IFileSystemInfo> treeView;
-		private TileView splitContainer;
-		private Button btnOk;
-		private Button btnCancel;
-		private Button btnToggleSplitterCollapse;
-		private Button btnForward;
-		private Button btnBack;
-		private Button btnUp;
-		private string feedback;
-		private TextField tbFind;
-		private SpinnerView spinnerView;
-		private MenuBar allowedTypeMenuBar;
-		private MenuBarItem allowedTypeMenu;
-		private MenuItem [] allowedTypeMenuItems;
-
-		private int currentSortColumn;
-
-		private bool currentSortIsAsc = true;
-		private Dictionary<IDirectoryInfo, string> _treeRoots = new Dictionary<IDirectoryInfo, string> ();
+	readonly TableView tableView;
+	readonly TextField tbFind;
+	readonly TextField tbPath;
+	readonly TreeView<IFileSystemInfo> treeView;
 
-		/// <summary>
-		/// Event fired when user attempts to confirm a selection (or multi selection).
-		/// Allows you to cancel the selection or undertake alternative behavior e.g.
-		/// open a dialog "File already exists, Overwrite? yes/no".
-		/// </summary>
-		public event EventHandler<FilesSelectedEventArgs> FilesSelected;
+	/// <summary>
+	/// Initializes a new instance of the <see cref="FileDialog"/> class.
+	/// </summary>
+	public FileDialog () : this (new FileSystem ()) { }
 
-		/// <summary>
-		/// Gets or sets behavior of the <see cref="FileDialog"/> when the user attempts
-		/// to delete a selected file(s).  Set to null to prevent deleting.
-		/// </summary>
-		/// <remarks>Ensure you use a try/catch block with appropriate
-		/// error handling (e.g. showing a <see cref="MessageBox"/></remarks>
-		public IFileOperations FileOperationsHandler { get; set; } = new DefaultFileOperations ();
+	/// <summary>
+	/// Initializes a new instance of the <see cref="FileDialog"/> class with
+	/// a custom <see cref="IFileSystem"/>.
+	/// </summary>
+	/// <remarks>This overload is mainly useful for testing.</remarks>
+	public FileDialog (IFileSystem fileSystem)
+	{
+		this.fileSystem = fileSystem;
+		Style = new FileDialogStyle (fileSystem);
+
+		btnOk = new Button (Style.OkButtonText) {
+			Y = Pos.AnchorEnd (1),
+			X = Pos.Function (CalculateOkButtonPosX),
+			IsDefault = true
+		};
+		btnOk.Clicked += (s, e) => Accept (true);
+		btnOk.KeyDown += (s, k) => {
+			NavigateIf (k, KeyCode.CursorLeft, btnCancel);
+			NavigateIf (k, KeyCode.CursorUp,   tableView);
+		};
 
-		/// <summary>
-		/// Initializes a new instance of the <see cref="FileDialog"/> class.
-		/// </summary>
-		public FileDialog () : this (new FileSystem ())
-		{
+		btnCancel = new Button (Strings.btnCancel) {
+			Y = Pos.AnchorEnd (1),
+			X = Pos.Right (btnOk) + 1
+		};
+		btnCancel.KeyDown += (s, k) => {
+			NavigateIf (k, KeyCode.CursorLeft,  btnToggleSplitterCollapse);
+			NavigateIf (k, KeyCode.CursorUp,    tableView);
+			NavigateIf (k, KeyCode.CursorRight, btnOk);
+		};
+		btnCancel.Clicked += (s, e) => {
+			Application.RequestStop ();
+		};
 
-		}
+		btnUp = new Button { X = 0, Y = 1, NoPadding = true };
+		btnUp.Text = GetUpButtonText ();
+		btnUp.Clicked += (s, e) => history.Up ();
 
-		/// <summary>
-		/// Initializes a new instance of the <see cref="FileDialog"/> class with
-		/// a custom <see cref="IFileSystem"/>.
-		/// </summary>
-		/// <remarks>This overload is mainly useful for testing.</remarks>
-		public FileDialog (IFileSystem fileSystem)
-		{
-			this.fileSystem = fileSystem;
-			Style = new FileDialogStyle (fileSystem);
+		btnBack = new Button { X = Pos.Right (btnUp) + 1, Y = 1, NoPadding = true };
+		btnBack.Text = GetBackButtonText ();
+		btnBack.Clicked += (s, e) => history.Back ();
 
-			this.btnOk = new Button (Style.OkButtonText) {
-				Y = Pos.AnchorEnd (1),
-				X = Pos.Function (CalculateOkButtonPosX),
-				IsDefault = true
-			};
-			this.btnOk.Clicked += (s, e) => this.Accept (true);
-			this.btnOk.KeyDown += (s, k) => {
-				this.NavigateIf (k, KeyCode.CursorLeft, this.btnCancel);
-				this.NavigateIf (k, KeyCode.CursorUp, this.tableView);
-			};
+		btnForward = new Button { X = Pos.Right (btnBack) + 1, Y = 1, NoPadding = true };
+		btnForward.Text = GetForwardButtonText ();
+		btnForward.Clicked += (s, e) => history.Forward ();
 
-			this.btnCancel = new Button (Strings.btnCancel) {
-				Y = Pos.AnchorEnd (1),
-				X = Pos.Right (btnOk) + 1
-			};
-			this.btnCancel.KeyDown += (s, k) => {
-				this.NavigateIf (k, KeyCode.CursorLeft, this.btnToggleSplitterCollapse);
-				this.NavigateIf (k, KeyCode.CursorUp, this.tableView);
-				this.NavigateIf (k, KeyCode.CursorRight, this.btnOk);
-			};
-			this.btnCancel.Clicked += (s, e) => {
-				Application.RequestStop ();
-			};
+		tbPath = new TextField {
+			Width = Dim.Fill (),
+			CaptionColor = new Color (Color.Black)
+		};
+		tbPath.KeyDown += (s, k) => {
 
-			this.btnUp = new Button () { X = 0, Y = 1, NoPadding = true };
-			btnUp.Text = GetUpButtonText ();
-			this.btnUp.Clicked += (s, e) => this.history.Up ();
+			ClearFeedback ();
 
-			this.btnBack = new Button () { X = Pos.Right (btnUp) + 1, Y = 1, NoPadding = true };
-			btnBack.Text = GetBackButtonText ();
-			this.btnBack.Clicked += (s, e) => this.history.Back ();
+			AcceptIf (k, KeyCode.Enter);
 
-			this.btnForward = new Button () { X = Pos.Right (btnBack) + 1, Y = 1, NoPadding = true };
-			btnForward.Text = GetForwardButtonText ();
-			this.btnForward.Clicked += (s, e) => this.history.Forward ();
+			SuppressIfBadChar (k);
+		};
 
-			this.tbPath = new TextField {
-				Width = Dim.Fill (0),
-				CaptionColor = new Color (Color.Black)
-			};
-			this.tbPath.KeyDown += (s, k) => {
+		tbPath.Autocomplete = new AppendAutocomplete (tbPath);
+		tbPath.Autocomplete.SuggestionGenerator = new FilepathSuggestionGenerator ();
 
-				ClearFeedback ();
+		splitContainer = new TileView {
+			X = 0,
+			Y = 2,
+			Width = Dim.Fill (),
+			Height = Dim.Fill (1)
+		};
 
-				this.AcceptIf (k, KeyCode.Enter);
+		Initialized += (s, e) => {
+			splitContainer.SetSplitterPos (0, 30);
+			splitContainer.Tiles.ElementAt (0).ContentView.Visible = false;
+		};
+		//			this.splitContainer.Border.BorderStyle = BorderStyle.None;
 
-				this.SuppressIfBadChar (k);
-			};
+		tableView = new TableView {
+			Width = Dim.Fill (),
+			Height = Dim.Fill (),
+			FullRowSelect = true,
+			CollectionNavigator = new FileDialogCollectionNavigator (this)
+		};
+		tableView.KeyBindings.Add (KeyCode.Space, Command.ToggleChecked);
+		tableView.MouseClick += OnTableViewMouseClick;
+		tableView.Style.InvertSelectedCellFirstCharacter = true;
+		Style.TableStyle = tableView.Style;
 
-			tbPath.Autocomplete = new AppendAutocomplete (tbPath);
-			tbPath.Autocomplete.SuggestionGenerator = new FilepathSuggestionGenerator ();
+		var nameStyle = Style.TableStyle.GetOrCreateColumnStyle (0);
+		nameStyle.MinWidth = 10;
+		nameStyle.ColorGetter = ColorGetter;
 
-			this.splitContainer = new TileView () {
-				X = 0,
-				Y = 2,
-				Width = Dim.Fill (0),
-				Height = Dim.Fill (1),
-			};
-			
-			Initialized += (s, e) => {
-				this.splitContainer.SetSplitterPos (0, 30);
-				this.splitContainer.Tiles.ElementAt (0).ContentView.Visible = false;
-			};
-			//			this.splitContainer.Border.BorderStyle = BorderStyle.None;
+		var sizeStyle = Style.TableStyle.GetOrCreateColumnStyle (1);
+		sizeStyle.MinWidth = 10;
+		sizeStyle.ColorGetter = ColorGetter;
 
-			this.tableView = new TableView {
-				Width = Dim.Fill (),
-				Height = Dim.Fill (),
-				FullRowSelect = true,
-				CollectionNavigator = new FileDialogCollectionNavigator (this)
-			};
-			this.tableView.KeyBindings.Add (KeyCode.Space, Command.ToggleChecked);
-			this.tableView.MouseClick += OnTableViewMouseClick;
-			tableView.Style.InvertSelectedCellFirstCharacter = true;
-			Style.TableStyle = tableView.Style;
-
-			var nameStyle = Style.TableStyle.GetOrCreateColumnStyle (0);
-			nameStyle.MinWidth = 10;
-			nameStyle.ColorGetter = this.ColorGetter;
-
-			var sizeStyle = Style.TableStyle.GetOrCreateColumnStyle (1);
-			sizeStyle.MinWidth = 10;
-			sizeStyle.ColorGetter = this.ColorGetter;
-
-			var dateModifiedStyle = Style.TableStyle.GetOrCreateColumnStyle (2);
-			dateModifiedStyle.MinWidth = 30;
-			dateModifiedStyle.ColorGetter = this.ColorGetter;
-
-			var typeStyle = Style.TableStyle.GetOrCreateColumnStyle (3);
-			typeStyle.MinWidth = 6;
-			typeStyle.ColorGetter = this.ColorGetter;
-
-			this.tableView.KeyDown += (s, k) => {
-				if (this.tableView.SelectedRow <= 0) {
-					this.NavigateIf (k, KeyCode.CursorUp, this.tbPath);
-				}
-				if (this.tableView.SelectedRow == this.tableView.Table.Rows - 1) {
-					this.NavigateIf (k, KeyCode.CursorDown, this.btnToggleSplitterCollapse);
-				}
+		var dateModifiedStyle = Style.TableStyle.GetOrCreateColumnStyle (2);
+		dateModifiedStyle.MinWidth = 30;
+		dateModifiedStyle.ColorGetter = ColorGetter;
 
-				if (splitContainer.Tiles.First ().ContentView.Visible && tableView.SelectedColumn == 0) {
-					this.NavigateIf (k, KeyCode.CursorLeft, this.treeView);
-				}
+		var typeStyle = Style.TableStyle.GetOrCreateColumnStyle (3);
+		typeStyle.MinWidth = 6;
+		typeStyle.ColorGetter = ColorGetter;
 
-				if (k.Handled) {
-					return;
-				}
-			};
+		tableView.KeyDown += (s, k) => {
+			if (tableView.SelectedRow <= 0) {
+				NavigateIf (k, KeyCode.CursorUp, tbPath);
+			}
+			if (tableView.SelectedRow == tableView.Table.Rows - 1) {
+				NavigateIf (k, KeyCode.CursorDown, btnToggleSplitterCollapse);
+			}
 
-			this.treeView = new TreeView<IFileSystemInfo> () {
-				Width = Dim.Fill (),
-				Height = Dim.Fill (),
-			};
+			if (splitContainer.Tiles.First ().ContentView.Visible && tableView.SelectedColumn == 0) {
+				NavigateIf (k, KeyCode.CursorLeft, treeView);
+			}
 
-			var fileDialogTreeBuilder = new FileSystemTreeBuilder ();
-			this.treeView.TreeBuilder = fileDialogTreeBuilder;
-			this.treeView.AspectGetter = this.AspectGetter;
-			this.Style.TreeStyle = treeView.Style;
+			if (k.Handled) { }
+		};
 
-			this.treeView.SelectionChanged += this.TreeView_SelectionChanged;
+		treeView = new TreeView<IFileSystemInfo> {
+			Width = Dim.Fill (),
+			Height = Dim.Fill ()
+		};
 
-			this.splitContainer.Tiles.ElementAt (0).ContentView.Add (this.treeView);
-			this.splitContainer.Tiles.ElementAt (1).ContentView.Add (this.tableView);
+		var fileDialogTreeBuilder = new FileSystemTreeBuilder ();
+		treeView.TreeBuilder = fileDialogTreeBuilder;
+		treeView.AspectGetter = AspectGetter;
+		Style.TreeStyle = treeView.Style;
 
-			this.btnToggleSplitterCollapse = new Button (GetToggleSplitterText (false)) {
-				Y = Pos.AnchorEnd (1),
-			};
-			this.btnToggleSplitterCollapse.Clicked += (s, e) => {
-				var tile = this.splitContainer.Tiles.ElementAt (0);
+		treeView.SelectionChanged += TreeView_SelectionChanged;
 
-				var newState = !tile.ContentView.Visible;
-				tile.ContentView.Visible = newState;
-				this.btnToggleSplitterCollapse.Text = GetToggleSplitterText (newState);
-				this.LayoutSubviews ();
-			};
+		splitContainer.Tiles.ElementAt (0).ContentView.Add (treeView);
+		splitContainer.Tiles.ElementAt (1).ContentView.Add (tableView);
 
-			tbFind = new TextField {
-				X = Pos.Right (this.btnToggleSplitterCollapse) + 1,
-				CaptionColor = new Color (Color.Black),
-				Width = 30,
-				Y = Pos.AnchorEnd (1),
-				HotKey = KeyCode.F | KeyCode.AltMask
-			};
-			spinnerView = new SpinnerView () {
-				X = Pos.Right (tbFind) + 1,
-				Y = Pos.AnchorEnd (1),
-				Visible = false,
-			};
+		btnToggleSplitterCollapse = new Button (GetToggleSplitterText (false)) {
+			Y = Pos.AnchorEnd (1)
+		};
+		btnToggleSplitterCollapse.Clicked += (s, e) => {
+			var tile = splitContainer.Tiles.ElementAt (0);
 
-			tbFind.TextChanged += (s, o) => RestartSearch ();
-			tbFind.KeyDown += (s, o) => {
-				if (o.KeyCode == KeyCode.Enter) {
-					RestartSearch ();
-					o.Handled = true;
-				}
+			var newState = !tile.ContentView.Visible;
+			tile.ContentView.Visible = newState;
+			btnToggleSplitterCollapse.Text = GetToggleSplitterText (newState);
+			LayoutSubviews ();
+		};
 
-				if (o.KeyCode == KeyCode.Esc) {
-					if (CancelSearch ()) {
-						o.Handled = true;
-					}
-				}
-				if (tbFind.CursorIsAtEnd ()) {
-					NavigateIf (o, KeyCode.CursorRight, btnCancel);
-				}
-				if (tbFind.CursorIsAtStart ()) {
-					NavigateIf (o, KeyCode.CursorLeft, btnToggleSplitterCollapse);
+		tbFind = new TextField {
+			X = Pos.Right (btnToggleSplitterCollapse) + 1,
+			CaptionColor = new Color (Color.Black),
+			Width = 30,
+			Y = Pos.AnchorEnd (1),
+			HotKey = KeyCode.F | KeyCode.AltMask
+		};
+		spinnerView = new SpinnerView {
+			X = Pos.Right (tbFind) + 1,
+			Y = Pos.AnchorEnd (1),
+			Visible = false
+		};
+
+		tbFind.TextChanged += (s, o) => RestartSearch ();
+		tbFind.KeyDown += (s, o) => {
+			if (o.KeyCode == KeyCode.Enter) {
+				RestartSearch ();
+				o.Handled = true;
+			}
+
+			if (o.KeyCode == KeyCode.Esc) {
+				if (CancelSearch ()) {
+					o.Handled = true;
 				}
-			};
+			}
+			if (tbFind.CursorIsAtEnd ()) {
+				NavigateIf (o, KeyCode.CursorRight, btnCancel);
+			}
+			if (tbFind.CursorIsAtStart ()) {
+				NavigateIf (o, KeyCode.CursorLeft, btnToggleSplitterCollapse);
+			}
+		};
 
-			this.tableView.Style.ShowHorizontalHeaderOverline = true;
-			this.tableView.Style.ShowVerticalCellLines = true;
-			this.tableView.Style.ShowVerticalHeaderLines = true;
-			this.tableView.Style.AlwaysShowHeaders = true;
-			this.tableView.Style.ShowHorizontalHeaderUnderline = true;
-			this.tableView.Style.ShowHorizontalScrollIndicators = true;
+		tableView.Style.ShowHorizontalHeaderOverline = true;
+		tableView.Style.ShowVerticalCellLines = true;
+		tableView.Style.ShowVerticalHeaderLines = true;
+		tableView.Style.AlwaysShowHeaders = true;
+		tableView.Style.ShowHorizontalHeaderUnderline = true;
+		tableView.Style.ShowHorizontalScrollIndicators = true;
 
-			this.history = new FileDialogHistory (this);
+		history = new FileDialogHistory (this);
 
-			this.tbPath.TextChanged += (s, e) => this.PathChanged ();
+		tbPath.TextChanged += (s, e) => PathChanged ();
 
-			this.tableView.CellActivated += this.CellActivate;
-			this.tableView.KeyUp += (s, k) => k.Handled = this.TableView_KeyUp (k);
-			this.tableView.SelectedCellChanged += this.TableView_SelectedCellChanged;
+		tableView.CellActivated += CellActivate;
+		tableView.KeyUp += (s, k) => k.Handled = TableView_KeyUp (k);
+		tableView.SelectedCellChanged += TableView_SelectedCellChanged;
 
-			this.tableView.KeyBindings.Add (KeyCode.Home, Command.TopHome);
-			this.tableView.KeyBindings.Add (KeyCode.End, Command.BottomEnd);
-			this.tableView.KeyBindings.Add (KeyCode.Home | KeyCode.ShiftMask, Command.TopHomeExtend);
-			this.tableView.KeyBindings.Add (KeyCode.End | KeyCode.ShiftMask, Command.BottomEndExtend);
+		tableView.KeyBindings.Add (KeyCode.Home,                     Command.TopHome);
+		tableView.KeyBindings.Add (KeyCode.End,                      Command.BottomEnd);
+		tableView.KeyBindings.Add (KeyCode.Home | KeyCode.ShiftMask, Command.TopHomeExtend);
+		tableView.KeyBindings.Add (KeyCode.End | KeyCode.ShiftMask,  Command.BottomEndExtend);
 
-			this.treeView.KeyDown += (s, k) => {
+		treeView.KeyDown += (s, k) => {
 
-				var selected = treeView.SelectedObject;
-				if (selected != null) {
-					if (!treeView.CanExpand (selected) || treeView.IsExpanded (selected)) {
-						this.NavigateIf (k, KeyCode.CursorRight, this.tableView);
-					} else
-					if (treeView.GetObjectRow (selected) == 0) {
-						this.NavigateIf (k, KeyCode.CursorUp, this.tbPath);
-					}
+			var selected = treeView.SelectedObject;
+			if (selected != null) {
+				if (!treeView.CanExpand (selected) || treeView.IsExpanded (selected)) {
+					NavigateIf (k, KeyCode.CursorRight, tableView);
+				} else if (treeView.GetObjectRow (selected) == 0) {
+					NavigateIf (k, KeyCode.CursorUp, tbPath);
 				}
+			}
 
-				if (k.Handled) {
-					return;
-				}
+			if (k.Handled) {
+				return;
+			}
 
-				k.Handled = this.TreeView_KeyDown (k);
+			k.Handled = TreeView_KeyDown (k);
 
-			};
+		};
 
-			this.AllowsMultipleSelection = false;
+		AllowsMultipleSelection = false;
+
+		UpdateNavigationVisibility ();
+
+		// Determines tab order
+		Add (btnToggleSplitterCollapse);
+		Add (tbFind);
+		Add (spinnerView);
+		Add (btnOk);
+		Add (btnCancel);
+		Add (btnUp);
+		Add (btnBack);
+		Add (btnForward);
+		Add (tbPath);
+		Add (splitContainer);
+	}
 
-			this.UpdateNavigationVisibility ();
+	/// <summary>
+	/// Gets settings for controlling how visual elements behave.  Style changes should
+	/// be made before the <see cref="Dialog"/> is loaded and shown to the user for the
+	/// first time.
+	/// </summary>
+	public FileDialogStyle Style { get; }
 
-			// Determines tab order
-			this.Add (this.btnToggleSplitterCollapse);
-			this.Add (this.tbFind);
-			this.Add (this.spinnerView);
-			this.Add (this.btnOk);
-			this.Add (this.btnCancel);
-			this.Add (this.btnUp);
-			this.Add (this.btnBack);
-			this.Add (this.btnForward);
-			this.Add (this.tbPath);
-			this.Add (this.splitContainer);
-		}
+	/// <summary>
+	/// The maximum number of results that will be collected
+	/// when searching before stopping.
+	/// </summary>
+	/// <remarks>
+	/// This prevents performance issues e.g. when searching
+	/// root of file system for a common letter (e.g. 'e').
+	/// </remarks>
+	[SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
+	public static int MaxSearchResults { get; set; } = 10000;
 
-		private int CalculateOkButtonPosX ()
-		{
-			if (!IsInitialized) {
-				return 0;
-			}
-			return Bounds.Width
-				- btnOk.Bounds.Width
-				- btnCancel.Bounds.Width
-				- 1
-				// TODO: Fiddle factor, seems the Bounds are wrong for someone
-				- 2;
-		}
+	/// <summary>
+	/// True if the file/folder must exist already to be selected.
+	/// This prevents user from entering the name of something that
+	/// doesn't exist. Defaults to false.
+	/// </summary>
+	public bool MustExist { get; set; }
 
-		private string AspectGetter (object o)
-		{
-			var fsi = (IFileSystemInfo)o;
+	/// <summary>
+	/// The UI selected <see cref="IAllowedType"/> from combo box. May be null.
+	/// </summary>
+	public IAllowedType CurrentFilter { get; private set; }
 
-			if (o is IDirectoryInfo dir && _treeRoots.ContainsKey (dir)) {
+	/// <summary>
+	/// Gets the currently open directory and known children presented in the dialog.
+	/// </summary>
+	internal FileDialogState State { get; private set; }
 
-				// Directory has a special name e.g. 'Pictures'
-				return _treeRoots [dir];
-			}
+	/// <summary>
+	/// Gets or sets behavior of the <see cref="FileDialog"/> when the user attempts
+	/// to delete a selected file(s).  Set to null to prevent deleting.
+	/// </summary>
+	/// <remarks>
+	/// Ensure you use a try/catch block with appropriate
+	/// error handling (e.g. showing a <see cref="MessageBox"/>
+	/// </remarks>
+	public IFileOperations FileOperationsHandler { get; set; } = new DefaultFileOperations ();
 
-			return (Style.IconProvider.GetIconWithOptionalSpace (fsi) + fsi.Name).Trim ();
+	/// <summary>
+	/// Gets or Sets which <see cref="System.IO.FileSystemInfo"/> type can be selected.
+	/// Defaults to <see cref="OpenMode.Mixed"/> (i.e. <see cref="DirectoryInfo"/> or
+	/// <see cref="FileInfo"/>).
+	/// </summary>
+	public OpenMode OpenMode { get; set; } = OpenMode.Mixed;
+
+	/// <summary>
+	/// Gets or Sets the selected path in the dialog. This is the result that should
+	/// be used if <see cref="AllowsMultipleSelection"/> is off and <see cref="Canceled"/>
+	/// is true.
+	/// </summary>
+	public string Path {
+		get => tbPath.Text;
+		set {
+			tbPath.Text = value;
+			tbPath.MoveEnd ();
 		}
+	}
 
-		private void OnTableViewMouseClick (object sender, MouseEventEventArgs e)
-		{
-			var clickedCell = this.tableView.ScreenToCell (e.MouseEvent.X, e.MouseEvent.Y, out int? clickedCol);
+	/// <summary>
+	/// Defines how the dialog matches files/folders when using the search
+	/// box. Provide a custom implementation if you want to tailor how matching
+	/// is performed.
+	/// </summary>
+	public ISearchMatcher SearchMatcher { get; set; } = new DefaultSearchMatcher ();
 
-			if (clickedCol != null) {
-				if (e.MouseEvent.Flags.HasFlag (MouseFlags.Button1Clicked)) {
+	/// <summary>
+	/// Gets or Sets a value indicating whether to allow selecting
+	/// multiple existing files/directories. Defaults to false.
+	/// </summary>
+	public bool AllowsMultipleSelection {
+		get => tableView.MultiSelect;
+		set => tableView.MultiSelect = value;
+	}
 
-					// left click in a header
-					this.SortColumn (clickedCol.Value);
-				} else if (e.MouseEvent.Flags.HasFlag (MouseFlags.Button3Clicked)) {
+	/// <summary>
+	/// Gets or Sets a collection of file types that the user can/must select. Only applies
+	/// when <see cref="OpenMode"/> is <see cref="OpenMode.File"/> or <see cref="OpenMode.Mixed"/>.
+	/// </summary>
+	/// <remarks>
+	/// <see cref="AllowedTypeAny"/> adds the option to select any type (*.*). If this
+	/// collection is empty then any type is supported and no Types drop-down is shown.
+	/// </remarks>
+	public List<IAllowedType> AllowedTypes { get; set; } = new ();
 
-					// right click in a header
-					this.ShowHeaderContextMenu (clickedCol.Value, e);
-				}
-			} else {
-				if (clickedCell != null && e.MouseEvent.Flags.HasFlag (MouseFlags.Button3Clicked)) {
+	/// <summary>
+	/// Gets a value indicating whether the <see cref="FileDialog"/> was closed
+	/// without confirming a selection.
+	/// </summary>
+	public bool Canceled { get; private set; } = true;
 
-					// right click in rest of table
-					this.ShowCellContextMenu (clickedCell, e);
-				}
-			}
-		}
+	/// <summary>
+	/// Gets all files/directories selected or an empty collection
+	/// <see cref="AllowsMultipleSelection"/> is <see langword="false"/> or <see cref="Canceled"/>.
+	/// </summary>
+	/// <remarks>If selecting only a single file/directory then you should use <see cref="Path"/> instead.</remarks>
+	public IReadOnlyList<string> MultiSelected { get; private set; }
+		= Enumerable.Empty<string> ().ToList ().AsReadOnly ();
 
-		private string GetForwardButtonText ()
-		{
-			return "-" + CM.Glyphs.RightArrow;
-		}
+	/// <summary>
+	/// Event fired when user attempts to confirm a selection (or multi selection).
+	/// Allows you to cancel the selection or undertake alternative behavior e.g.
+	/// open a dialog "File already exists, Overwrite? yes/no".
+	/// </summary>
+	public event EventHandler<FilesSelectedEventArgs> FilesSelected;
 
-		private string GetBackButtonText ()
-		{
-			return CM.Glyphs.LeftArrow + "-";
+	int CalculateOkButtonPosX ()
+	{
+		if (!IsInitialized || !btnOk.IsInitialized || !btnCancel.IsInitialized) {
+			return 0;
 		}
+		return Bounds.Width -
+		       btnOk.Bounds.Width -
+		       btnCancel.Bounds.Width -
+		       1
+		       // TODO: Fiddle factor, seems the Bounds are wrong for someone
+		       -
+		       2;
+	}
 
-		private string GetUpButtonText ()
-		{
-			return Style.UseUnicodeCharacters ? "◭" : "▲";
-		}
+	string AspectGetter (object o)
+	{
+		var fsi = (IFileSystemInfo)o;
 
-		private string GetToggleSplitterText (bool isExpanded)
-		{
-			return isExpanded ?
-				new string ((char)CM.Glyphs.LeftArrow.Value, 2) :
-				new string ((char)CM.Glyphs.RightArrow.Value, 2);
+		if (o is IDirectoryInfo dir && _treeRoots.ContainsKey (dir)) {
+
+			// Directory has a special name e.g. 'Pictures'
+			return _treeRoots [dir];
 		}
 
-		private void Delete ()
-		{
-			var toDelete = GetFocusedFiles ();
+		return (Style.IconProvider.GetIconWithOptionalSpace (fsi) + fsi.Name).Trim ();
+	}
 
-			if (toDelete != null && FileOperationsHandler.Delete (toDelete)) {
-				RefreshState ();
-			}
-		}
+	void OnTableViewMouseClick (object sender, MouseEventEventArgs e)
+	{
+		var clickedCell = tableView.ScreenToCell (e.MouseEvent.X, e.MouseEvent.Y, out var clickedCol);
 
-		private void Rename ()
-		{
-			var toRename = GetFocusedFiles ();
+		if (clickedCol != null) {
+			if (e.MouseEvent.Flags.HasFlag (MouseFlags.Button1Clicked)) {
 
-			if (toRename?.Length == 1) {
-				var newNamed = FileOperationsHandler.Rename (this.fileSystem, toRename.Single ());
+				// left click in a header
+				SortColumn (clickedCol.Value);
+			} else if (e.MouseEvent.Flags.HasFlag (MouseFlags.Button3Clicked)) {
 
-				if (newNamed != null) {
-					RefreshState ();
-					RestoreSelection (newNamed);
-				}
+				// right click in a header
+				ShowHeaderContextMenu (clickedCol.Value, e);
 			}
-		}
-		private void New ()
-		{
-			if (State != null) {
-				var created = FileOperationsHandler.New (this.fileSystem, State.Directory);
-				if (created != null) {
-					RefreshState ();
-					RestoreSelection (created);
-				}
+		} else {
+			if (clickedCell != null && e.MouseEvent.Flags.HasFlag (MouseFlags.Button3Clicked)) {
+
+				// right click in rest of table
+				ShowCellContextMenu (clickedCell, e);
 			}
 		}
-		private IFileSystemInfo [] GetFocusedFiles ()
-		{
+	}
 
-			if (!tableView.HasFocus || !tableView.CanFocus || FileOperationsHandler == null) {
-				return null;
-			}
+	string GetForwardButtonText () => "-" + Glyphs.RightArrow;
 
-			tableView.EnsureValidSelection ();
+	string GetBackButtonText () => Glyphs.LeftArrow + "-";
 
-			if (tableView.SelectedRow < 0) {
-				return null;
-			}
+	string GetUpButtonText () => Style.UseUnicodeCharacters ? "◭" : "▲";
 
-			return tableView.GetAllSelectedCells ()
-				.Select (c => c.Y)
-				.Distinct ()
-				.Select (RowToStats)
-				.Where (s => !s.IsParent)
-				.Select (d => d.FileSystemInfo)
-				.ToArray ();
-		}
+	string GetToggleSplitterText (bool isExpanded) => isExpanded ?
+		new string ((char)Glyphs.LeftArrow.Value,  2) :
+		new string ((char)Glyphs.RightArrow.Value, 2);
 
+	void Delete ()
+	{
+		var toDelete = GetFocusedFiles ();
 
-//		/// <inheritdoc/>
-//		public override bool OnHotKey (KeyEventArgs keyEvent)
-//		{
-//#if BROKE_IN_2927
-//			// BUGBUG: Ctrl-F is forward in a TextField. 
-//			if (this.NavigateIf (keyEvent, Key.Alt | Key.F, this.tbFind)) {
-//				return true;
-//			}
-//#endif
+		if (toDelete != null && FileOperationsHandler.Delete (toDelete)) {
+			RefreshState ();
+		}
+	}
 
-//			ClearFeedback ();
+	void Rename ()
+	{
+		var toRename = GetFocusedFiles ();
 
-//			if (allowedTypeMenuBar != null &&
-//				keyEvent.ConsoleDriverKey == Key.Tab &&
-//				allowedTypeMenuBar.IsMenuOpen) {
-//				allowedTypeMenuBar.CloseMenu (false, false, false);
-//			}
+		if (toRename?.Length == 1) {
+			var newNamed = FileOperationsHandler.Rename (fileSystem, toRename.Single ());
 
-//			return base.OnHotKey (keyEvent);
-//		}
-		private void RestartSearch ()
-		{
-			if (disposed || State?.Directory == null) {
-				return;
+			if (newNamed != null) {
+				RefreshState ();
+				RestoreSelection (newNamed);
 			}
+		}
+	}
 
-			if (State is SearchState oldSearch) {
-				oldSearch.Cancel ();
+	void New ()
+	{
+		if (State != null) {
+			var created = FileOperationsHandler.New (fileSystem, State.Directory);
+			if (created != null) {
+				RefreshState ();
+				RestoreSelection (created);
 			}
+		}
+	}
 
-			// user is clearing search terms
-			if (tbFind.Text == null || tbFind.Text.Length == 0) {
-
-				// Wait for search cancellation (if any) to finish
-				// then push the current dir state
-				lock (onlyOneSearchLock) {
-					PushState (new FileDialogState (State.Directory, this), false);
-				}
-				return;
-			}
+	IFileSystemInfo [] GetFocusedFiles ()
+	{
 
-			PushState (new SearchState (State?.Directory, this, tbFind.Text), true);
+		if (!tableView.HasFocus || !tableView.CanFocus || FileOperationsHandler == null) {
+			return null;
 		}
 
-		/// <inheritdoc/>
-		protected override void Dispose (bool disposing)
-		{
-			disposed = true;
-			base.Dispose (disposing);
+		tableView.EnsureValidSelection ();
 
-			CancelSearch ();
+		if (tableView.SelectedRow < 0) {
+			return null;
 		}
 
-		private bool CancelSearch ()
-		{
-			if (State is SearchState search) {
-				return search.Cancel ();
-			}
+		return tableView.GetAllSelectedCells ()
+				.Select (c => c.Y)
+				.Distinct ()
+				.Select (RowToStats)
+				.Where (s => !s.IsParent)
+				.Select (d => d.FileSystemInfo)
+				.ToArray ();
+	}
 
-			return false;
+
+	//		/// <inheritdoc/>
+	//		public override bool OnHotKey (KeyEventArgs keyEvent)
+	//		{
+	//#if BROKE_IN_2927
+	//			// BUGBUG: Ctrl-F is forward in a TextField. 
+	//			if (this.NavigateIf (keyEvent, Key.Alt | Key.F, this.tbFind)) {
+	//				return true;
+	//			}
+	//#endif
+
+	//			ClearFeedback ();
+
+	//			if (allowedTypeMenuBar != null &&
+	//				keyEvent.ConsoleDriverKey == Key.Tab &&
+	//				allowedTypeMenuBar.IsMenuOpen) {
+	//				allowedTypeMenuBar.CloseMenu (false, false, false);
+	//			}
+
+	//			return base.OnHotKey (keyEvent);
+	//		}
+	void RestartSearch ()
+	{
+		if (disposed || State?.Directory == null) {
+			return;
 		}
 
-		private void ClearFeedback ()
-		{
-			feedback = null;
+		if (State is SearchState oldSearch) {
+			oldSearch.Cancel ();
 		}
 
-		/// <summary>
-		/// Gets or Sets which <see cref="System.IO.FileSystemInfo"/> type can be selected.
-		/// Defaults to <see cref="OpenMode.Mixed"/> (i.e. <see cref="DirectoryInfo"/> or
-		/// <see cref="FileInfo"/>).
-		/// </summary>
-		public OpenMode OpenMode { get; set; } = OpenMode.Mixed;
+		// user is clearing search terms
+		if (tbFind.Text == null || tbFind.Text.Length == 0) {
 
-		/// <summary>
-		/// Gets or Sets the selected path in the dialog. This is the result that should
-		/// be used if <see cref="AllowsMultipleSelection"/> is off and <see cref="Canceled"/>
-		/// is true.
-		/// </summary>
-		public string Path {
-			get => this.tbPath.Text;
-			set {
-				this.tbPath.Text = value;
-				this.tbPath.MoveEnd ();
+			// Wait for search cancellation (if any) to finish
+			// then push the current dir state
+			lock (onlyOneSearchLock) {
+				PushState (new FileDialogState (State.Directory, this), false);
 			}
+			return;
 		}
 
-		/// <summary>
-		/// Defines how the dialog matches files/folders when using the search
-		/// box. Provide a custom implementation if you want to tailor how matching
-		/// is performed.
-		/// </summary>
-		public ISearchMatcher SearchMatcher { get; set; } = new DefaultSearchMatcher ();
+		PushState (new SearchState (State?.Directory, this, tbFind.Text), true);
+	}
 
-		/// <summary>
-		/// Gets or Sets a value indicating whether to allow selecting 
-		/// multiple existing files/directories. Defaults to false.
-		/// </summary>
-		public bool AllowsMultipleSelection {
-			get => this.tableView.MultiSelect;
-			set => this.tableView.MultiSelect = value;
+	/// <inheritdoc/>
+	protected override void Dispose (bool disposing)
+	{
+		disposed = true;
+		base.Dispose (disposing);
+
+		CancelSearch ();
+	}
+
+	bool CancelSearch ()
+	{
+		if (State is SearchState search) {
+			return search.Cancel ();
 		}
 
-		/// <summary>
-		/// Gets or Sets a collection of file types that the user can/must select. Only applies
-		/// when <see cref="OpenMode"/> is <see cref="OpenMode.File"/> or <see cref="OpenMode.Mixed"/>.
-		/// </summary>
-		/// <remarks><see cref="AllowedTypeAny"/> adds the option to select any type (*.*). If this
-		/// collection is empty then any type is supported and no Types drop-down is shown.</remarks> 
-		public List<IAllowedType> AllowedTypes { get; set; } = new List<IAllowedType> ();
+		return false;
+	}
 
-		/// <summary>
-		/// Gets a value indicating whether the <see cref="FileDialog"/> was closed
-		/// without confirming a selection.
-		/// </summary>
-		public bool Canceled { get; private set; } = true;
+	void ClearFeedback () => feedback = null;
 
-		/// <summary>
-		/// Gets all files/directories selected or an empty collection
-		/// <see cref="AllowsMultipleSelection"/> is <see langword="false"/> or <see cref="Canceled"/>.
-		/// </summary>
-		/// <remarks>If selecting only a single file/directory then you should use <see cref="Path"/> instead.</remarks>
-		public IReadOnlyList<string> MultiSelected { get; private set; }
-			= Enumerable.Empty<string> ().ToList ().AsReadOnly ();
+	/// <inheritdoc/>
+	public override void OnDrawContent (Rect contentArea)
+	{
+		base.OnDrawContent (contentArea);
 
-		/// <inheritdoc/>
-		public override void OnDrawContent (Rect contentArea)
-		{
-			base.OnDrawContent (contentArea);
+		if (!string.IsNullOrWhiteSpace (feedback)) {
+			var feedbackWidth = feedback.EnumerateRunes ().Sum (c => c.GetColumns ());
+			var feedbackPadLeft = (Bounds.Width - feedbackWidth) / 2 - 1;
 
-			if (!string.IsNullOrWhiteSpace (feedback)) {
-				var feedbackWidth = feedback.EnumerateRunes ().Sum (c => c.GetColumns ());
-				var feedbackPadLeft = ((Bounds.Width - feedbackWidth) / 2) - 1;
+			feedbackPadLeft = Math.Min (Bounds.Width, feedbackPadLeft);
+			feedbackPadLeft = Math.Max (0, feedbackPadLeft);
 
-				feedbackPadLeft = Math.Min (Bounds.Width, feedbackPadLeft);
-				feedbackPadLeft = Math.Max (0, feedbackPadLeft);
+			var feedbackPadRight = Bounds.Width - (feedbackPadLeft + feedbackWidth + 2);
+			feedbackPadRight = Math.Min (Bounds.Width, feedbackPadRight);
+			feedbackPadRight = Math.Max (0, feedbackPadRight);
 
-				var feedbackPadRight = Bounds.Width - (feedbackPadLeft + feedbackWidth + 2);
-				feedbackPadRight = Math.Min (Bounds.Width, feedbackPadRight);
-				feedbackPadRight = Math.Max (0, feedbackPadRight);
+			Move (0, Bounds.Height / 2);
 
-				Move (0, Bounds.Height / 2);
+			Driver.SetAttribute (new Attribute (Color.Red, ColorScheme.Normal.Background));
+			Driver.AddStr (new string (' ', feedbackPadLeft));
+			Driver.AddStr (feedback);
+			Driver.AddStr (new string (' ', feedbackPadRight));
+		}
+	}
 
-				Driver.SetAttribute (new Attribute (Color.Red, this.ColorScheme.Normal.Background));
-				Driver.AddStr (new string (' ', feedbackPadLeft));
-				Driver.AddStr (feedback);
-				Driver.AddStr (new string (' ', feedbackPadRight));
-			}
+	/// <inheritdoc/>
+	public override void OnLoaded ()
+	{
+		base.OnLoaded ();
+		if (loaded) {
+			return;
 		}
+		loaded = true;
 
-		/// <inheritdoc/>
-		public override void OnLoaded ()
-		{
-			base.OnLoaded ();
-			if (loaded) {
-				return;
-			}
-			loaded = true;
+		// May have been updated after instance was constructed
+		btnOk.Text = Style.OkButtonText;
+		btnCancel.Text = Style.CancelButtonText;
+		btnUp.Text = GetUpButtonText ();
+		btnBack.Text = GetBackButtonText ();
+		btnForward.Text = GetForwardButtonText ();
+		btnToggleSplitterCollapse.Text = GetToggleSplitterText (false);
 
-			// May have been updated after instance was constructed
-			this.btnOk.Text = Style.OkButtonText;
-			this.btnCancel.Text = Style.CancelButtonText;
-			this.btnUp.Text = this.GetUpButtonText ();
-			this.btnBack.Text = this.GetBackButtonText ();
-			this.btnForward.Text = this.GetForwardButtonText ();
-			this.btnToggleSplitterCollapse.Text = this.GetToggleSplitterText (false);
+		if (Style.FlipOkCancelButtonLayoutOrder) {
+			btnCancel.X = Pos.Function (CalculateOkButtonPosX);
+			btnOk.X = Pos.Right (btnCancel) + 1;
 
-			if (Style.FlipOkCancelButtonLayoutOrder) {
-				btnCancel.X = Pos.Function (this.CalculateOkButtonPosX);
-				btnOk.X = Pos.Right (btnCancel) + 1;
 
+			// Flip tab order too for consistency
+			var p1 = btnOk.TabIndex;
+			var p2 = btnCancel.TabIndex;
 
-				// Flip tab order too for consistency
-				var p1 = this.btnOk.TabIndex;
-				var p2 = this.btnCancel.TabIndex;
+			btnOk.TabIndex = p2;
+			btnCancel.TabIndex = p1;
+		}
 
-				this.btnOk.TabIndex = p2;
-				this.btnCancel.TabIndex = p1;
-			}
+		tbPath.Caption = Style.PathCaption;
+		tbFind.Caption = Style.SearchCaption;
 
-			tbPath.Caption = Style.PathCaption;
-			tbFind.Caption = Style.SearchCaption;
+		tbPath.Autocomplete.ColorScheme.Normal = new Attribute (Color.Black, tbPath.ColorScheme.Normal.Background);
 
-			tbPath.Autocomplete.ColorScheme.Normal = new Attribute (Color.Black, tbPath.ColorScheme.Normal.Background);
+		_treeRoots = Style.TreeRootGetter ();
+		Style.IconProvider.IsOpenGetter = treeView.IsExpanded;
 
-			_treeRoots = Style.TreeRootGetter ();
-			Style.IconProvider.IsOpenGetter = treeView.IsExpanded;
+		treeView.AddObjects (_treeRoots.Keys);
 
-			treeView.AddObjects (_treeRoots.Keys);
+		// if filtering on file type is configured then create the ComboBox and establish
+		// initial filtering by extension(s)
+		if (AllowedTypes.Any ()) {
 
-			// if filtering on file type is configured then create the ComboBox and establish
-			// initial filtering by extension(s)
-			if (this.AllowedTypes.Any ()) {
+			CurrentFilter = AllowedTypes [0];
 
-				this.CurrentFilter = this.AllowedTypes [0];
+			// Fiddle factor
+			var width = AllowedTypes.Max (a => a.ToString ().Length) + 6;
 
-				// Fiddle factor
-				var width = this.AllowedTypes.Max (a => a.ToString ().Length) + 6;
+			allowedTypeMenu = new MenuBarItem ("<placeholder>",
+				allowedTypeMenuItems = AllowedTypes.Select (
+									   (a, i) => new MenuItem (a.ToString (), null, () => {
+										   AllowedTypeMenuClicked (i);
+									   }))
+								   .ToArray ());
 
-				allowedTypeMenu = new MenuBarItem ("<placeholder>",
-					allowedTypeMenuItems = AllowedTypes.Select (
-						(a, i) => new MenuItem (a.ToString (), null, () => {
-							AllowedTypeMenuClicked (i);
-						}))
-					.ToArray ());
+			allowedTypeMenuBar = new MenuBar (new [] { allowedTypeMenu }) {
+				Width = width,
+				Y = 1,
+				X = Pos.AnchorEnd (width),
 
-				allowedTypeMenuBar = new MenuBar (new [] { allowedTypeMenu }) {
-					Width = width,
-					Y = 1,
-					X = Pos.AnchorEnd (width),
+				// TODO: Does not work, if this worked then we could tab to it instead
+				// of having to hit F9
+				CanFocus = true,
+				TabStop = true
+			};
+			AllowedTypeMenuClicked (0);
 
-					// TODO: Does not work, if this worked then we could tab to it instead
-					// of having to hit F9
-					CanFocus = true,
-					TabStop = true
-				};
-				AllowedTypeMenuClicked (0);
+			allowedTypeMenuBar.Enter += (s, e) => {
+				allowedTypeMenuBar.OpenMenu (0);
+			};
 
-				allowedTypeMenuBar.Enter += (s, e) => {
-					allowedTypeMenuBar.OpenMenu (0);
-				};
+			allowedTypeMenuBar.DrawContentComplete += (s, e) => {
 
-				allowedTypeMenuBar.DrawContentComplete += (s, e) => {
+				allowedTypeMenuBar.Move (e.Rect.Width - 1, 0);
+				Driver.AddRune (Glyphs.DownArrow);
 
-					allowedTypeMenuBar.Move (e.Rect.Width - 1, 0);
-					Driver.AddRune (CM.Glyphs.DownArrow);
+			};
 
-				};
+			Add (allowedTypeMenuBar);
+		}
 
-				this.Add (allowedTypeMenuBar);
-			}
+		// if no path has been provided
+		if (tbPath.Text.Length <= 0) {
+			Path = Environment.CurrentDirectory;
+		}
 
-			// if no path has been provided
-			if (this.tbPath.Text.Length <= 0) {
-				this.Path = Environment.CurrentDirectory;
-			}
+		// to streamline user experience and allow direct typing of paths
+		// with zero navigation we start with focus in the text box and any
+		// default/current path fully selected and ready to be overwritten
+		tbPath.FocusFirst ();
+		tbPath.SelectAll ();
 
-			// to streamline user experience and allow direct typing of paths
-			// with zero navigation we start with focus in the text box and any
-			// default/current path fully selected and ready to be overwritten
-			this.tbPath.FocusFirst ();
-			this.tbPath.SelectAll ();
+		if (string.IsNullOrEmpty (Title)) {
+			Title = GetDefaultTitle ();
+		}
+		LayoutSubviews ();
+	}
 
-			if (string.IsNullOrEmpty (Title)) {
-				this.Title = GetDefaultTitle ();
-			}
-			this.LayoutSubviews ();
+	/// <summary>
+	/// Gets a default dialog title, when <see cref="View.Title"/> is not set or empty,
+	/// result of the function will be shown.
+	/// </summary>
+	protected virtual string GetDefaultTitle ()
+	{
+		List<string> titleParts = new () {
+			Strings.fdOpen
+		};
+		if (MustExist) {
+			titleParts.Add (Strings.fdExisting);
 		}
-		/// <summary>
-		/// Gets a default dialog title, when <see cref="View.Title"/> is not set or empty, 
-		/// result of the function will be shown.
-		/// </summary>
-		protected virtual string GetDefaultTitle ()
-		{
-			List<string> titleParts = new () {
-				Strings.fdOpen
-			};
-			if (MustExist) {
-				titleParts.Add (Strings.fdExisting);
-			}
-			switch (OpenMode) {
-			case OpenMode.File:
-				titleParts.Add (Strings.fdFile);
-				break;
-			case OpenMode.Directory:
-				titleParts.Add (Strings.fdDirectory);
-				break;
-			}
-			return string.Join (' ', titleParts);
+		switch (OpenMode) {
+		case OpenMode.File:
+			titleParts.Add (Strings.fdFile);
+			break;
+		case OpenMode.Directory:
+			titleParts.Add (Strings.fdDirectory);
+			break;
 		}
+		return string.Join (' ', titleParts);
+	}
 
-		private void AllowedTypeMenuClicked (int idx)
-		{
+	void AllowedTypeMenuClicked (int idx)
+	{
 
-			var allow = AllowedTypes [idx];
-			for (int i = 0; i < AllowedTypes.Count; i++) {
-				allowedTypeMenuItems [i].Checked = i == idx;
-			}
-			allowedTypeMenu.Title = allow.ToString ();
+		var allow = AllowedTypes [idx];
+		for (var i = 0; i < AllowedTypes.Count; i++) {
+			allowedTypeMenuItems [i].Checked = i == idx;
+		}
+		allowedTypeMenu.Title = allow.ToString ();
 
-			this.CurrentFilter = allow;
+		CurrentFilter = allow;
 
-			this.tbPath.ClearAllSelection ();
-			this.tbPath.Autocomplete.ClearSuggestions ();
+		tbPath.ClearAllSelection ();
+		tbPath.Autocomplete.ClearSuggestions ();
 
-			if (this.State != null) {
-				this.State.RefreshChildren ();
-				this.WriteStateToTableView ();
-			}
+		if (State != null) {
+			State.RefreshChildren ();
+			WriteStateToTableView ();
 		}
+	}
 
-		private void SuppressIfBadChar (Key k)
-		{
-			// don't let user type bad letters
-			var ch = (char)k;
+	void SuppressIfBadChar (Key k)
+	{
+		// don't let user type bad letters
+		var ch = (char)k;
 
-			if (badChars.Contains (ch)) {
-				k.Handled = true;
-			}
+		if (badChars.Contains (ch)) {
+			k.Handled = true;
 		}
+	}
 
-		private bool TreeView_KeyDown (Key keyEvent)
-		{
-			if (this.treeView.HasFocus && Separators.Contains ((char)keyEvent)) {
-				this.tbPath.FocusFirst ();
+	bool TreeView_KeyDown (Key keyEvent)
+	{
+		if (treeView.HasFocus && Separators.Contains ((char)keyEvent)) {
+			tbPath.FocusFirst ();
 
-				// let that keystroke go through on the tbPath instead
-				return true;
-			}
-
-			return false;
+			// let that keystroke go through on the tbPath instead
+			return true;
 		}
 
-		private void AcceptIf (Key keyEvent, KeyCode isKey)
-		{
-			if (!keyEvent.Handled && keyEvent.KeyCode == isKey) {
-				keyEvent.Handled = true;
+		return false;
+	}
 
-				// User hit Enter in text box so probably wants the
-				// contents of the text box as their selection not
-				// whatever lingering selection is in TableView
-				this.Accept (false);
-			}
+	void AcceptIf (Key keyEvent, KeyCode isKey)
+	{
+		if (!keyEvent.Handled && keyEvent.KeyCode == isKey) {
+			keyEvent.Handled = true;
+
+			// User hit Enter in text box so probably wants the
+			// contents of the text box as their selection not
+			// whatever lingering selection is in TableView
+			Accept (false);
 		}
+	}
 
-		private void Accept (IEnumerable<FileSystemInfoStats> toMultiAccept)
-		{
-			if (!this.AllowsMultipleSelection) {
-				return;
-			}
+	void Accept (IEnumerable<FileSystemInfoStats> toMultiAccept)
+	{
+		if (!AllowsMultipleSelection) {
+			return;
+		}
 
-			// Don't include ".." (IsParent) in multiselections
-			this.MultiSelected = toMultiAccept
+		// Don't include ".." (IsParent) in multiselections
+		MultiSelected = toMultiAccept
 				.Where (s => !s.IsParent)
 				.Select (s => s.FileSystemInfo.FullName)
 				.ToList ().AsReadOnly ();
 
-			this.Path = this.MultiSelected.Count == 1 ? this.MultiSelected [0] : string.Empty;
+		Path = MultiSelected.Count == 1 ? MultiSelected [0] : string.Empty;
+
+		FinishAccept ();
+	}
 
-			FinishAccept ();
+	void Accept (IFileInfo f)
+	{
+		if (!IsCompatibleWithOpenMode (f.FullName, out var reason)) {
+			feedback = reason;
+			SetNeedsDisplay ();
+			return;
 		}
 
-		private void Accept (IFileInfo f)
-		{
-			if (!this.IsCompatibleWithOpenMode (f.FullName, out var reason)) {
+		Path = f.FullName;
+
+		if (AllowsMultipleSelection) {
+			MultiSelected = new List<string> { f.FullName }.AsReadOnly ();
+		}
+
+		FinishAccept ();
+	}
+
+	void Accept (bool allowMulti)
+	{
+		if (allowMulti && TryAcceptMulti ()) {
+			return;
+		}
+
+		if (!IsCompatibleWithOpenMode (tbPath.Text, out var reason)) {
+			if (reason != null) {
 				feedback = reason;
 				SetNeedsDisplay ();
-				return;
 			}
+			return;
+		}
 
-			this.Path = f.FullName;
+		FinishAccept ();
+	}
 
-			if (AllowsMultipleSelection) {
-				this.MultiSelected = new List<string> { f.FullName }.AsReadOnly ();
-			}
+	void FinishAccept ()
+	{
+		var e = new FilesSelectedEventArgs (this);
 
-			FinishAccept ();
-		}
+		FilesSelected?.Invoke (this, e);
 
-		private void Accept (bool allowMulti)
-		{
-			if (allowMulti && TryAcceptMulti ()) {
-				return;
-			}
+		if (e.Cancel) {
+			return;
+		}
 
-			if (!this.IsCompatibleWithOpenMode (this.tbPath.Text, out string reason)) {
-				if (reason != null) {
-					feedback = reason;
-					SetNeedsDisplay ();
-				}
-				return;
-			}
+		// if user uses Path selection mode (e.g. Enter in text box)
+		// then also copy to MultiSelected
+		if (AllowsMultipleSelection && !MultiSelected.Any ()) {
 
-			FinishAccept ();
+			MultiSelected = string.IsNullOrWhiteSpace (Path) ?
+				Enumerable.Empty<string> ().ToList ().AsReadOnly () :
+				new List<string> { Path }.AsReadOnly ();
 		}
 
-		private void FinishAccept ()
-		{
-			var e = new FilesSelectedEventArgs (this);
+		Canceled = false;
+		Application.RequestStop ();
+	}
 
-			this.FilesSelected?.Invoke (this, e);
+	bool NavigateIf (Key keyEvent, KeyCode isKey, View to)
+	{
+		if (keyEvent.KeyCode == isKey) {
 
-			if (e.Cancel) {
-				return;
+			to.FocusFirst ();
+			if (to == tbPath) {
+				tbPath.MoveEnd ();
 			}
+			return true;
+		}
 
-			// if user uses Path selection mode (e.g. Enter in text box)
-			// then also copy to MultiSelected
-			if (AllowsMultipleSelection && (!MultiSelected.Any ())) {
-
-				MultiSelected = string.IsNullOrWhiteSpace (Path) ?
-						Enumerable.Empty<string> ().ToList ().AsReadOnly () :
-						new List<string> () { Path }.AsReadOnly ();
-			}
+		return false;
+	}
 
-			this.Canceled = false;
-			Application.RequestStop ();
+	void TreeView_SelectionChanged (object sender, SelectionChangedEventArgs<IFileSystemInfo> e)
+	{
+		if (e.NewValue == null) {
+			return;
 		}
 
-		private bool NavigateIf (Key keyEvent, KeyCode isKey, View to)
-		{
-			if (keyEvent.KeyCode == isKey) {
+		Path = e.NewValue.FullName;
+	}
 
-				to.FocusFirst ();
-				if (to == tbPath) {
-					tbPath.MoveEnd ();
-				}
-				return true;
-			}
+	void UpdateNavigationVisibility ()
+	{
+		btnBack.Visible = history.CanBack ();
+		btnForward.Visible = history.CanForward ();
+		btnUp.Visible = history.CanUp ();
+	}
 
-			return false;
+	void TableView_SelectedCellChanged (object sender, SelectedCellChangedEventArgs obj)
+	{
+		if (!tableView.HasFocus || obj.NewRow == -1 || obj.Table.Rows == 0) {
+			return;
 		}
 
-		private void TreeView_SelectionChanged (object sender, SelectionChangedEventArgs<IFileSystemInfo> e)
-		{
-			if (e.NewValue == null) {
-				return;
-			}
-
-			this.Path = e.NewValue.FullName;
+		if (tableView.MultiSelect && tableView.MultiSelectedRegions.Any ()) {
+			return;
 		}
 
-		private void UpdateNavigationVisibility ()
-		{
-			this.btnBack.Visible = this.history.CanBack ();
-			this.btnForward.Visible = this.history.CanForward ();
-			this.btnUp.Visible = this.history.CanUp ();
-		}
+		var stats = RowToStats (obj.NewRow);
 
-		private void TableView_SelectedCellChanged (object sender, SelectedCellChangedEventArgs obj)
-		{
-			if (!this.tableView.HasFocus || obj.NewRow == -1 || obj.Table.Rows == 0) {
-				return;
-			}
+		if (stats == null) {
+			return;
+		}
+		IFileSystemInfo dest;
 
-			if (this.tableView.MultiSelect && this.tableView.MultiSelectedRegions.Any ()) {
-				return;
-			}
+		if (stats.IsParent) {
+			dest = State.Directory;
+		} else {
+			dest = stats.FileSystemInfo;
+		}
 
-			var stats = this.RowToStats (obj.NewRow);
+		try {
+			pushingState = true;
 
-			if (stats == null) {
-				return;
-			}
-			IFileSystemInfo dest;
+			Path = dest.FullName;
+			State.Selected = stats;
+			tbPath.Autocomplete.ClearSuggestions ();
 
-			if (stats.IsParent) {
-				dest = State.Directory;
-			} else {
-				dest = stats.FileSystemInfo;
-			}
+		} finally {
 
-			try {
-				this.pushingState = true;
+			pushingState = false;
+		}
+	}
 
-				this.Path = dest.FullName;
-				this.State.Selected = stats;
-				this.tbPath.Autocomplete.ClearSuggestions ();
+	bool TableView_KeyUp (Key keyEvent)
+	{
+		if (keyEvent.KeyCode == KeyCode.Backspace) {
+			return history.Back ();
+		}
+		if (keyEvent.KeyCode == (KeyCode.ShiftMask | KeyCode.Backspace)) {
+			return history.Forward ();
+		}
 
-			} finally {
+		if (keyEvent.KeyCode == KeyCode.Delete) {
 
-				this.pushingState = false;
-			}
+			Delete ();
+			return true;
 		}
 
-		private bool TableView_KeyUp (Key keyEvent)
-		{
-			if (keyEvent.KeyCode == KeyCode.Backspace) {
-				return this.history.Back ();
-			}
-			if (keyEvent.KeyCode == (KeyCode.ShiftMask | KeyCode.Backspace)) {
-				return this.history.Forward ();
-			}
+		if (keyEvent.KeyCode == (KeyCode.CtrlMask | KeyCode.R)) {
 
-			if (keyEvent.KeyCode == KeyCode.Delete) {
+			Rename ();
+			return true;
+		}
 
-				Delete ();
-				return true;
-			}
+		if (keyEvent.KeyCode == (KeyCode.CtrlMask | KeyCode.N)) {
+			New ();
+			return true;
+		}
 
-			if (keyEvent.KeyCode == (KeyCode.CtrlMask | KeyCode.R)) {
+		return false;
+	}
 
-				Rename ();
-				return true;
-			}
+	void CellActivate (object sender, CellActivatedEventArgs obj)
+	{
+		if (TryAcceptMulti ()) {
+			return;
+		}
 
-			if (keyEvent.KeyCode == (KeyCode.CtrlMask | KeyCode.N)) {
-				New ();
-				return true;
-			}
+		var stats = RowToStats (obj.Row);
 
-			return false;
+		if (stats.FileSystemInfo is IDirectoryInfo d) {
+			PushState (d, true);
+			return;
 		}
 
-		private void CellActivate (object sender, CellActivatedEventArgs obj)
-		{
-			if (TryAcceptMulti ()) {
-				return;
-			}
-
-			var stats = this.RowToStats (obj.Row);
+		if (stats.FileSystemInfo is IFileInfo f) {
+			Accept (f);
+		}
+	}
 
-			if (stats.FileSystemInfo is IDirectoryInfo d) {
-				this.PushState (d, true);
-				return;
-			}
+	bool TryAcceptMulti ()
+	{
+		var multi = MultiRowToStats ();
+		string reason = null;
 
-			if (stats.FileSystemInfo is IFileInfo f) {
-				this.Accept (f);
-			}
+		if (!multi.Any ()) {
+			return false;
 		}
 
-		private bool TryAcceptMulti ()
-		{
-			var multi = this.MultiRowToStats ();
-			string reason = null;
-
-			if (!multi.Any ()) {
-				return false;
-			}
+		if (multi.All (m => IsCompatibleWithOpenMode (
+			m.FileSystemInfo.FullName, out reason))) {
+			Accept (multi);
+			return true;
+		}
+		if (reason != null) {
+			feedback = reason;
+			SetNeedsDisplay ();
+		}
 
-			if (multi.All (m => this.IsCompatibleWithOpenMode (
-				m.FileSystemInfo.FullName, out reason))) {
-				this.Accept (multi);
-				return true;
-			} else {
-				if (reason != null) {
-					feedback = reason;
-					SetNeedsDisplay ();
-				}
+		return false;
+	}
 
-				return false;
-			}
+	/// <summary>
+	/// Returns true if there are no <see cref="AllowedTypes"/> or one of them agrees
+	/// that <paramref name="file"/> <see cref="IAllowedType.IsAllowed(string)"/>.
+	/// </summary>
+	/// <param name="file"></param>
+	/// <returns></returns>
+	public bool IsCompatibleWithAllowedExtensions (IFileInfo file)
+	{
+		// no restrictions
+		if (!AllowedTypes.Any ()) {
+			return true;
 		}
+		return MatchesAllowedTypes (file);
+	}
 
-		/// <summary>
-		/// Returns true if there are no <see cref="AllowedTypes"/> or one of them agrees
-		/// that <paramref name="file"/> <see cref="IAllowedType.IsAllowed(string)"/>.
-		/// </summary>
-		/// <param name="file"></param>
-		/// <returns></returns>
-		public bool IsCompatibleWithAllowedExtensions (IFileInfo file)
-		{
-			// no restrictions
-			if (!this.AllowedTypes.Any ()) {
-				return true;
-			}
-			return this.MatchesAllowedTypes (file);
+	bool IsCompatibleWithAllowedExtensions (string path)
+	{
+		// no restrictions
+		if (!AllowedTypes.Any ()) {
+			return true;
 		}
 
-		private bool IsCompatibleWithAllowedExtensions (string path)
-		{
-			// no restrictions
-			if (!this.AllowedTypes.Any ()) {
-				return true;
-			}
+		return AllowedTypes.Any (t => t.IsAllowed (path));
+	}
 
-			return this.AllowedTypes.Any (t => t.IsAllowed (path));
+	/// <summary>
+	/// Returns true if any <see cref="AllowedTypes"/> matches <paramref name="file"/>.
+	/// </summary>
+	/// <param name="file"></param>
+	/// <returns></returns>
+	bool MatchesAllowedTypes (IFileInfo file) => AllowedTypes.Any (t => t.IsAllowed (file.FullName));
+
+	bool IsCompatibleWithOpenMode (string s, out string reason)
+	{
+		reason = null;
+		if (string.IsNullOrWhiteSpace (s)) {
+			return false;
 		}
 
-		/// <summary>
-		/// Returns true if any <see cref="AllowedTypes"/> matches <paramref name="file"/>.
-		/// </summary>
-		/// <param name="file"></param>
-		/// <returns></returns>
-		private bool MatchesAllowedTypes (IFileInfo file)
-		{
-			return this.AllowedTypes.Any (t => t.IsAllowed (file.FullName));
+		if (!IsCompatibleWithAllowedExtensions (s)) {
+			reason = Style.WrongFileTypeFeedback;
+			return false;
 		}
-		private bool IsCompatibleWithOpenMode (string s, out string reason)
-		{
-			reason = null;
-			if (string.IsNullOrWhiteSpace (s)) {
+
+		switch (OpenMode) {
+		case OpenMode.Directory:
+			if (MustExist && !Directory.Exists (s)) {
+				reason = Style.DirectoryMustExistFeedback;
 				return false;
 			}
 
-			if (!this.IsCompatibleWithAllowedExtensions (s)) {
-				reason = Style.WrongFileTypeFeedback;
+			if (File.Exists (s)) {
+				reason = Style.FileAlreadyExistsFeedback;
 				return false;
 			}
+			return true;
+		case OpenMode.File:
 
-			switch (this.OpenMode) {
-			case OpenMode.Directory:
-				if (MustExist && !Directory.Exists (s)) {
-					reason = Style.DirectoryMustExistFeedback;
-					return false;
-				}
-
-				if (File.Exists (s)) {
-					reason = Style.FileAlreadyExistsFeedback;
-					return false;
-				}
-				return true;
-			case OpenMode.File:
-
-				if (MustExist && !File.Exists (s)) {
-					reason = Style.FileMustExistFeedback;
-					return false;
-				}
-				if (Directory.Exists (s)) {
-					reason = Style.DirectoryAlreadyExistsFeedback;
-					return false;
-				}
-				return true;
-			case OpenMode.Mixed:
-				if (MustExist && !File.Exists (s) && !Directory.Exists (s)) {
-					reason = Style.FileOrDirectoryMustExistFeedback;
-					return false;
-				}
-				return true;
-			default: throw new ArgumentOutOfRangeException (nameof (this.OpenMode));
+			if (MustExist && !File.Exists (s)) {
+				reason = Style.FileMustExistFeedback;
+				return false;
 			}
-		}
-
-		/// <summary>
-		/// Changes the dialog such that <paramref name="d"/> is being explored.
-		/// </summary>
-		/// <param name="d"></param>
-		/// <param name="addCurrentStateToHistory"></param>
-		/// <param name="setPathText"></param>
-		/// <param name="clearForward"></param>
-		/// <param name="pathText">Optional alternate string to set path to.</param>
-		internal void PushState (IDirectoryInfo d, bool addCurrentStateToHistory, bool setPathText = true, bool clearForward = true, string pathText = null)
-		{
-			// no change of state
-			if (d == this.State?.Directory) {
-				return;
+			if (Directory.Exists (s)) {
+				reason = Style.DirectoryAlreadyExistsFeedback;
+				return false;
 			}
-			if (d.FullName == this.State?.Directory.FullName) {
-				return;
+			return true;
+		case OpenMode.Mixed:
+			if (MustExist && !File.Exists (s) && !Directory.Exists (s)) {
+				reason = Style.FileOrDirectoryMustExistFeedback;
+				return false;
 			}
-
-			PushState (new FileDialogState (d, this), addCurrentStateToHistory, setPathText, clearForward, pathText);
+			return true;
+		default: throw new ArgumentOutOfRangeException (nameof (OpenMode));
 		}
+	}
 
-		private void RefreshState ()
-		{
-			State.RefreshChildren ();
-			PushState (State, false, false, false);
+	/// <summary>
+	/// Changes the dialog such that <paramref name="d"/> is being explored.
+	/// </summary>
+	/// <param name="d"></param>
+	/// <param name="addCurrentStateToHistory"></param>
+	/// <param name="setPathText"></param>
+	/// <param name="clearForward"></param>
+	/// <param name="pathText">Optional alternate string to set path to.</param>
+	internal void PushState (IDirectoryInfo d, bool addCurrentStateToHistory, bool setPathText = true, bool clearForward = true, string pathText = null)
+	{
+		// no change of state
+		if (d == State?.Directory) {
+			return;
+		}
+		if (d.FullName == State?.Directory.FullName) {
+			return;
 		}
 
-		private void PushState (FileDialogState newState, bool addCurrentStateToHistory, bool setPathText = true, bool clearForward = true, string pathText = null)
-		{
-			if (State is SearchState search) {
-				search.Cancel ();
-			}
+		PushState (new FileDialogState (d, this), addCurrentStateToHistory, setPathText, clearForward, pathText);
+	}
 
-			try {
-				this.pushingState = true;
+	void RefreshState ()
+	{
+		State.RefreshChildren ();
+		PushState (State, false, false, false);
+	}
 
-				// push the old state to history
-				if (addCurrentStateToHistory) {
-					this.history.Push (this.State, clearForward);
-				}
+	void PushState (FileDialogState newState, bool addCurrentStateToHistory, bool setPathText = true, bool clearForward = true, string pathText = null)
+	{
+		if (State is SearchState search) {
+			search.Cancel ();
+		}
 
-				this.tbPath.Autocomplete.ClearSuggestions ();
+		try {
+			pushingState = true;
 
-				if (pathText != null) {
-					this.Path = pathText;
-				} else
-				if (setPathText) {
-					this.Path = newState.Directory.FullName;
-				}
+			// push the old state to history
+			if (addCurrentStateToHistory) {
+				history.Push (State, clearForward);
+			}
 
-				this.State = newState;
-				this.tbPath.Autocomplete.GenerateSuggestions (
-					new AutocompleteFilepathContext (tbPath.Text, tbPath.CursorPosition, this.State));
+			tbPath.Autocomplete.ClearSuggestions ();
 
-				this.WriteStateToTableView ();
+			if (pathText != null) {
+				Path = pathText;
+			} else if (setPathText) {
+				Path = newState.Directory.FullName;
+			}
 
-				if (clearForward) {
-					this.history.ClearForward ();
-				}
+			State = newState;
+			tbPath.Autocomplete.GenerateSuggestions (
+				new AutocompleteFilepathContext (tbPath.Text, tbPath.CursorPosition, State));
 
-				this.tableView.RowOffset = 0;
-				this.tableView.SelectedRow = 0;
+			WriteStateToTableView ();
 
-				this.SetNeedsDisplay ();
-				this.UpdateNavigationVisibility ();
+			if (clearForward) {
+				history.ClearForward ();
+			}
 
-			} finally {
+			tableView.RowOffset = 0;
+			tableView.SelectedRow = 0;
 
-				this.pushingState = false;
-			}
-			ClearFeedback ();
-		}
+			SetNeedsDisplay ();
+			UpdateNavigationVisibility ();
 
-		private void WriteStateToTableView ()
-		{
-			if (this.State == null) {
-				return;
-			}
-			this.tableView.Table = new FileDialogTableSource (this, this.State, this.Style, currentSortColumn, currentSortIsAsc);
+		} finally {
 
-			this.ApplySort ();
-			this.tableView.Update ();
+			pushingState = false;
 		}
+		ClearFeedback ();
+	}
 
-		private ColorScheme ColorGetter (CellColorGetterArgs args)
-		{
-			var stats = this.RowToStats (args.RowIndex);
-
-			if (!Style.UseColors) {
-				return tableView.ColorScheme;
-			}
+	void WriteStateToTableView ()
+	{
+		if (State == null) {
+			return;
+		}
+		tableView.Table = new FileDialogTableSource (this, State, Style, currentSortColumn, currentSortIsAsc);
 
+		ApplySort ();
+		tableView.Update ();
+	}
 
-			var color = Style.ColorProvider.GetColor (stats.FileSystemInfo) ?? new Color (Color.White);
-			var black = new Color (Color.Black);
+	ColorScheme ColorGetter (CellColorGetterArgs args)
+	{
+		var stats = RowToStats (args.RowIndex);
 
-			// TODO: Add some kind of cache for this
-			return new ColorScheme {
-				Normal = new Attribute (color, black),
-				HotNormal = new Attribute (color, black),
-				Focus = new Attribute (black, color),
-				HotFocus = new Attribute (black, color),
-			};
+		if (!Style.UseColors) {
+			return tableView.ColorScheme;
 		}
 
-		/// <summary>
-		/// If <see cref="TableView.MultiSelect"/> is this returns a union of all
-		/// <see cref="FileSystemInfoStats"/> in the selection.
-		/// </summary>
-		/// <returns></returns>
-		private IEnumerable<FileSystemInfoStats> MultiRowToStats ()
-		{
-			var toReturn = new HashSet<FileSystemInfoStats> ();
-
-			if (this.AllowsMultipleSelection && this.tableView.MultiSelectedRegions.Any ()) {
 
-				foreach (var p in this.tableView.GetAllSelectedCells ()) {
+		var color = Style.ColorProvider.GetColor (stats.FileSystemInfo) ?? new Color (Color.White);
+		var black = new Color (Color.Black);
 
-					var add = this.State?.Children [p.Y];
-					if (add != null) {
-						toReturn.Add (add);
-					}
-				}
-			}
+		// TODO: Add some kind of cache for this
+		return new ColorScheme {
+			Normal = new Attribute (color,    black),
+			HotNormal = new Attribute (color, black),
+			Focus = new Attribute (black,     color),
+			HotFocus = new Attribute (black,  color)
+		};
+	}
 
-			return toReturn;
-		}
-		private FileSystemInfoStats RowToStats (int rowIndex)
-		{
-			return this.State?.Children [rowIndex];
-		}
+	/// <summary>
+	/// If <see cref="TableView.MultiSelect"/> is this returns a union of all
+	/// <see cref="FileSystemInfoStats"/> in the selection.
+	/// </summary>
+	/// <returns></returns>
+	IEnumerable<FileSystemInfoStats> MultiRowToStats ()
+	{
+		var toReturn = new HashSet<FileSystemInfoStats> ();
 
-		private void PathChanged ()
-		{
-			// avoid re-entry
-			if (this.pushingState) {
-				return;
-			}
+		if (AllowsMultipleSelection && tableView.MultiSelectedRegions.Any ()) {
 
-			var path = this.tbPath.Text;
+			foreach (var p in tableView.GetAllSelectedCells ()) {
 
-			if (string.IsNullOrWhiteSpace (path)) {
-				return;
+				var add = State?.Children [p.Y];
+				if (add != null) {
+					toReturn.Add (add);
+				}
 			}
+		}
 
-			var dir = this.StringToDirectoryInfo (path);
+		return toReturn;
+	}
 
-			if (dir.Exists) {
-				this.PushState (dir, true, false);
-			} else
-			if (dir.Parent?.Exists ?? false) {
-				this.PushState (dir.Parent, true, false);
-			}
+	FileSystemInfoStats RowToStats (int rowIndex) => State?.Children [rowIndex];
 
-			tbPath.Autocomplete.GenerateSuggestions (new AutocompleteFilepathContext (tbPath.Text, tbPath.CursorPosition, State));
+	void PathChanged ()
+	{
+		// avoid re-entry
+		if (pushingState) {
+			return;
 		}
 
-		private IDirectoryInfo StringToDirectoryInfo (string path)
-		{
-			// if you pass new DirectoryInfo("C:") you get a weird object
-			// where the FullName is in fact the current working directory.
-			// really not what most users would expect
-			if (Regex.IsMatch (path, "^\\w:$")) {
-				return fileSystem.DirectoryInfo.New (path + System.IO.Path.DirectorySeparatorChar);
-			}
+		var path = tbPath.Text;
 
-			return fileSystem.DirectoryInfo.New (path);
+		if (string.IsNullOrWhiteSpace (path)) {
+			return;
 		}
 
-		/// <summary>
-		/// Select <paramref name="toRestore"/> in the table view (if present)
-		/// </summary>
-		/// <param name="toRestore"></param>
-		internal void RestoreSelection (IFileSystemInfo toRestore)
-		{
-			tableView.SelectedRow = State.Children.IndexOf (r => r.FileSystemInfo == toRestore);
-			tableView.EnsureSelectedCellIsVisible ();
+		var dir = StringToDirectoryInfo (path);
+
+		if (dir.Exists) {
+			PushState (dir, true, false);
+		} else if (dir.Parent?.Exists ?? false) {
+			PushState (dir.Parent, true, false);
 		}
 
-		internal void ApplySort ()
-		{
-			var stats = State?.Children ?? new FileSystemInfoStats [0];
+		tbPath.Autocomplete.GenerateSuggestions (new AutocompleteFilepathContext (tbPath.Text, tbPath.CursorPosition, State));
+	}
 
-			// This portion is never reordered (aways .. at top then folders)
-			var forcedOrder = stats
-			.OrderByDescending (f => f.IsParent)
-					.ThenBy (f => f.IsDir ? -1 : 100);
+	IDirectoryInfo StringToDirectoryInfo (string path)
+	{
+		// if you pass new DirectoryInfo("C:") you get a weird object
+		// where the FullName is in fact the current working directory.
+		// really not what most users would expect
+		if (Regex.IsMatch (path, "^\\w:$")) {
+			return fileSystem.DirectoryInfo.New (path + System.IO.Path.DirectorySeparatorChar);
+		}
 
-			// This portion is flexible based on the column clicked (e.g. alphabetical)
-			var ordered =
-				this.currentSortIsAsc ?
-					forcedOrder.ThenBy (f => FileDialogTableSource.GetRawColumnValue (currentSortColumn, f)) :
-					forcedOrder.ThenByDescending (f => FileDialogTableSource.GetRawColumnValue (currentSortColumn, f));
+		return fileSystem.DirectoryInfo.New (path);
+	}
 
-			State.Children = ordered.ToArray ();
+	/// <summary>
+	/// Select <paramref name="toRestore"/> in the table view (if present)
+	/// </summary>
+	/// <param name="toRestore"></param>
+	internal void RestoreSelection (IFileSystemInfo toRestore)
+	{
+		tableView.SelectedRow = State.Children.IndexOf (r => r.FileSystemInfo == toRestore);
+		tableView.EnsureSelectedCellIsVisible ();
+	}
 
-			this.tableView.Update ();
-		}
+	internal void ApplySort ()
+	{
+		var stats = State?.Children ?? new FileSystemInfoStats [0];
 
-		private void SortColumn (int clickedCol)
-		{
-			this.GetProposedNewSortOrder (clickedCol, out var isAsc);
-			this.SortColumn (clickedCol, isAsc);
-			this.tableView.Table = new FileDialogTableSource (this, State, Style, currentSortColumn, currentSortIsAsc);
-		}
+		// This portion is never reordered (aways .. at top then folders)
+		var forcedOrder = stats
+				  .OrderByDescending (f => f.IsParent)
+				  .ThenBy (f => f.IsDir ? -1 : 100);
 
-		internal void SortColumn (int col, bool isAsc)
-		{
-			// set a sort order
-			this.currentSortColumn = col;
-			this.currentSortIsAsc = isAsc;
+		// This portion is flexible based on the column clicked (e.g. alphabetical)
+		var ordered =
+			currentSortIsAsc ?
+				forcedOrder.ThenBy (f => FileDialogTableSource.GetRawColumnValue (currentSortColumn,           f)) :
+				forcedOrder.ThenByDescending (f => FileDialogTableSource.GetRawColumnValue (currentSortColumn, f));
 
-			this.ApplySort ();
-		}
+		State.Children = ordered.ToArray ();
 
-		private string GetProposedNewSortOrder (int clickedCol, out bool isAsc)
-		{
-			// work out new sort order
-			if (this.currentSortColumn == clickedCol && this.currentSortIsAsc) {
-				isAsc = false;
-				return string.Format (Strings.fdCtxSortDesc, tableView.Table.ColumnNames [clickedCol]);
-			} else {
-				isAsc = true;
-				return string.Format (Strings.fdCtxSortAsc, tableView.Table.ColumnNames [clickedCol]);
-			}
-		}
+		tableView.Update ();
+	}
 
-		private void ShowHeaderContextMenu (int clickedCol, MouseEventEventArgs e)
-		{
-			var sort = this.GetProposedNewSortOrder (clickedCol, out var isAsc);
+	void SortColumn (int clickedCol)
+	{
+		GetProposedNewSortOrder (clickedCol, out var isAsc);
+		SortColumn (clickedCol, isAsc);
+		tableView.Table = new FileDialogTableSource (this, State, Style, currentSortColumn, currentSortIsAsc);
+	}
 
-			var contextMenu = new ContextMenu (
-				e.MouseEvent.X + 1,
-				e.MouseEvent.Y + 1,
-				new MenuBarItem (new MenuItem []
-				{
-					new MenuItem(string.Format (Strings.fdCtxHide, StripArrows (tableView.Table.ColumnNames[clickedCol])), string.Empty, () => this.HideColumn (clickedCol)),
-					new MenuItem(StripArrows (sort), string.Empty, () => this.SortColumn (clickedCol, isAsc)),
-				})
-			);
+	internal void SortColumn (int col, bool isAsc)
+	{
+		// set a sort order
+		currentSortColumn = col;
+		currentSortIsAsc = isAsc;
 
-			contextMenu.Show ();
-		}
+		ApplySort ();
+	}
 
-		private static string StripArrows (string columnName)
-		{
-			return columnName.Replace (" (▼)", string.Empty).Replace (" (▲)", string.Empty);
+	string GetProposedNewSortOrder (int clickedCol, out bool isAsc)
+	{
+		// work out new sort order
+		if (currentSortColumn == clickedCol && currentSortIsAsc) {
+			isAsc = false;
+			return string.Format (Strings.fdCtxSortDesc, tableView.Table.ColumnNames [clickedCol]);
 		}
+		isAsc = true;
+		return string.Format (Strings.fdCtxSortAsc, tableView.Table.ColumnNames [clickedCol]);
+	}
 
-		private void ShowCellContextMenu (Point? clickedCell, MouseEventEventArgs e)
-		{
-			if (clickedCell == null) {
-				return;
-			}
+	void ShowHeaderContextMenu (int clickedCol, MouseEventEventArgs e)
+	{
+		var sort = GetProposedNewSortOrder (clickedCol, out var isAsc);
 
-			var contextMenu = new ContextMenu (
-				e.MouseEvent.X + 1,
-				e.MouseEvent.Y + 1,
-				new MenuBarItem (new MenuItem []
-				{
-					new MenuItem(Strings.fdCtxNew, string.Empty, New),
-					new MenuItem(Strings.fdCtxRename, string.Empty, Rename),
-					new MenuItem(Strings.fdCtxDelete,string.Empty, Delete),
-				})
-			);
+		var contextMenu = new ContextMenu (
+			e.MouseEvent.X + 1,
+			e.MouseEvent.Y + 1,
+			new MenuBarItem (new MenuItem [] {
+				new (string.Format (Strings.fdCtxHide, StripArrows (tableView.Table.ColumnNames [clickedCol])), string.Empty, () => HideColumn (clickedCol)),
+				new (StripArrows (sort), string.Empty, () => SortColumn (clickedCol, isAsc))
+			})
+		);
 
-			tableView.SetSelection (clickedCell.Value.X, clickedCell.Value.Y, false);
+		contextMenu.Show ();
+	}
 
-			contextMenu.Show ();
-		}
+	static string StripArrows (string columnName) => columnName.Replace (" (▼)", string.Empty).Replace (" (▲)", string.Empty);
 
-		private void HideColumn (int clickedCol)
-		{
-			var style = this.tableView.Style.GetOrCreateColumnStyle (clickedCol);
-			style.Visible = false;
-			this.tableView.Update ();
+	void ShowCellContextMenu (Point? clickedCell, MouseEventEventArgs e)
+	{
+		if (clickedCell == null) {
+			return;
 		}
 
-		/// <summary>
-		/// State representing a recursive search from <see cref="FileDialogState.Directory"/>
-		/// downwards.
-		/// </summary>
-		internal class SearchState : FileDialogState {
+		var contextMenu = new ContextMenu (
+			e.MouseEvent.X + 1,
+			e.MouseEvent.Y + 1,
+			new MenuBarItem (new MenuItem [] {
+				new (Strings.fdCtxNew, string.Empty, New),
+				new (Strings.fdCtxRename, string.Empty, Rename),
+				new (Strings.fdCtxDelete, string.Empty, Delete)
+			})
+		);
 
-			bool cancel = false;
-			bool finished = false;
+		tableView.SetSelection (clickedCell.Value.X, clickedCell.Value.Y, false);
 
-			// TODO: Add thread safe child adding
-			List<FileSystemInfoStats> found = new List<FileSystemInfoStats> ();
-			object oLockFound = new object ();
-			CancellationTokenSource token = new CancellationTokenSource ();
+		contextMenu.Show ();
+	}
 
-			public SearchState (IDirectoryInfo dir, FileDialog parent, string searchTerms) : base (dir, parent)
-			{
-				parent.SearchMatcher.Initialize (searchTerms);
-				Children = new FileSystemInfoStats [0];
-				BeginSearch ();
-			}
+	void HideColumn (int clickedCol)
+	{
+		var style = tableView.Style.GetOrCreateColumnStyle (clickedCol);
+		style.Visible = false;
+		tableView.Update ();
+	}
 
-			private void BeginSearch ()
-			{
-				Task.Run (() => {
-					RecursiveFind (Directory);
-					finished = true;
-				});
+	/// <summary>
+	/// State representing a recursive search from <see cref="FileDialogState.Directory"/>
+	/// downwards.
+	/// </summary>
+	internal class SearchState : FileDialogState {
+		bool cancel;
+		bool finished;
 
-				Task.Run (() => {
-					UpdateChildren ();
-				});
-			}
+		// TODO: Add thread safe child adding
+		readonly List<FileSystemInfoStats> found = new ();
+		readonly object oLockFound = new ();
+		readonly CancellationTokenSource token = new ();
 
-			private void UpdateChildren ()
-			{
-				lock (Parent.onlyOneSearchLock) {
-					while (!cancel && !finished) {
+		public SearchState (IDirectoryInfo dir, FileDialog parent, string searchTerms) : base (dir, parent)
+		{
+			parent.SearchMatcher.Initialize (searchTerms);
+			Children = new FileSystemInfoStats [0];
+			BeginSearch ();
+		}
 
-						try {
-							Task.Delay (250).Wait (token.Token);
-						} catch (OperationCanceledException) {
-							cancel = true;
-						}
+		void BeginSearch ()
+		{
+			Task.Run (() => {
+				RecursiveFind (Directory);
+				finished = true;
+			});
+
+			Task.Run (() => {
+				UpdateChildren ();
+			});
+		}
 
-						if (cancel || finished) {
-							break;
-						}
+		void UpdateChildren ()
+		{
+			lock (Parent.onlyOneSearchLock) {
+				while (!cancel && !finished) {
 
-						UpdateChildrenToFound ();
+					try {
+						Task.Delay (250).Wait (token.Token);
+					} catch (OperationCanceledException) {
+						cancel = true;
 					}
 
-					if (finished && !cancel) {
-						UpdateChildrenToFound ();
+					if (cancel || finished) {
+						break;
 					}
 
-					Application.Invoke (() => {
-						Parent.spinnerView.Visible = false;
-					});
+					UpdateChildrenToFound ();
 				}
-			}
 
-			private void UpdateChildrenToFound ()
-			{
-				lock (oLockFound) {
-					Children = found.ToArray ();
+				if (finished && !cancel) {
+					UpdateChildrenToFound ();
 				}
 
 				Application.Invoke (() => {
-					Parent.tbPath.Autocomplete.GenerateSuggestions (
-						new AutocompleteFilepathContext (Parent.tbPath.Text, Parent.tbPath.CursorPosition, this)
-						);
-					Parent.WriteStateToTableView ();
-
-					Parent.spinnerView.Visible = true;
-					Parent.spinnerView.SetNeedsDisplay ();
+					Parent.spinnerView.Visible = false;
 				});
 			}
+		}
+
+		void UpdateChildrenToFound ()
+		{
+			lock (oLockFound) {
+				Children = found.ToArray ();
+			}
 
-			private void RecursiveFind (IDirectoryInfo directory)
-			{
-				foreach (var f in GetChildren (directory)) {
+			Application.Invoke (() => {
+				Parent.tbPath.Autocomplete.GenerateSuggestions (
+					new AutocompleteFilepathContext (Parent.tbPath.Text, Parent.tbPath.CursorPosition, this)
+				);
+				Parent.WriteStateToTableView ();
 
-					if (cancel) {
-						return;
-					}
+				Parent.spinnerView.Visible = true;
+				Parent.spinnerView.SetNeedsDisplay ();
+			});
+		}
 
-					if (f.IsParent) {
-						continue;
-					}
+		void RecursiveFind (IDirectoryInfo directory)
+		{
+			foreach (var f in GetChildren (directory)) {
 
-					lock (oLockFound) {
-						if (found.Count >= FileDialog.MaxSearchResults) {
-							finished = true;
-							return;
-						}
-					}
+				if (cancel) {
+					return;
+				}
+
+				if (f.IsParent) {
+					continue;
+				}
 
-					if (Parent.SearchMatcher.IsMatch (f.FileSystemInfo)) {
-						lock (oLockFound) {
-							found.Add (f);
-						}
+				lock (oLockFound) {
+					if (found.Count >= MaxSearchResults) {
+						finished = true;
+						return;
 					}
+				}
 
-					if (f.FileSystemInfo is IDirectoryInfo sub) {
-						RecursiveFind (sub);
+				if (Parent.SearchMatcher.IsMatch (f.FileSystemInfo)) {
+					lock (oLockFound) {
+						found.Add (f);
 					}
 				}
-			}
 
-			internal override void RefreshChildren ()
-			{
+				if (f.FileSystemInfo is IDirectoryInfo sub) {
+					RecursiveFind (sub);
+				}
 			}
+		}
 
-			/// <summary>
-			/// Cancels the current search (if any).  Returns true if a search
-			/// was running and cancellation was successfully set.
-			/// </summary>
-			/// <returns></returns>
-			internal bool Cancel ()
-			{
-				var alreadyCancelled = token.IsCancellationRequested || cancel;
+		internal override void RefreshChildren () { }
+
+		/// <summary>
+		/// Cancels the current search (if any).  Returns true if a search
+		/// was running and cancellation was successfully set.
+		/// </summary>
+		/// <returns></returns>
+		internal bool Cancel ()
+		{
+			var alreadyCancelled = token.IsCancellationRequested || cancel;
 
-				cancel = true;
-				token.Cancel ();
+			cancel = true;
+			token.Cancel ();
 
-				return !alreadyCancelled;
-			}
+			return !alreadyCancelled;
 		}
-		internal class FileDialogCollectionNavigator : CollectionNavigatorBase {
-			private FileDialog fileDialog;
+	}
 
-			public FileDialogCollectionNavigator (FileDialog fileDialog)
-			{
-				this.fileDialog = fileDialog;
-			}
+	internal class FileDialogCollectionNavigator : CollectionNavigatorBase {
+		readonly FileDialog fileDialog;
 
-			protected override object ElementAt (int idx)
-			{
-				var val = FileDialogTableSource.GetRawColumnValue (fileDialog.tableView.SelectedColumn, fileDialog.State?.Children [idx]);
-				if (val == null) {
-					return string.Empty;
-				}
+		public FileDialogCollectionNavigator (FileDialog fileDialog) => this.fileDialog = fileDialog;
 
-				return val.ToString ().Trim ('.');
+		protected override object ElementAt (int idx)
+		{
+			var val = FileDialogTableSource.GetRawColumnValue (fileDialog.tableView.SelectedColumn, fileDialog.State?.Children [idx]);
+			if (val == null) {
+				return string.Empty;
 			}
 
-			protected override int GetCollectionLength ()
-			{
-				return fileDialog.State?.Children.Length ?? 0;
-			}
+			return val.ToString ().Trim ('.');
 		}
+
+		protected override int GetCollectionLength () => fileDialog.State?.Children.Length ?? 0;
 	}
 }