Sfoglia il codice sorgente

Fixes #2617 - Support for Nerd icons for FileDialog (#2613)

* Add explicit file name unicode mappings

* Added nerd file extensions

* Add folder icon

* Added tests and attribution

* Fix tree view not using System.IO.Abstractions

* Make FileDialog tree use IconGetter

* Added context to IconGetter

* Make Nerd a config setting

* Fix typo in attribution

* tidy up

* Change open folder check to null or whitespace

* Update class diagram to show new classes

* Rename configuration property NerdFonts and set it to ThemeScope

* Move NerdFonts setting to ConsoleDriver

* Move NerdFonts setting to NerdFonts class and rename Enable

---------

Co-authored-by: Tig <[email protected]>
Thomas Nind 2 anni fa
parent
commit
c04703f5d6

+ 1 - 0
Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs

@@ -685,6 +685,7 @@ namespace Terminal.Gui {
 	/// There are currently three implementations: <see cref="CursesDriver"/> (for Unix and Mac), <see cref="WindowsDriver"/>, and <see cref="NetDriver"/> that uses the .NET Console API.
 	/// </summary>
 	public abstract class ConsoleDriver {
+
 		/// <summary>
 		/// The handler fired when the terminal is resized.
 		/// </summary>

+ 35 - 0
Terminal.Gui/FileServices/FileDialogIconGetterArgs.cs

@@ -0,0 +1,35 @@
+using System.IO.Abstractions;
+
+namespace Terminal.Gui {
+
+	/// <summary>
+	/// Arguments for the <see cref="FileDialogStyle.IconGetter"/> delegate
+	/// </summary>
+	public class FileDialogIconGetterArgs {
+
+		/// <summary>
+		/// Creates a new instance of the class
+		/// </summary>
+		public FileDialogIconGetterArgs (FileDialog fileDialog, IFileSystemInfo file, FileDialogIconGetterContext context)
+		{
+			FileDialog = fileDialog;
+			File = file;
+			Context = context;
+		}
+
+		/// <summary>
+		/// Gets the dialog that requires the icon.
+		/// </summary>
+		public FileDialog FileDialog { get; }
+
+		/// <summary>
+		/// Gets the file/folder for which the icon is required.
+		/// </summary>
+		public IFileSystemInfo File { get; }
+
+		/// <summary>
+		/// Gets the context in which the icon will be used in.
+		/// </summary>
+		public FileDialogIconGetterContext Context { get; }
+	}
+}

+ 18 - 0
Terminal.Gui/FileServices/FileDialogIconGetterContext.cs

@@ -0,0 +1,18 @@
+namespace Terminal.Gui {
+	/// <summary>
+	/// Describes the context in which icons are being sought
+	/// during <see cref="FileDialogIconGetterArgs"/>.
+	/// </summary>
+	public enum FileDialogIconGetterContext {
+
+		/// <summary>
+		/// Icon will be used in the tree view
+		/// </summary>
+		Tree,
+
+		/// <summary>
+		/// Icon will be used in the main table area of the dialog
+		/// </summary>
+		Table
+	}
+}

+ 3 - 2
Terminal.Gui/FileServices/FileDialogRootTreeNode.cs

@@ -1,5 +1,6 @@
 using System.Collections.Generic;
 using System.IO;
+using System.IO.Abstractions;
 
 namespace Terminal.Gui {
 
@@ -22,7 +23,7 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// <param name="displayName"></param>
 		/// <param name="path"></param>
-		public FileDialogRootTreeNode (string displayName, DirectoryInfo path)
+		public FileDialogRootTreeNode (string displayName, IDirectoryInfo path)
 		{
 			this.DisplayName = displayName;
 			this.Path = path;
@@ -37,7 +38,7 @@ namespace Terminal.Gui {
 		/// Gets the path that should be shown/explored when selecting this node
 		/// of the tree.
 		/// </summary>
-		public DirectoryInfo Path { get; }
+		public IDirectoryInfo Path { get; }
 
 		/// <summary>
 		/// Returns a string representation of this instance (<see cref="DisplayName"/>).

+ 33 - 9
Terminal.Gui/FileServices/FileDialogStyle.cs

@@ -15,6 +15,7 @@ namespace Terminal.Gui {
 	/// Stores style settings for <see cref="FileDialog"/>.
 	/// </summary>
 	public class FileDialogStyle {
+		readonly IFileSystem _fileSystem;
 
 		/// <summary>
 		/// Gets or sets the default value to use for <see cref="UseColors"/>.
@@ -35,7 +36,7 @@ namespace Terminal.Gui {
 		/// should be used for different file types/directories.  Defaults
 		/// to false.
 		/// </summary>
-		public bool UseColors { get; set; }
+		public bool UseColors { get; set; } = DefaultUseColors;
 
 		/// <summary>
 		/// Gets or sets the culture to use (e.g. for number formatting).
@@ -155,7 +156,7 @@ namespace Terminal.Gui {
 		/// <see cref="Environment.SpecialFolder"/>.
 		/// </summary>
 		/// <remarks>Must be configured before showing the dialog.</remarks>
-		public FileDialogTreeRootGetter TreeRootGetter { get; set; } = DefaultTreeRootGetter;
+		public FileDialogTreeRootGetter TreeRootGetter { get; set; }
 
 		/// <summary>
 		/// Gets or sets whether to use advanced unicode characters which might not be installed
@@ -167,7 +168,7 @@ namespace Terminal.Gui {
 		/// User defined delegate for picking which character(s)/unicode
 		/// symbol(s) to use as an 'icon' for files/folders. 
 		/// </summary>
-		public Func<IFileSystemInfo, string> IconGetter { get; set; }
+		public Func<FileDialogIconGetterArgs, string> IconGetter { get; set; }
 
 		/// <summary>
 		/// Gets or sets the format to use for date/times in the Modified column.
@@ -179,9 +180,17 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// Creates a new instance of the <see cref="FileDialogStyle"/> class.
 		/// </summary>
-		public FileDialogStyle ()
+		public FileDialogStyle (IFileSystem fileSystem)
 		{
+			_fileSystem = fileSystem;
 			IconGetter = DefaultIconGetter;
+			TreeRootGetter = DefaultTreeRootGetter;
+
+			if(NerdFonts.Enable)
+			{
+				UseNerdForIcons();
+			}
+
 			DateFormat = CultureInfo.CurrentCulture.DateTimeFormat.SortableDateTimePattern;
 
 			ColorSchemeDirectory = new ColorScheme {
@@ -210,12 +219,24 @@ namespace Terminal.Gui {
 				Focus = Application.Driver.MakeAttribute (Color.Black, Color.White),
 				HotFocus = Application.Driver.MakeAttribute (Color.Black, Color.White),
 			};
+		}
 
+		/// <summary>
+		/// Changes <see cref="IconGetter"/> to serve diverse icon set using
+		/// the Nerd fonts. This option requires users to have specific font(s)
+		/// installed.
+		/// </summary>
+		public void UseNerdForIcons ()
+		{
+			var nerd = new NerdFonts();
+			IconGetter = nerd.GetNerdIcon;
 		}
 
-		private string DefaultIconGetter (IFileSystemInfo arg)
+		private string DefaultIconGetter (FileDialogIconGetterArgs args)
 		{
-			if (arg is IDirectoryInfo) {
+			var file = args.File;
+
+			if (file is IDirectoryInfo) {
 				return UseUnicodeCharacters ? "\ua909 " : "\\";
 			}
 
@@ -223,12 +244,14 @@ namespace Terminal.Gui {
 
 		}
 
-		private static IEnumerable<FileDialogRootTreeNode> DefaultTreeRootGetter ()
+		private IEnumerable<FileDialogRootTreeNode> DefaultTreeRootGetter ()
 		{
 			var roots = new List<FileDialogRootTreeNode> ();
 			try {
 				foreach (var d in Environment.GetLogicalDrives ()) {
-					roots.Add (new FileDialogRootTreeNode (d, new DirectoryInfo (d)));
+
+					
+					roots.Add (new FileDialogRootTreeNode (d, _fileSystem.DirectoryInfo.New(d)));
 				}
 
 			} catch (Exception) {
@@ -246,7 +269,8 @@ namespace Terminal.Gui {
 
 							roots.Add (new FileDialogRootTreeNode (
 							special.ToString (),
-							new DirectoryInfo (Environment.GetFolderPath (special))));
+							_fileSystem.DirectoryInfo.New(Environment.GetFolderPath (special))
+							));
 						}
 					} catch (Exception) {
 						// Special file exists but contents are unreadable (permissions?)

+ 34 - 4
Terminal.Gui/FileServices/FileDialogTreeBuilder.cs

@@ -1,11 +1,19 @@
 using System;
 using System.Collections.Generic;
 using System.IO;
+using System.IO.Abstractions;
 using System.Linq;
 
 namespace Terminal.Gui {
 
 	class FileDialogTreeBuilder : ITreeBuilder<object> {
+		readonly FileDialog _dlg;
+
+		public FileDialogTreeBuilder(FileDialog dlg)
+		{
+			_dlg = dlg;
+		}
+
 		public bool SupportsCanExpand => true;
 
 		public bool CanExpand (object toExpand)
@@ -18,18 +26,40 @@ namespace Terminal.Gui {
 			return this.TryGetDirectories (NodeToDirectory (forObject));
 		}
 
-		internal static DirectoryInfo NodeToDirectory (object toExpand)
+		internal static IDirectoryInfo NodeToDirectory (object toExpand)
 		{
-			return toExpand is FileDialogRootTreeNode f ? f.Path : (DirectoryInfo)toExpand;
+			return toExpand is FileDialogRootTreeNode f ? f.Path : (IDirectoryInfo)toExpand;
+		}
+
+		internal string AspectGetter(object o)
+		{
+			string icon;
+			string name;
+
+			if(o is FileDialogRootTreeNode r)
+			{
+				icon = _dlg.Style.IconGetter.Invoke(
+					new FileDialogIconGetterArgs(_dlg, r.Path, FileDialogIconGetterContext.Tree));
+				name = r.DisplayName;
+			}
+			else
+			{
+				var dir  = (IDirectoryInfo)o;
+				icon = _dlg.Style.IconGetter.Invoke(
+					new FileDialogIconGetterArgs(_dlg, dir, FileDialogIconGetterContext.Tree));
+				name = dir.Name;
+			}
+
+			return icon + name;
 		}
 
-		private IEnumerable<DirectoryInfo> TryGetDirectories (DirectoryInfo directoryInfo)
+		private IEnumerable<IDirectoryInfo> TryGetDirectories (IDirectoryInfo directoryInfo)
 		{
 			try {
 				return directoryInfo.EnumerateDirectories ();
 			} catch (Exception) {
 
-				return Enumerable.Empty<DirectoryInfo> ();
+				return Enumerable.Empty<IDirectoryInfo> ();
 			}
 		}
 

+ 45 - 10
Terminal.Gui/Views/FileDialog.cd

@@ -8,19 +8,19 @@
       <Compartment Name="Nested Types" Collapsed="false" />
     </Compartments>
     <NestedTypes>
-      <Class Name="Terminal.Gui.FileDialog.FileDialogSorter" Collapsed="true">
+      <Class Name="Terminal.Gui.FileDialog.SearchState" Collapsed="true">
         <TypeIdentifier>
           <NewMemberFileName>Windows\FileDialog.cs</NewMemberFileName>
         </TypeIdentifier>
       </Class>
-      <Class Name="Terminal.Gui.FileDialog.SearchState" Collapsed="true">
+      <Class Name="Terminal.Gui.FileDialog.FileDialogCollectionNavigator" Collapsed="true">
         <TypeIdentifier>
-          <NewMemberFileName>Windows\FileDialog.cs</NewMemberFileName>
+          <NewMemberFileName>Views\FileDialog.cs</NewMemberFileName>
         </TypeIdentifier>
       </Class>
     </NestedTypes>
     <TypeIdentifier>
-      <HashCode>goYYDAEnEDIZgHMByFAikQDFSIUQpUDABoZIFRSQwgQ=</HashCode>
+      <HashCode>g4YYDAEXEDKZgHMFyFAikQCFSKUQhRDABqJIlBSAwgw=</HashCode>
       <FileName>Views\FileDialog.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
@@ -75,7 +75,7 @@
     </ShowAsCollectionAssociation>
   </Class>
   <Class Name="Terminal.Gui.FileDialogRootTreeNode" Collapsed="true">
-    <Position X="2.5" Y="8.25" Width="2" />
+    <Position X="2.5" Y="9" Width="2" />
     <TypeIdentifier>
       <HashCode>AAAAEAAAAAAAAAIEAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
       <FileName>FileServices\FileDialogRootTreeNode.cs</FileName>
@@ -84,7 +84,7 @@
   <Class Name="Terminal.Gui.FileDialogState">
     <Position X="11.5" Y="0.5" Width="1.5" />
     <TypeIdentifier>
-      <HashCode>AABAABAAAAAAAAAAAAAEQAAAAAAAQAAAAgAAAAAAAAI=</HashCode>
+      <HashCode>AABAABAAAAAAAAIAAAAEQAAAAAAAQAAAAgAAAAAAAAI=</HashCode>
       <FileName>FileServices\FileDialogState.cs</FileName>
     </TypeIdentifier>
     <ShowAsCollectionAssociation>
@@ -94,14 +94,14 @@
   <Class Name="Terminal.Gui.FileDialogStyle">
     <Position X="3.5" Y="0.5" Width="2.75" />
     <TypeIdentifier>
-      <HashCode>GgBIAAFEAAAAuAAAAAAEEACABAACKRkAAAEYACCAAAA=</HashCode>
+      <HashCode>GgBIAAFEAAAAuAAAAgAEEASABQACKRkAAAEYACCAAAA=</HashCode>
       <FileName>FileServices\FileDialogStyle.cs</FileName>
     </TypeIdentifier>
   </Class>
   <Class Name="Terminal.Gui.FileDialogTreeBuilder" BaseTypeListCollapsed="true">
     <Position X="0.5" Y="6.75" Width="1.75" />
     <TypeIdentifier>
-      <HashCode>EAAAAAAAAAAAAAAAQAAAAAAAAAAAQAAAAAAAAAQACAA=</HashCode>
+      <HashCode>EAAACAAAAAAAAAAAQAAAAAQAAAAAQAAAAAAAAAQACAA=</HashCode>
       <FileName>FileServices\FileDialogTreeBuilder.cs</FileName>
     </TypeIdentifier>
     <Lollipop Position="0.2" />
@@ -119,10 +119,38 @@
   <Class Name="Terminal.Gui.FileSystemInfoStats">
     <Position X="14" Y="0.5" Width="2.5" />
     <TypeIdentifier>
-      <HashCode>ABAIQAIIIAAAAAACQAAAAIQAAAQAAIAAAQABAAAYAAI=</HashCode>
+      <HashCode>ABAIQAIIIAAAAAACQAAAAIQAAAQAAIAAAQAAAAAIAAI=</HashCode>
       <FileName>FileServices\FileSystemInfoStats.cs</FileName>
     </TypeIdentifier>
   </Class>
+  <Class Name="Terminal.Gui.NerdFonts">
+    <Position X="9.25" Y="6.75" Width="2" />
+    <Compartments>
+      <Compartment Name="Fields" Collapsed="true" />
+    </Compartments>
+    <TypeIdentifier>
+      <HashCode>AIACAAABQAAAAAAAAAAAAAAAIACAAAAAAAIAAAQAAAA=</HashCode>
+      <FileName>Views\NerdFonts.cs</FileName>
+    </TypeIdentifier>
+  </Class>
+  <Class Name="Terminal.Gui.FileDialogIconGetterArgs" Collapsed="true">
+    <Position X="6.75" Y="6.75" Width="2" />
+    <TypeIdentifier>
+      <HashCode>AAAAAAAAAgAAAAAAAAAQAAAAAAAEAAAAAAAAAAAAAAA=</HashCode>
+      <FileName>FileServices\FileDialogIconGetterArgs.cs</FileName>
+    </TypeIdentifier>
+  </Class>
+  <Class Name="Terminal.Gui.FileDialogTableSource" Collapsed="true">
+    <Position X="4.75" Y="9" Width="2" />
+    <Compartments>
+      <Compartment Name="Fields" Collapsed="true" />
+    </Compartments>
+    <TypeIdentifier>
+      <HashCode>AQAAAAAAIAACAEAACAAAAAACAAAEAAAEAAAAgAgBBAA=</HashCode>
+      <FileName>Views\FileDialogTableSource.cs</FileName>
+    </TypeIdentifier>
+    <Lollipop Position="0.2" />
+  </Class>
   <Interface Name="Terminal.Gui.IAllowedType" Collapsed="true">
     <Position X="12" Y="4.25" Width="1.5" />
     <TypeIdentifier>
@@ -151,8 +179,15 @@
       <FileName>Views\OpenDialog.cs</FileName>
     </TypeIdentifier>
   </Enum>
+  <Enum Name="Terminal.Gui.FileDialogIconGetterContext">
+    <Position X="6.75" Y="7.25" Width="2.25" />
+    <TypeIdentifier>
+      <HashCode>AAAAAAAAAACAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
+      <FileName>FileServices\FileDialogIconGetterContext.cs</FileName>
+    </TypeIdentifier>
+  </Enum>
   <Delegate Name="Terminal.Gui.FileDialogTreeRootGetter" Collapsed="true">
-    <Position X="2.5" Y="7.5" Width="2" />
+    <Position X="2.5" Y="8.25" Width="2" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAA=</HashCode>
       <FileName>FileServices\FileDialogRootTreeNode.cs</FileName>

+ 8 - 6
Terminal.Gui/Views/FileDialog.cs

@@ -12,7 +12,6 @@ using Terminal.Gui.Resources;
 using static Terminal.Gui.ConfigurationManager;
 
 namespace Terminal.Gui {
-
 	/// <summary>
 	/// Modal dialog for selecting files/directories. Has auto-complete and expandable
 	/// navigation pane (Recent, Root drives etc).
@@ -24,7 +23,7 @@ namespace Terminal.Gui {
 		/// be made before the <see cref="Dialog"/> is loaded and shown to the user for the
 		/// first time.
 		/// </summary>
-		public FileDialogStyle Style { get; } = new FileDialogStyle ();
+		public FileDialogStyle Style { get; }
 
 		/// <summary>
 		/// The maximum number of results that will be collected
@@ -138,6 +137,8 @@ namespace Terminal.Gui {
 		public FileDialog (IFileSystem fileSystem)
 		{
 			this.fileSystem = fileSystem;
+			Style = new FileDialogStyle (fileSystem);
+
 			this.btnOk = new Button (Style.OkButtonText) {
 				Y = Pos.AnchorEnd (1),
 				X = Pos.Function (() =>
@@ -259,8 +260,9 @@ namespace Terminal.Gui {
 				Height = Dim.Fill (),
 			};
 
-			this.treeView.TreeBuilder = new FileDialogTreeBuilder ();
-			this.treeView.AspectGetter = (m) => m is IDirectoryInfo d ? d.Name : m.ToString ();
+			var fileDialogTreeBuilder = new FileDialogTreeBuilder (this);
+			this.treeView.TreeBuilder = fileDialogTreeBuilder;
+			this.treeView.AspectGetter = fileDialogTreeBuilder.AspectGetter;
 			this.Style.TreeStyle = treeView.Style;
 
 			this.treeView.SelectionChanged += this.TreeView_SelectionChanged;
@@ -1139,7 +1141,7 @@ namespace Terminal.Gui {
 			if (this.State == null) {
 				return;
 			}
-			this.tableView.Table = new FileDialogTableSource (this.State, this.Style, currentSortColumn, currentSortIsAsc);
+			this.tableView.Table = new FileDialogTableSource (this, this.State, this.Style, currentSortColumn, currentSortIsAsc);
 
 			this.ApplySort ();
 			this.tableView.Update ();
@@ -1267,7 +1269,7 @@ namespace Terminal.Gui {
 		{
 			this.GetProposedNewSortOrder (clickedCol, out var isAsc);
 			this.SortColumn (clickedCol, isAsc);
-			this.tableView.Table = new FileDialogTableSource (State, Style, currentSortColumn, currentSortIsAsc);
+			this.tableView.Table = new FileDialogTableSource (this, State, Style, currentSortColumn, currentSortIsAsc);
 		}
 
 		internal void SortColumn (int col, bool isAsc)

+ 5 - 2
Terminal.Gui/Views/FileDialogTableSource.cs

@@ -6,13 +6,15 @@ namespace Terminal.Gui {
 		readonly FileDialogStyle style;
 		readonly int currentSortColumn;
 		readonly bool currentSortIsAsc;
+		readonly FileDialog dlg;
 		readonly FileDialogState state;
 
-		public FileDialogTableSource (FileDialogState state, FileDialogStyle style, int currentSortColumn, bool currentSortIsAsc)
+		public FileDialogTableSource (FileDialog dlg, FileDialogState state, FileDialogStyle style, int currentSortColumn, bool currentSortIsAsc)
 		{
 			this.style = style;
 			this.currentSortColumn = currentSortColumn;
 			this.currentSortIsAsc = currentSortIsAsc;
+			this.dlg = dlg;
 			this.state = state;
 		}
 
@@ -22,7 +24,8 @@ namespace Terminal.Gui {
 		{
 			switch (col) {
 			case 0:
-				var icon = stats.IsParent ? null : style.IconGetter?.Invoke (stats.FileSystemInfo);
+				var icon = stats.IsParent ? null : style.IconGetter?.Invoke (
+					new FileDialogIconGetterArgs(dlg,stats.FileSystemInfo, FileDialogIconGetterContext.Table));
 				return icon + (stats?.Name ?? string.Empty);
 			case 1:
 				return stats?.HumanReadableLength ?? string.Empty;

+ 790 - 0
Terminal.Gui/Views/NerdFonts.cs

@@ -0,0 +1,790 @@
+// This code is adapted from https://github.com/devblackops/Terminal-Icons (which also uses the MIT license).
+// Nerd fonts can be installed by following the instructions on the Nerd Fonts repository: https://github.com/ryanoasis/nerd-fonts
+
+using System.Collections.Generic;
+using System.IO.Abstractions;
+using static Terminal.Gui.ConfigurationManager;
+
+namespace Terminal.Gui {
+	internal class NerdFonts {
+
+		/// <summary>
+		/// If <see langword="true"/>, enables the use of Nerd unicode symbols.
+		/// This requires specific font(s) to be installed on the users machine
+		/// to work correctly.  Defaults to <see langword="false"/>.
+		/// </summary>
+		[SerializableConfigurationProperty (Scope = typeof (ThemeScope))]
+		public static bool Enable { get; set; } = false;
+
+		public string GetNerdIcon(FileDialogIconGetterArgs args)
+		{
+			return GetNerdIconChar(args) + " ";
+		}
+
+		private char GetNerdIconChar(FileDialogIconGetterArgs args)
+		{
+			var file = args.File;
+			var path = args.FileDialog.Path;
+
+			if(FilenameToIcon.ContainsKey(file.Name))
+			{
+				return Glyphs[FilenameToIcon[file.Name]];
+			}
+
+			if(ExtensionToIcon.ContainsKey(file.Extension))
+			{
+				return Glyphs[ExtensionToIcon[file.Extension]];
+			}
+
+			if(file is IDirectoryInfo d)
+			{
+				if(!string.IsNullOrWhiteSpace(path) &&
+				   path.Contains(d.FullName) &&
+				   args.Context == FileDialogIconGetterContext.Tree)
+				{
+					return _nf_cod_folder_opened;
+				}
+				return _nf_cod_folder;
+			}
+				
+			return _nf_cod_file;
+		}
+		char _nf_cod_folder = '';
+		char _nf_cod_folder_opened = '';
+		char _nf_cod_file = '';
+
+		/// <summary>
+		/// All nerd glyphs used by Terminal.Gui by name.
+		/// </summary>
+		public Dictionary<string,char> Glyphs {get;set;} = new Dictionary<string, char>{
+			{"nf-cod-package", ''},
+			{"nf-cod-preview", ''},
+			{"nf-custom-folder_config", ''},
+			{"nf-custom-folder_git", ''},
+			{"nf-custom-folder_git_branch", ''},
+			{"nf-custom-folder_github", ''},
+			{"nf-custom-folder_npm", ''},
+			{"nf-dev-aws", ''},
+			{"nf-dev-docker", ''},
+			{"nf-dev-html5_multimedia", ''},
+			{"nf-fa-font", ''},
+			{"nf-fa-font_awesome", ''},
+			{"nf-fa-fonticons", ''},
+			{"nf-fa-github_alt", ''},
+			{"nf-fa-users", ''},
+			{"nf-fa-windows", ''},
+			{"nf-mdi-apps", ''},
+			{"nf-mdi-azure", 'ﴃ'},
+			{"nf-mdi-cached", ''},
+			{"nf-mdi-contacts", '﯉'},
+			{"nf-mdi-desktop_classic", 'ﲾ'},
+			{"nf-mdi-folder_download", ''},
+			{"nf-mdi-folder_image", ''},
+			{"nf-mdi-folder_star", 'ﮛ'},
+			{"nf-mdi-library_music", ''},
+			{"nf-mdi-movie", ''},
+			{"nf-mdi-movie_roll", 'ﳜ'},
+			{"nf-mdi-onedrive", ''},
+			{"nf-mdi-ship_wheel", 'ﴱ'},
+			{"nf-mdi-test_tube", 'ﭧ'},
+			{"nf-mdi-timer", '祥'},
+			{"nf-mdi-timer_10", '福'},
+			{"nf-mdi-timer_3", '靖'},
+			{"nf-mdi-timer_off", '精'},
+			{"nf-mdi-timer_sand", '羽'},
+			{"nf-mdi-timer_sand_empty", 'ﮫ'},
+			{"nf-mdi-timer_sand_full", 'ﲊ'},
+			{"nf-mdi-umbraco", '煮'},
+			{"nf-oct-file_binary", ''},
+			{"nf-oct-file_symlink_directory", ''},
+			{"nf-oct-repo", ''},
+			{"nf-oct-repo_clone", ''},
+			{"nf-oct-repo_force_push", ''},
+			{"nf-oct-repo_forked", ''},
+			{"nf-oct-repo_pull", ''},
+			{"nf-oct-repo_push", ''},
+			{"nf-oct-terminal", ''},
+			{"nf-seti-config", ''},
+			{"nf-seti-project", ''},
+			{"nf-custom-elixir", ''},
+			{"nf-custom-elm", ''},
+			{"nf-custom-msdos", ''},
+			{"nf-custom-puppet", ''},
+			{"nf-custom-vim", ''},
+			{"nf-dev-apple", ''},
+			{"nf-dev-bitbucket", ''},
+			{"nf-dev-bower", ''},
+			{"nf-dev-clojure", ''},
+			{"nf-dev-clojure_alt", ''},
+			{"nf-dev-code_badge", ''},
+			{"nf-dev-css3", ''},
+			{"nf-dev-css3_full", ''},
+			{"nf-dev-dart", ''},
+			{"nf-dev-database", ''},
+			{"nf-dev-erlang", ''},
+			{"nf-dev-firebase", ''},
+			{"nf-dev-fsharp", ''},
+			{"nf-dev-git", ''},
+			{"nf-dev-git_branch", ''},
+			{"nf-dev-git_commit", ''},
+			{"nf-dev-git_compare", ''},
+			{"nf-dev-git_merge", ''},
+			{"nf-dev-git_pull_request", ''},
+			{"nf-dev-github", ''},
+			{"nf-dev-github_alt", ''},
+			{"nf-dev-github_badge", ''},
+			{"nf-dev-github_full", ''},
+			{"nf-dev-go", ''},
+			{"nf-dev-google_cloud_platform", ''},
+			{"nf-dev-google_drive", ''},
+			{"nf-dev-groovy", ''},
+			{"nf-dev-gulp", ''},
+			{"nf-dev-haskell", ''},
+			{"nf-dev-javascript", ''},
+			{"nf-dev-javascript_badge", ''},
+			{"nf-dev-javascript_shield", ''},
+			{"nf-dev-jenkins", ''},
+			{"nf-dev-less", ''},
+			{"nf-dev-markdown", ''},
+			{"nf-dev-nodejs_small", ''},
+			{"nf-dev-npm", ''},
+			{"nf-dev-perl", ''},
+			{"nf-dev-php", ''},
+			{"nf-dev-python", ''},
+			{"nf-dev-react", ''},
+			{"nf-dev-rust", ''},
+			{"nf-dev-sass", ''},
+			{"nf-dev-sublime", ''},
+			{"nf-dev-travis", ''},
+			{"nf-dev-visualstudio", ''},
+			{"nf-fa-archive", ''},
+			{"nf-fa-calendar", ''},
+			{"nf-fa-calendar_check_o", ''},
+			{"nf-fa-calendar_minus_o", ''},
+			{"nf-fa-calendar_o", ''},
+			{"nf-fa-calendar_plus_o", ''},
+			{"nf-fa-calendar_times_o", ''},
+			{"nf-fa-certificate", ''},
+			{"nf-fa-eye", ''},
+			{"nf-fa-eye_slash", ''},
+			{"nf-fa-eyedropper", ''},
+			{"nf-fa-file_audio_o", ''},
+			{"nf-fa-file_image_o", ''},
+			{"nf-fa-file_o", ''},
+			{"nf-fa-file_video_o", ''},
+			{"nf-fa-gear", ''},
+			{"nf-fa-gears", ''},
+			{"nf-fa-gitlab", ''},
+			{"nf-fa-handshake_o", ''},
+			{"nf-fa-key", ''},
+			{"nf-fa-keyboard_o", ''},
+			{"nf-fa-list", ''},
+			{"nf-fa-list_alt", ''},
+			{"nf-fa-list_ol", ''},
+			{"nf-fa-list_ul", ''},
+			{"nf-fa-lock", ''},
+			{"nf-fae-checklist_o", ''},
+			{"nf-fae-disco", ''},
+			{"nf-fae-java", ''},
+			{"nf-mdi-application", 'ﬓ'},
+			{"nf-mdi-certificate", ''},
+			{"nf-mdi-console_line", 'ﲵ'},
+			{"nf-mdi-elephant", 'ﳄ'},
+			{"nf-mdi-file_document", ''},
+			{"nf-mdi-file_document_box", ''},
+			{"nf-mdi-file_excel", ''},
+			{"nf-mdi-file_excel_box", ''},
+			{"nf-mdi-file_pdf", ''},
+			{"nf-mdi-file_pdf_box", ''},
+			{"nf-mdi-file_powerpoint", ''},
+			{"nf-mdi-file_powerpoint_box", ''},
+			{"nf-mdi-file_word", ''},
+			{"nf-mdi-file_word_box", ''},
+			{"nf-mdi-file_xml", ''},
+			{"nf-mdi-format_align_left", ''},
+			{"nf-mdi-harddisk", ''},
+			{"nf-mdi-help_box", 'ﲉ'},
+			{"nf-mdi-language_c", 'ﭰ'},
+			{"nf-mdi-language_cpp", 'ﭱ'},
+			{"nf-mdi-language_csharp", ''},
+			{"nf-mdi-language_css3", ''},
+			{"nf-mdi-language_r", 'ﳒ'},
+			{"nf-mdi-library_books", ''},
+			{"nf-mdi-notebook", 'ﴬ'},
+			{"nf-mdi-package_variant", ''},
+			{"nf-mdi-package_variant_closed", ''},
+			{"nf-mdi-svg", 'ﰟ'},
+			{"nf-mdi-visualstudio", '﬏'},
+			{"nf-mdi-vuejs", '﵂'},
+			{"nf-mdi-xaml", 'ﭲ'},
+			{"nf-oct-file_zip", ''},
+			{"nf-oct-person", ''},
+			{"nf-oct-ruby", ''},
+			{"nf-seti-favicon", ''},
+			{"nf-seti-grunt", ''},
+			{"nf-seti-html", ''},
+			{"nf-seti-json", ''},
+			{"nf-seti-julia", ''},
+			{"nf-seti-lua", ''},
+			{"nf-seti-mustache", ''},
+			{"nf-seti-typescript", ''},
+
+		};
+
+		/// <summary>
+		/// Mapping of file name to <see cref="Glyphs"/> name.
+		/// </summary>
+		public Dictionary<string, string> FilenameToIcon {get;set;} = new Dictionary<string, string> ()
+		{
+			{"docs","nf-oct-repo"},
+			{"documents","nf-oct-repo"},
+			{"desktop","nf-mdi-desktop_classic"},
+			{"benchmark","nf-mdi-timer"},
+			{"demo","nf-cod-preview"},
+			{"samples","nf-cod-preview"},
+			{"contacts","nf-mdi-contacts"},
+			{"apps","nf-mdi-apps"},
+			{"applications","nf-mdi-apps"},
+			{"artifacts","nf-cod-package"},
+			{"shortcuts","nf-oct-file_symlink_directory"},
+			{"links","nf-oct-file_symlink_directory"},
+			{"fonts","nf-fa-font"},
+			{"images","nf-mdi-folder_image"},
+			{"photos","nf-mdi-folder_image"},
+			{"pictures","nf-mdi-folder_image"},
+			{"videos","nf-mdi-movie"},
+			{"movies","nf-mdi-movie"},
+			{"media","nf-dev-html5_multimedia"},
+			{"music","nf-mdi-library_music"},
+			{"songs","nf-mdi-library_music"},
+			{"onedrive","nf-mdi-onedrive"},
+			{"downloads","nf-mdi-folder_download"},
+			{"src","nf-oct-terminal"},
+			{"development","nf-oct-terminal"},
+			{"projects","nf-seti-project"},
+			{"bin","nf-oct-file_binary"},
+			{"tests","nf-mdi-test_tube"},
+			{"windows","nf-fa-windows"},
+			{"users","nf-fa-users"},
+			{"favorites","nf-mdi-folder_star"},
+			{".config","nf-seti-config"},
+			{".cache","nf-mdi-cached"},
+			{".vscode","nf-custom-folder_config"},
+			{".vscode-insiders","nf-custom-folder_config"},
+			{".git","nf-custom-folder_git"},
+			{".github","nf-custom-folder_github"},
+			{"github","nf-fa-github_alt"},
+			{"node_modules","nf-custom-folder_npm"},
+			{".azure","nf-mdi-azure"},
+			{".aws","nf-dev-aws"},
+			{".kube","nf-mdi-ship_wheel"},
+			{".docker","nf-dev-docker"},
+			{"umbraco","nf-mdi-umbraco"},
+			{".gitattributes", "nf-dev-git"},
+			{".gitconfig", "nf-dev-git"},
+			{".gitignore", "nf-dev-git"},
+			{".gitmodules", "nf-dev-git"},
+			{".gitkeep", "nf-dev-git"},
+			{"git-history", "nf-dev-git"},
+			{"LICENSE", "nf-mdi-certificate"},
+			{"CHANGELOG.md", "nf-fae-checklist_o"},
+			{"CHANGELOG.txt", "nf-fae-checklist_o"},
+			{"CHANGELOG", "nf-fae-checklist_o"},
+			{"README.md", "nf-mdi-library_books"},
+			{"README.txt", "nf-mdi-library_books"},
+			{"README", "nf-mdi-library_books"},
+			{".DS_Store", "nf-fa-file_o"},
+			{".tsbuildinfo", "nf-seti-json"},
+			{".jscsrc", "nf-seti-json"},
+			{".jshintrc", "nf-seti-json"},
+			{"tsconfig.json", "nf-seti-json"},
+			{"tslint.json", "nf-seti-json"},
+			{"composer.lock", "nf-seti-json"},
+			{".jsbeautifyrc", "nf-seti-json"},
+			{".esformatter", "nf-seti-json"},
+			{"cdp.pid", "nf-seti-json"},
+			{".htaccess", "nf-mdi-file_xml"},
+			{".jshintignore", "nf-fa-gear"},
+			{".buildignore", "nf-fa-gear"},
+			{".mrconfig", "nf-fa-gear"},
+			{".yardopts", "nf-fa-gear"},
+			{"manifest.mf", "nf-fa-gear"},
+			{".clang-format", "nf-fa-gear"},
+			{".clang-tidy", "nf-fa-gear"},
+			{"favicon.ico", "nf-seti-favicon"},
+			{".travis.yml", "nf-dev-travis"},
+			{".gitlab-ci.yml", "nf-fa-gitlab"},
+			{".jenkinsfile", "nf-dev-jenkins"},
+			{"bitbucket-pipelines.yml", "nf-dev-bitbucket"},
+			{"bitbucket-pipelines.yaml", "nf-dev-bitbucket"},
+			{".azure-pipelines.yml", "nf-mdi-azure"},
+			
+
+			// Firebase
+			{"firebase.json", "nf-dev-firebase"},
+			{".firebaserc", "nf-dev-firebase"},
+
+			// Bower
+			{".bowerrc", "nf-dev-bower"},
+			{"bower.json", "nf-dev-bower"},
+
+			// Conduct
+			{"code_of_conduct.md", "nf-fa-handshake_o"},
+			{"code_of_conduct.txt", "nf-fa-handshake_o"},
+
+			// Docker
+			{"Dockerfile", "nf-dev-docker"},
+			{"docker-compose.yml", "nf-dev-docker"},
+			{"docker-compose.yaml", "nf-dev-docker"},
+			{"docker-compose.dev.yml", "nf-dev-docker"},
+			{"docker-compose.local.yml", "nf-dev-docker"},
+			{"docker-compose.ci.yml", "nf-dev-docker"},
+			{"docker-compose.override.yml", "nf-dev-docker"},
+			{"docker-compose.staging.yml", "nf-dev-docker"},
+			{"docker-compose.prod.yml", "nf-dev-docker"},
+			{"docker-compose.production.yml", "nf-dev-docker"},
+			{"docker-compose.test.yml", "nf-dev-docker"},
+
+			// Vue
+			{"vue.config.js", "nf-mdi-vuejs"},
+			{"vue.config.ts", "nf-mdi-vuejs"},
+
+			// Gulp
+			{"gulpfile.js", "nf-dev-gulp"},
+			{"gulpfile.ts", "nf-dev-gulp"},
+			{"gulpfile.babel.js", "nf-dev-gulp"},
+
+			// Javascript
+			{"gruntfile.js", "nf-seti-grunt"},
+
+			// NodeJS
+			{"package.json", "nf-dev-nodejs_small"},
+			{"package-lock.json", "nf-dev-nodejs_small"},
+			{".nvmrc", "nf-dev-nodejs_small"},
+			{".esmrc", "nf-dev-nodejs_small"},
+
+			// NPM
+			{".nmpignore", "nf-dev-npm"},
+			{".npmrc", "nf-dev-npm"},
+
+			// Authors
+			{"authors", "nf-oct-person"},
+			{"authors.md", "nf-oct-person"},
+			{"authors.txt", "nf-oct-person"},
+
+			// Terraform
+			{".terraform.lock.hcl", "nf-fa-lock"},
+
+			// Gradle
+			{"gradlew", "nf-mdi-elephant"},
+		};
+
+		/// <summary>
+		/// Mapping of file extension to <see cref="Glyphs"/> name.
+		/// </summary>
+		public Dictionary<string, string> ExtensionToIcon {get;set;} = new Dictionary<string, string> ()
+		{
+
+			// Archive files
+			{".7z"                   , "nf-oct-file_zip"},
+			{".bz"                   , "nf-oct-file_zip"},
+			{".tar"                  , "nf-oct-file_zip"},
+			{".zip"                  , "nf-oct-file_zip"},
+			{".gz"                   , "nf-oct-file_zip"},
+			{".xz"                   , "nf-oct-file_zip"},
+			{".br"                   , "nf-oct-file_zip"},
+			{".bzip2"                , "nf-oct-file_zip"},
+			{".gzip"                 , "nf-oct-file_zip"},
+			{".brotli"               , "nf-oct-file_zip"},
+			{".rar"                  , "nf-oct-file_zip"},
+			{".tgz"                  , "nf-oct-file_zip"},
+
+			// Executable things
+			{".bat"                  , "nf-custom-msdos"},
+			{".cmd"                  , "nf-custom-msdos"},
+			{".exe"                  , "nf-mdi-application"},
+			{".pl"                   , "nf-dev-perl"},
+
+			{".sh"                   , "nf-oct-terminal"},
+
+			// App Packages
+			{".msi"                  , "nf-mdi-package_variant"},
+			{".msix"                 , "nf-mdi-package_variant"},
+			{".msixbundle"           , "nf-mdi-package_variant"},
+			{".appx"                 , "nf-mdi-package_variant"},
+			{".AppxBundle"           , "nf-mdi-package_variant"},
+			{".deb"                  , "nf-mdi-package_variant"},
+			{".rpm"                  , "nf-mdi-package_variant"},
+
+			// PowerShell
+			{".ps1"                  , "nf-mdi-console_line"},
+			{".psm1"                 , "nf-mdi-console_line"},
+			{".psd1"                 , "nf-mdi-console_line"},
+			{".ps1xml"               , "nf-mdi-console_line"},
+			{".psc1"                 , "nf-mdi-console_line"},
+			{".pssc"                 , "nf-mdi-console_line"},
+
+			// Javascript
+			{".js"                   , "nf-dev-javascript"},
+			{".esx"                  , "nf-dev-javascript"},
+			{".mjs"                  , "nf-dev-javascript"},
+
+			// Java
+			{".java"                 , "nf-fae-java"},
+			{".jar"                  , "nf-fae-java"},
+
+			{".gradle"               , "nf-mdi-elephant"},
+
+			// Python
+			{".py"                   , "nf-dev-python"},
+			{".ipynb"                , "nf-mdi-notebook"},
+
+			// React
+			{".jsx"                  , "nf-dev-react"},
+			{".tsx"                  , "nf-dev-react"},
+
+			// Typescript
+			{".ts"                   , "nf-seti-typescript"},
+
+			// Not-executable code files
+			{".dll"                  , "nf-fa-archive"},
+
+			// Importable Data files
+			{".clixml"               , "nf-dev-code_badge"},
+			{".csv"                  , "nf-mdi-file_excel"},
+			{".tsv"                  , "nf-mdi-file_excel"},
+
+			// Settings
+			{".ini"                  , "nf-fa-gear"},
+			{".dlc"                  , "nf-fa-gear"},
+			{".config"               , "nf-fa-gear"},
+			{".conf"                 , "nf-fa-gear"},
+			{".properties"           , "nf-fa-gear"},
+			{".prop"                 , "nf-fa-gear"},
+			{".settings"             , "nf-fa-gear"},
+			{".option"               , "nf-fa-gear"},
+			{".reg"                  , "nf-fa-gear"},
+			{".props"                , "nf-fa-gear"},
+			{".toml"                 , "nf-fa-gear"},
+			{".prefs"                , "nf-fa-gear"},
+			{".sln.dotsettings"      , "nf-fa-gear"},
+			{".sln.dotsettings.user" , "nf-fa-gear"},
+			{".cfg"                  , "nf-fa-gear"},
+
+			// Source Files
+			{".c"                    , "nf-mdi-language_c"},
+			{".cpp"                  , "nf-mdi-language_cpp"},
+			{".go"                   , "nf-dev-go"},
+			{".php"                  , "nf-dev-php"},
+
+			// Visual Studio
+			{".csproj"               , "nf-dev-visualstudio"},
+			{".ruleset"              , "nf-dev-visualstudio"},
+			{".sln"                  , "nf-dev-visualstudio"},
+			{".slnf"                 , "nf-dev-visualstudio"},
+			{".suo"                  , "nf-dev-visualstudio"},
+			{".vb"                   , "nf-dev-visualstudio"},
+			{".vbs"                  , "nf-dev-visualstudio"},
+			{".vcxitems"             , "nf-dev-visualstudio"},
+			{".vcxitems.filters"     , "nf-dev-visualstudio"},
+			{".vcxproj"              , "nf-dev-visualstudio"},
+			{".vsxproj.filters"      , "nf-dev-visualstudio"},
+
+			// CSharp
+			{".cs"                   , "nf-mdi-language_csharp"},
+			{".csx"                  , "nf-mdi-language_csharp"},
+
+			// Haskell
+			{".hs"                   , "nf-dev-haskell"},
+
+			// XAML
+			{".xaml"                 , "nf-mdi-xaml"},
+
+			// Rust
+			{".rs"                   , "nf-dev-rust"},
+
+			// Database
+			{".pdb"                  , "nf-dev-database"},
+			{".sql"                  , "nf-dev-database"},
+			{".pks"                  , "nf-dev-database"},
+			{".pkb"                  , "nf-dev-database"},
+			{".accdb"                , "nf-dev-database"},
+			{".mdb"                  , "nf-dev-database"},
+			{".sqlite"               , "nf-dev-database"},
+			{".pgsql"                , "nf-dev-database"},
+			{".postgres"             , "nf-dev-database"},
+			{".psql"                 , "nf-dev-database"},
+
+			// Source Control
+			{".patch"                , "nf-dev-git"},
+
+			// Project files
+			{".user"                 , "nf-mdi-visualstudio"},
+			{".code-workspace"       , "nf-mdi-visualstudio"},
+
+			// Text data files
+			{".log"                  , "nf-fa-list"},
+			{".txt"                  , "nf-mdi-file_document"},
+
+			// Subtitle files
+			{".srt"                  , "nf-mdi-file_document"},
+			{".lrc"                  , "nf-mdi-file_document"},
+			{".ass"                  , "nf-fa-eye"},
+
+			// HTML/css
+			{".html"                 , "nf-seti-html"},
+			{".htm"                  , "nf-seti-html"},
+			{".xhtml"                , "nf-seti-html"},
+			{".html_vm"              , "nf-seti-html"},
+			{".asp"                  , "nf-seti-html"},
+			{".css"                  , "nf-dev-css3"},
+			{".sass"                 , "nf-dev-sass"},
+			{".scss"                 , "nf-dev-sass"},
+			{".less"                 , "nf-dev-less"},
+
+			// Markdown
+			{".md"                   , "nf-dev-markdown"},
+			{".markdown"             , "nf-dev-markdown"},
+			{".rst"                  , "nf-dev-markdown"},
+
+			// Handlebars
+			{".hbs"                  , "nf-seti-mustache"},
+
+			// JSON
+			{".json"                 , "nf-seti-json"},
+			{".tsbuildinfo"          , "nf-seti-json"},
+
+			// YAML
+			{".yml"                  , "nf-mdi-format_align_left"},
+			{".yaml"                 , "nf-mdi-format_align_left"},
+
+			// LUA
+			{".lua"                  , "nf-seti-lua"},
+
+			// Clojure
+			{".clj"                  , "nf-dev-clojure"},
+			{".cljs"                 , "nf-dev-clojure"},
+			{".cljc"                 , "nf-dev-clojure"},
+
+			// Groovy
+			{".groovy"               , "nf-dev-groovy"},
+
+			// Vue
+			{".vue"                  , "nf-mdi-vuejs"},
+
+			// Dart
+			{".dart"                 , "nf-dev-dart"},
+
+			// Elixir
+			{".ex"                   , "nf-custom-elixir"},
+			{".exs"                  , "nf-custom-elixir"},
+			{".eex"                  , "nf-custom-elixir"},
+			{".leex"                 , "nf-custom-elixir"},
+
+			// Erlang
+			{".erl"                  , "nf-dev-erlang"},
+
+			// Elm
+			{".elm"                  , "nf-custom-elm"},
+
+			// Applescript
+			{".applescript"          , "nf-dev-apple"},
+
+			// XML
+			{".xml"                  , "nf-mdi-file_xml"},
+			{".plist"                , "nf-mdi-file_xml"},
+			{".xsd"                  , "nf-mdi-file_xml"},
+			{".dtd"                  , "nf-mdi-file_xml"},
+			{".xsl"                  , "nf-mdi-file_xml"},
+			{".xslt"                 , "nf-mdi-file_xml"},
+			{".resx"                 , "nf-mdi-file_xml"},
+			{".iml"                  , "nf-mdi-file_xml"},
+			{".xquery"               , "nf-mdi-file_xml"},
+			{".tmLanguage"           , "nf-mdi-file_xml"},
+			{".manifest"             , "nf-mdi-file_xml"},
+			{".project"              , "nf-mdi-file_xml"},
+
+			// Documents
+			{".chm"                  , "nf-mdi-help_box"},
+			{".pdf"                  , "nf-mdi-file_pdf"},
+
+			// Excel
+			{".xls"                  , "nf-mdi-file_excel"},
+			{".xlsx"                 , "nf-mdi-file_excel"},
+
+			// PowerPoint
+			{".pptx"                 , "nf-mdi-file_powerpoint"},
+			{".ppt"                  , "nf-mdi-file_powerpoint"},
+			{".pptm"                 , "nf-mdi-file_powerpoint"},
+			{".potx"                 , "nf-mdi-file_powerpoint"},
+			{".potm"                 , "nf-mdi-file_powerpoint"},
+			{".ppsx"                 , "nf-mdi-file_powerpoint"},
+			{".ppsm"                 , "nf-mdi-file_powerpoint"},
+			{".pps"                  , "nf-mdi-file_powerpoint"},
+			{".ppam"                 , "nf-mdi-file_powerpoint"},
+			{".ppa"                  , "nf-mdi-file_powerpoint"},
+
+			// Word
+			{".doc"                  , "nf-mdi-file_word"},
+			{".docx"                 , "nf-mdi-file_word"},
+			{".rtf"                  , "nf-mdi-file_word"},
+
+			// Audio
+			{".mp3"                  , "nf-fa-file_audio_o"},
+			{".flac"                 , "nf-fa-file_audio_o"},
+			{".m4a"                  , "nf-fa-file_audio_o"},
+			{".wma"                  , "nf-fa-file_audio_o"},
+			{".aiff"                 , "nf-fa-file_audio_o"},
+			{".wav"                  , "nf-fa-file_audio_o"},
+			{".aac"                  , "nf-fa-file_audio_o"},
+			{".opus"                 , "nf-fa-file_audio_o"},
+
+			// Images
+			{".png"                  , "nf-fa-file_image_o"},
+			{".jpeg"                 , "nf-fa-file_image_o"},
+			{".jpg"                  , "nf-fa-file_image_o"},
+			{".gif"                  , "nf-fa-file_image_o"},
+			{".ico"                  , "nf-fa-file_image_o"},
+			{".tif"                  , "nf-fa-file_image_o"},
+			{".tiff"                 , "nf-fa-file_image_o"},
+			{".psd"                  , "nf-fa-file_image_o"},
+			{".psb"                  , "nf-fa-file_image_o"},
+			{".ami"                  , "nf-fa-file_image_o"},
+			{".apx"                  , "nf-fa-file_image_o"},
+			{".bmp"                  , "nf-fa-file_image_o"},
+			{".bpg"                  , "nf-fa-file_image_o"},
+			{".brk"                  , "nf-fa-file_image_o"},
+			{".cur"                  , "nf-fa-file_image_o"},
+			{".dds"                  , "nf-fa-file_image_o"},
+			{".dng"                  , "nf-fa-file_image_o"},
+			{".eps"                  , "nf-fa-file_image_o"},
+			{".exr"                  , "nf-fa-file_image_o"},
+			{".fpx"                  , "nf-fa-file_image_o"},
+			{".gbr"                  , "nf-fa-file_image_o"},
+			{".jbig2"                , "nf-fa-file_image_o"},
+			{".jb2"                  , "nf-fa-file_image_o"},
+			{".jng"                  , "nf-fa-file_image_o"},
+			{".jxr"                  , "nf-fa-file_image_o"},
+			{".pbm"                  , "nf-fa-file_image_o"},
+			{".pgf"                  , "nf-fa-file_image_o"},
+			{".pic"                  , "nf-fa-file_image_o"},
+			{".raw"                  , "nf-fa-file_image_o"},
+			{".webp"                 , "nf-fa-file_image_o"},
+			{".svg"                  , "nf-mdi-svg"},
+
+			// Video
+			{".webm"                 , "nf-fa-file_video_o"},
+			{".mkv"                  , "nf-fa-file_video_o"},
+			{".flv"                  , "nf-fa-file_video_o"},
+			{".vob"                  , "nf-fa-file_video_o"},
+			{".ogv"                  , "nf-fa-file_video_o"},
+			{".ogg"                  , "nf-fa-file_video_o"},
+			{".gifv"                 , "nf-fa-file_video_o"},
+			{".avi"                  , "nf-fa-file_video_o"},
+			{".mov"                  , "nf-fa-file_video_o"},
+			{".qt"                   , "nf-fa-file_video_o"},
+			{".wmv"                  , "nf-fa-file_video_o"},
+			{".yuv"                  , "nf-fa-file_video_o"},
+			{".rm"                   , "nf-fa-file_video_o"},
+			{".rmvb"                 , "nf-fa-file_video_o"},
+			{".mp4"                  , "nf-fa-file_video_o"},
+			{".mpg"                  , "nf-fa-file_video_o"},
+			{".mp2"                  , "nf-fa-file_video_o"},
+			{".mpeg"                 , "nf-fa-file_video_o"},
+			{".mpe"                  , "nf-fa-file_video_o"},
+			{".mpv"                  , "nf-fa-file_video_o"},
+			{".m2v"                  , "nf-fa-file_video_o"},
+
+			// Email
+			{".ics"                  , "nf-fa-calendar"},
+
+			// Certificates
+			{".cer"                  , "nf-fa-certificate"},
+			{".cert"                 , "nf-fa-certificate"},
+			{".crt"                  , "nf-fa-certificate"},
+			{".pfx"                  , "nf-fa-certificate"},
+
+			// Keys
+			{".pem"                  , "nf-fa-key"},
+			{".pub"                  , "nf-fa-key"},
+			{".key"                  , "nf-fa-key"},
+			{".asc"                  , "nf-fa-key"},
+			{".gpg"                  , "nf-fa-key"},
+
+			// Fonts
+			{".woff"                 , "nf-fa-font"},
+			{".woff2"                , "nf-fa-font"},
+			{".ttf"                  , "nf-fa-font"},
+			{".eot"                  , "nf-fa-font"},
+			{".suit"                 , "nf-fa-font"},
+			{".otf"                  , "nf-fa-font"},
+			{".bmap"                 , "nf-fa-font"},
+			{".fnt"                  , "nf-fa-font"},
+			{".odttf"                , "nf-fa-font"},
+			{".ttc"                  , "nf-fa-font"},
+			{".font"                 , "nf-fa-font"},
+			{".fonts"                , "nf-fa-font"},
+			{".sui"                  , "nf-fa-font"},
+			{".ntf"                  , "nf-fa-font"},
+			{".mrg"                  , "nf-fa-font"},
+
+			// Ruby
+			{".rb"                   , "nf-oct-ruby"},
+			{".erb"                  , "nf-oct-ruby"},
+			{".gemfile"              , "nf-oct-ruby"},
+			{"rakefile"              , "nf-oct-ruby"},
+
+			// FSharp
+			{".fs"                   , "nf-dev-fsharp"},
+			{".fsx"                  , "nf-dev-fsharp"},
+			{".fsi"                  , "nf-dev-fsharp"},
+			{".fsproj"               , "nf-dev-fsharp"},
+
+			// Docker
+			{".dockerignore"         , "nf-dev-docker"},
+			{".dockerfile"           , "nf-dev-docker"},
+
+			// VSCode
+			{".vscodeignore"         , "nf-fa-gear"},
+			{".vsixmanifest"         , "nf-fa-gear"},
+			{".vsix"                 , "nf-fa-gear"},
+			{".code-workplace"       , "nf-fa-gear"},
+
+			// Sublime
+			{".sublime-project"      , "nf-dev-sublime"},
+			{".sublime-workspace"    , "nf-dev-sublime"},
+
+			{".lock"                 , "nf-fa-lock"},
+
+			// Terraform
+			{".tf"                   , "nf-dev-code_badge"},
+			{".tfvars"               , "nf-dev-code_badge"},
+			{".tf.json"              , "nf-dev-code_badge"},
+			{".tfvars.json"          , "nf-dev-code_badge"},
+			{".auto.tfvars"          , "nf-dev-code_badge"},
+			{".auto.tfvars.json"     , "nf-dev-code_badge"},
+
+			// Disk Image
+			{".vmdk"                 , "nf-mdi-harddisk"},
+			{".vhd"                  , "nf-mdi-harddisk"},
+			{".vhdx"                 , "nf-mdi-harddisk"},
+			{".img"                  , "nf-fae-disco"},
+			{".iso"                  , "nf-fae-disco"},
+
+			// R language
+			{".R"                    , "nf-mdi-language_r"},
+			{".Rmd"                  , "nf-mdi-language_r"},
+			{".Rproj"                , "nf-mdi-language_r"},
+
+			// Julia language
+			{".jl"                   , "nf-seti-julia"},
+
+			// Vim
+			{".vim"                  , "nf-custom-vim"},
+
+			// Puppet
+			{".pp"                   , "nf-custom-puppet"},
+			{".epp"                  , "nf-custom-puppet"},
+		};
+	}
+}

+ 30 - 7
UICatalog/Scenarios/FileDialogExamples.cs

@@ -13,7 +13,6 @@ namespace UICatalog.Scenarios {
 	[ScenarioCategory ("Files and IO")]
 	public class FileDialogExamples : Scenario {
 		private CheckBox cbMustExist;
-		private CheckBox cbUnicode;
 		private CheckBox cbUseColors;
 		private CheckBox cbCaseSensitive;
 		private CheckBox cbAllowMultipleSelection;
@@ -23,6 +22,7 @@ namespace UICatalog.Scenarios {
 
 		private RadioGroup rgCaption;
 		private RadioGroup rgOpenMode;
+		private RadioGroup rgIcons;
 		private RadioGroup rgAllowedTypes;
 
 		public override void Setup ()
@@ -33,9 +33,6 @@ namespace UICatalog.Scenarios {
 			cbMustExist = new CheckBox ("Must Exist") { Checked = true, Y = y++, X = x };
 			Win.Add (cbMustExist);
 
-			cbUnicode = new CheckBox ("UseUnicode") { Checked = FileDialogStyle.DefaultUseUnicodeCharacters, Y = y++, X = x };
-			Win.Add (cbUnicode);
-
 			cbUseColors = new CheckBox ("Use Colors") { Checked = FileDialogStyle.DefaultUseColors, Y = y++, X = x };
 			Win.Add (cbUseColors);
 
@@ -69,7 +66,7 @@ namespace UICatalog.Scenarios {
 			Win.Add (rgCaption);
 
 			y = 0;
-			x = 37;
+			x = 34;
 
 			Win.Add (new LineView (Orientation.Vertical) {
 				X = x++,
@@ -82,6 +79,23 @@ namespace UICatalog.Scenarios {
 			rgOpenMode.RadioLabels = new NStack.ustring [] { "File", "Directory", "Mixed" };
 			Win.Add (rgOpenMode);
 
+			y = 0;
+			x = 48;
+
+			Win.Add (new LineView (Orientation.Vertical) {
+				X = x++,
+				Y = 1,
+				Height = 4
+			});
+			Win.Add (new Label ("Icons") { X = x++, Y = y++ });
+
+			rgIcons = new RadioGroup { X = x, Y = y };
+			rgIcons.RadioLabels = new NStack.ustring [] { "None", "Unicode", "Nerd*" };
+			Win.Add (rgIcons);
+
+			Win.Add(new Label("* Requires installing Nerd fonts"){Y = Pos.AnchorEnd(2)});
+			Win.Add(new Label("  (see: https://github.com/devblackops/Terminal-Icons)"){Y = Pos.AnchorEnd(1)});
+
 			y = 5;
 			x = 24;
 
@@ -137,7 +151,14 @@ namespace UICatalog.Scenarios {
 					fd.FilesSelected += ConfirmOverwrite;
 				}
 
-				fd.Style.UseUnicodeCharacters = cbUnicode.Checked ?? false;
+				if(rgIcons.SelectedItem == 1)
+				{
+					fd.Style.UseUnicodeCharacters = true;
+				}
+				else if(rgIcons.SelectedItem == 2)
+				{
+					fd.Style.UseNerdForIcons();
+				}
 
 				if (cbCaseSensitive.Checked ?? false) {
 
@@ -149,10 +170,12 @@ namespace UICatalog.Scenarios {
 				fd.Style.TreeStyle.ShowBranchLines = cbShowTreeBranchLines.Checked ?? false;
 				fd.Style.TableStyle.AlwaysShowHeaders = cbAlwaysTableShowHeaders.Checked ?? false;
 
+				var dirInfoFactory = new FileSystem().DirectoryInfo;
+
 				if (cbDrivesOnlyInTree.Checked ?? false) {
 					fd.Style.TreeRootGetter = () => {
 						return System.Environment.GetLogicalDrives ()
-						.Select (d => new FileDialogRootTreeNode (d, new DirectoryInfo (d)));
+						.Select (d => new FileDialogRootTreeNode (d, dirInfoFactory.New(d)));
 					};
 				}
 

+ 37 - 0
UnitTests/FileServices/NerdFontsTests.cs

@@ -0,0 +1,37 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.IO.Abstractions.TestingHelpers;
+using System.Linq;
+using Terminal.Gui;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Terminal.Gui.FileServicesTests {
+
+    public class NerdFontTests
+    {
+        [Fact]
+        public void TestAllFilenamesMapToKnownGlyphs()
+        {
+            var f = new NerdFonts();
+            foreach(var k in f.FilenameToIcon)
+            {
+                Assert.Contains(k.Value, f.Glyphs.Keys);
+            }
+        }
+
+
+        [Fact]
+        public void TestAllExtensionsMapToKnownGlyphs()
+        {
+            var f = new NerdFonts();
+            foreach(var k in f.ExtensionToIcon)
+            {
+                Assert.Contains(k.Value, f.Glyphs.Keys);
+            }
+        }
+    }
+
+}