diff --git a/Classes/ButtonCommands/CreateFile.cs b/Classes/ButtonCommands/CreateFile.cs index 71baf01..12acd6f 100644 --- a/Classes/ButtonCommands/CreateFile.cs +++ b/Classes/ButtonCommands/CreateFile.cs @@ -10,50 +10,55 @@ using System.Windows.Forms; using System.Windows.Input; -namespace UnityDotsAuthoringGenerator.Classes -{ - public class CreateFile : ICommand +namespace UnityDotsAuthoringGenerator.Classes { +public class CreateFile : ICommand { + private string m_path; + private string m_content; + private string m_extension; + + public CreateFile(string extension, string content) { - private string m_path; - private string m_content; - private string m_extension; - - public CreateFile(string path, string extension, string content) { - m_path = path; - m_content = content; - m_extension = extension; - } + m_content = content; + m_extension = extension; + } - public void Execute(object parameter) { - try { - Microsoft.VisualStudio.Shell.ThreadHelper.ThrowIfNotOnUIThread(); - var canceled = !TextInputDialog.Show("Enter name", "Enter the name for the desired file:", "", - delegate (string obj) { return obj != ""; }, out var name); - if (canceled) { - return; - } - - if (!name.Contains(".")) { - name += m_extension; - } - var filePath = Path.Combine(m_path, name); - - m_content = Regex.Replace(m_content,"TEMPLATENAME_", Path.GetFileNameWithoutExtension(name)); - File.WriteAllText(filePath, m_content); - DteHelper.GetProject().ProjectItems.AddFromFile(filePath); + public void Execute(object parameter) + { + try { + Microsoft.VisualStudio.Shell.ThreadHelper.ThrowIfNotOnUIThread(); + var canceled = !TextInputDialog.Show("Enter name", "Enter the name for the desired file:", "", + delegate(string obj) { return obj != ""; }, out var name); + if (canceled) { + return; } - catch (Exception ex) { - Utils.ShowErrorBox(ex.Message); + if (!name.Contains(".")) { + name += m_extension; } - } - public event EventHandler CanExecuteChanged { - add => CommandManager.RequerySuggested += value; - remove => CommandManager.RequerySuggested -= value; - } + var path = Utils.AskUserForPath("Choose destination folder", DteHelper.GetSelectedPath(), ""); + if (path == "") { + return; + } + + var filePath = Path.Combine(path, name); - public bool CanExecute(object parameter) { - return true; + m_content = Regex.Replace(m_content, "TEMPLATENAME_", Path.GetFileNameWithoutExtension(name)); + File.WriteAllText(filePath, m_content); + DteHelper.GetProject().ProjectItems.AddFromFile(filePath); + } catch (Exception ex) { + Utils.ShowErrorBox(ex.Message); } } + + public event EventHandler CanExecuteChanged + { + add => CommandManager.RequerySuggested += value; + remove => CommandManager.RequerySuggested -= value; + } + + public bool CanExecute(object parameter) + { + return true; + } +} } diff --git a/Classes/Checkbox.cs b/Classes/Checkbox.cs new file mode 100644 index 0000000..22b914e --- /dev/null +++ b/Classes/Checkbox.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace UnityDotsAuthoringGenerator.Classes { + +internal class Checkbox { + public string Name { get; set; } + + public bool Checked + { + get { + if (SettingsManager.Instance.TryGet(Name) != "True") { + return false; + } + return true; + } + set { + SettingsManager.Instance.Set(Name, value.ToString()); + } + } + + public Checkbox(string name, bool defaultChecked) + { + Name = name; + var isChecked = SettingsManager.Instance.TryGet(name); + + if (isChecked == "") { + SettingsManager.Instance.Set(Name, defaultChecked.ToString()); + SettingsManager.Instance.SaveSettings(); + } else { + Checked = bool.Parse(isChecked); + } + } +} +} diff --git a/Classes/DteHelper.cs b/Classes/DteHelper.cs index 0a174c2..be8bd63 100644 --- a/Classes/DteHelper.cs +++ b/Classes/DteHelper.cs @@ -8,47 +8,105 @@ using System.Text; using System.Threading.Tasks; using UnityDotsAuthoringGenerator.Classes; +using System.Windows; -namespace UnityDotsAuthoringGenerator -{ - internal class DteHelper +namespace UnityDotsAuthoringGenerator { +internal class DteHelper { + public static DTE2 GetDte() { - public static DTE2 GetDte() { - return AsyncPackage.GetGlobalService(typeof(SDTE)) as DTE2; + return AsyncPackage.GetGlobalService(typeof(SDTE)) as DTE2; + } + + public static string GetSelectedPath() + { + ThreadHelper.ThrowIfNotOnUIThread(); + if (IsSolutionExplorerFocused()) { + return GetSelectedSolutionFileDirectory(); + } else if (GetActiveDocument() != null) { + return GetActiveDocument().Path; + } else { + return ""; } + } - public static string GetSelectedFileDirectory() { - var filePath = GetSelectedFilePath(); - return Utils.GetAsDirectory(filePath); + public static string GetSelectedFilePath() + { + ThreadHelper.ThrowIfNotOnUIThread(); + if (IsSolutionExplorerFocused()) { + return GetSelectedSolutionFilePath(); + } else { + return GetActiveDocument().FullName; } + } + + public static string GetSelectedSolutionFileDirectory() + { + var filePath = GetSelectedSolutionFilePath(); + return Utils.GetAsDirectory(filePath); + } - public static string GetSelectedFilePath() { - ThreadHelper.ThrowIfNotOnUIThread(); + public static string GetSelectedSolutionFilePath() + { + ThreadHelper.ThrowIfNotOnUIThread(); - UIHierarchy uih = GetDte().ToolWindows.SolutionExplorer; - Array selectedItems = (Array)uih.SelectedItems; - if (null != selectedItems) { - foreach (UIHierarchyItem selItem in selectedItems) { - var prjItem = selItem.Object as ProjectItem; - string filePath = prjItem.Properties.Item("FullPath").Value.ToString(); - return filePath; - } + UIHierarchy uih = GetDte().ToolWindows.SolutionExplorer; + Array selectedItems = (Array)uih.SelectedItems; + if (null != selectedItems) { + foreach (UIHierarchyItem selItem in selectedItems) { + var prjItem = selItem.Object as ProjectItem; + string filePath = prjItem.Properties.Item("FullPath").Value.ToString(); + return filePath; + } + } + return string.Empty; + } + + public static Document GetActiveDocument() + { + ThreadHelper.ThrowIfNotOnUIThread(); + try { + return GetDte().ActiveDocument; + } catch (Exception ex) { + + MessageBox.Show(string.Format("Failed getting active document: ", ex.Message), + "Error", MessageBoxButton.OK, MessageBoxImage.Error); + return null; + } + } + + public static bool IsSolutionExplorerFocused() + { + ThreadHelper.ThrowIfNotOnUIThread(); + try { + if (GetDte().ActiveWindow.ObjectKind.Equals(EnvDTE.Constants.vsWindowKindSolutionExplorer, + StringComparison.OrdinalIgnoreCase)) { + return true; } - return string.Empty; + } catch (Exception ex) { + + MessageBox.Show(string.Format("Failed checking if solution explorer is focused: ", ex.Message), + "Error", MessageBoxButton.OK, MessageBoxImage.Error); } + return false; + } - public static Project GetProject() { - ThreadHelper.ThrowIfNotOnUIThread(); + public static Project GetProject() + { + ThreadHelper.ThrowIfNotOnUIThread(); - UIHierarchy uih = GetDte().ToolWindows.SolutionExplorer; - Array selectedItems = (Array)uih.SelectedItems; - if (null != selectedItems) { - foreach (UIHierarchyItem selItem in selectedItems) { - var prjItem = selItem.Object as ProjectItem; + UIHierarchy uih = GetDte().ToolWindows.SolutionExplorer; + Array selectedItems = (Array)uih.SelectedItems; + if (null != selectedItems) { + foreach (UIHierarchyItem selItem in selectedItems) { + if (selItem.Object is ProjectItem prjItem) { return prjItem.ContainingProject; } + if (selItem.Object is Project prj) { + return prj; + } } - return null; } + return null; } } +} diff --git a/Classes/FolderPicker.cs b/Classes/FolderPicker.cs new file mode 100644 index 0000000..c05f1cc --- /dev/null +++ b/Classes/FolderPicker.cs @@ -0,0 +1,253 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; +using System.Windows; +using System.Windows.Interop; + +namespace UnityDotsAuthoringGenerator.Classes { + +// from here: https://stackoverflow.com/a/66187224/2269559 +public class FolderPicker { + private readonly List _resultPaths = new List(); + private readonly List _resultNames = new List(); + + public IReadOnlyList ResultPaths => _resultPaths; + public IReadOnlyList ResultNames => _resultNames; + public string ResultPath => ResultPaths.FirstOrDefault(); + public string ResultName => ResultNames.FirstOrDefault(); + public virtual string InputPath { get; set; } + public virtual bool ForceFileSystem { get; set; } + public virtual bool Multiselect { get; set; } + public virtual string Title { get; set; } + public virtual string OkButtonLabel { get; set; } + public virtual string FileNameLabel { get; set; } + + protected virtual int SetOptions(int options) + { + if (ForceFileSystem) { + options |= (int)FOS.FOS_FORCEFILESYSTEM; + } + + if (Multiselect) { + options |= (int)FOS.FOS_ALLOWMULTISELECT; + } + return options; + } + + // for WPF support + public bool? ShowDialog(Window owner = null, bool throwOnError = false) + { + owner = owner ?? Application.Current?.MainWindow; + return ShowDialog(owner != null ? new WindowInteropHelper(owner).Handle : IntPtr.Zero, throwOnError); + } + + // for all .NET + public virtual bool? ShowDialog(IntPtr owner, bool throwOnError = false) + { + var dialog = (IFileOpenDialog) new FileOpenDialog(); + if (!string.IsNullOrEmpty(InputPath)) { + if (CheckHr(SHCreateItemFromParsingName(InputPath, null, typeof(IShellItem).GUID, out var item), throwOnError) != 0) + return null; + + dialog.SetFolder(item); + } + + var options = FOS.FOS_PICKFOLDERS; + options = (FOS)SetOptions((int)options); + dialog.SetOptions(options); + + if (Title != null) { + dialog.SetTitle(Title); + } + + if (OkButtonLabel != null) { + dialog.SetOkButtonLabel(OkButtonLabel); + } + + if (FileNameLabel != null) { + dialog.SetFileName(FileNameLabel); + } + + if (owner == IntPtr.Zero) { + owner = Process.GetCurrentProcess().MainWindowHandle; + if (owner == IntPtr.Zero) { + owner = GetDesktopWindow(); + } + } + + var hr = dialog.Show(owner); + if (hr == ERROR_CANCELLED) + return null; + + if (CheckHr(hr, throwOnError) != 0) + return null; + + if (CheckHr(dialog.GetResults(out var items), throwOnError) != 0) + return null; + + items.GetCount(out var count); + for (var i = 0; i < count; i++) { + items.GetItemAt(i, out var item); + CheckHr(item.GetDisplayName(SIGDN.SIGDN_DESKTOPABSOLUTEPARSING, out var path), throwOnError); + CheckHr(item.GetDisplayName(SIGDN.SIGDN_DESKTOPABSOLUTEEDITING, out var name), throwOnError); + if (path != null || name != null) { + _resultPaths.Add(path); + _resultNames.Add(name); + } + } + return true; + } + + private static int CheckHr(int hr, bool throwOnError) + { + if (hr != 0 && throwOnError) + Marshal.ThrowExceptionForHR(hr); + return hr; + } + + [DllImport("shell32")] + private static extern int SHCreateItemFromParsingName([MarshalAs(UnmanagedType.LPWStr)] string pszPath, IBindCtx pbc, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IShellItem ppv); + + [DllImport("user32")] + private static extern IntPtr GetDesktopWindow(); + +#pragma warning disable IDE1006 // Naming Styles + private const int ERROR_CANCELLED = unchecked((int)0x800704C7); +#pragma warning restore IDE1006 // Naming Styles + + [ComImport, Guid("DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7")] // CLSID_FileOpenDialog + private class FileOpenDialog { } + + [ComImport, Guid("d57c7288-d4ad-4768-be02-9d969532d960"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + private interface IFileOpenDialog { + [PreserveSig] + int Show(IntPtr parent); // IModalWindow + [PreserveSig] + int SetFileTypes(); // not fully defined + [PreserveSig] + int SetFileTypeIndex(int iFileType); + [PreserveSig] + int GetFileTypeIndex(out int piFileType); + [PreserveSig] + int Advise(); // not fully defined + [PreserveSig] + int Unadvise(); + [PreserveSig] + int SetOptions(FOS fos); + [PreserveSig] + int GetOptions(out FOS pfos); + [PreserveSig] + int SetDefaultFolder(IShellItem psi); + [PreserveSig] + int SetFolder(IShellItem psi); + [PreserveSig] + int GetFolder(out IShellItem ppsi); + [PreserveSig] + int GetCurrentSelection(out IShellItem ppsi); + [PreserveSig] + int SetFileName([MarshalAs(UnmanagedType.LPWStr)] string pszName); + [PreserveSig] + int GetFileName([MarshalAs(UnmanagedType.LPWStr)] out string pszName); + [PreserveSig] + int SetTitle([MarshalAs(UnmanagedType.LPWStr)] string pszTitle); + [PreserveSig] + int SetOkButtonLabel([MarshalAs(UnmanagedType.LPWStr)] string pszText); + [PreserveSig] + int SetFileNameLabel([MarshalAs(UnmanagedType.LPWStr)] string pszLabel); + [PreserveSig] + int GetResult(out IShellItem ppsi); + [PreserveSig] + int AddPlace(IShellItem psi, int alignment); + [PreserveSig] + int SetDefaultExtension([MarshalAs(UnmanagedType.LPWStr)] string pszDefaultExtension); + [PreserveSig] + int Close(int hr); + [PreserveSig] + int SetClientGuid(); // not fully defined + [PreserveSig] + int ClearClientData(); + [PreserveSig] + int SetFilter([MarshalAs(UnmanagedType.IUnknown)] object pFilter); + [PreserveSig] + int GetResults(out IShellItemArray ppenum); + [PreserveSig] + int GetSelectedItems([MarshalAs(UnmanagedType.IUnknown)] out object ppsai); + } + + [ComImport, Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + private interface IShellItem { + [PreserveSig] + int BindToHandler(); // not fully defined + [PreserveSig] + int GetParent(); // not fully defined + [PreserveSig] + int GetDisplayName(SIGDN sigdnName, [MarshalAs(UnmanagedType.LPWStr)] out string ppszName); + [PreserveSig] + int GetAttributes(); // not fully defined + [PreserveSig] + int Compare(); // not fully defined + } + + [ComImport, Guid("b63ea76d-1f85-456f-a19c-48159efa858b"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + private interface IShellItemArray { + [PreserveSig] + int BindToHandler(); // not fully defined + [PreserveSig] + int GetPropertyStore(); // not fully defined + [PreserveSig] + int GetPropertyDescriptionList(); // not fully defined + [PreserveSig] + int GetAttributes(); // not fully defined + [PreserveSig] + int GetCount(out int pdwNumItems); + [PreserveSig] + int GetItemAt(int dwIndex, out IShellItem ppsi); + [PreserveSig] + int EnumItems(); // not fully defined + } + +#pragma warning disable CA1712 // Do not prefix enum values with type name + private enum SIGDN : uint { + SIGDN_DESKTOPABSOLUTEEDITING = 0x8004c000, + SIGDN_DESKTOPABSOLUTEPARSING = 0x80028000, + SIGDN_FILESYSPATH = 0x80058000, + SIGDN_NORMALDISPLAY = 0, + SIGDN_PARENTRELATIVE = 0x80080001, + SIGDN_PARENTRELATIVEEDITING = 0x80031001, + SIGDN_PARENTRELATIVEFORADDRESSBAR = 0x8007c001, + SIGDN_PARENTRELATIVEPARSING = 0x80018001, + SIGDN_URL = 0x80068000 + } + + [Flags] + private enum FOS { + FOS_OVERWRITEPROMPT = 0x2, + FOS_STRICTFILETYPES = 0x4, + FOS_NOCHANGEDIR = 0x8, + FOS_PICKFOLDERS = 0x20, + FOS_FORCEFILESYSTEM = 0x40, + FOS_ALLNONSTORAGEITEMS = 0x80, + FOS_NOVALIDATE = 0x100, + FOS_ALLOWMULTISELECT = 0x200, + FOS_PATHMUSTEXIST = 0x800, + FOS_FILEMUSTEXIST = 0x1000, + FOS_CREATEPROMPT = 0x2000, + FOS_SHAREAWARE = 0x4000, + FOS_NOREADONLYRETURN = 0x8000, + FOS_NOTESTFILECREATE = 0x10000, + FOS_HIDEMRUPLACES = 0x20000, + FOS_HIDEPINNEDPLACES = 0x40000, + FOS_NODEREFERENCELINKS = 0x100000, + FOS_OKBUTTONNEEDSINTERACTION = 0x200000, + FOS_DONTADDTORECENT = 0x2000000, + FOS_FORCESHOWHIDDEN = 0x10000000, + FOS_DEFAULTNOMINIMODE = 0x20000000, + FOS_FORCEPREVIEWPANEON = 0x40000000, + FOS_SUPPORTSTREAMABLEITEMS = unchecked((int)0x80000000) + } +#pragma warning restore CA1712 // Do not prefix enum values with type name +} +} diff --git a/Classes/SettingsManager.cs b/Classes/SettingsManager.cs index d4f3557..f5532d2 100644 --- a/Classes/SettingsManager.cs +++ b/Classes/SettingsManager.cs @@ -12,9 +12,11 @@ namespace UnityDotsAuthoringGenerator { internal class SettingsManager { public static string GENERATOR_PATH = "GenerationPath"; + public static string GENERATE_RELATIVE = "GenerateRelative"; public static string SNIPPETS_PATH = "SnippetsPath"; public static string FILES_PATH = "FilesPath"; public static string DISABLE_CLIPBOARD_MESSAGE = "ShowClipboardMessage"; + public static string PLAY_GENERATED_SOUND = "PlayGeneratedSound"; private const string SettingsFileName = "DotsGeneratorSettings.txt"; private Dictionary m_settings = new Dictionary(); diff --git a/Classes/Templates.cs b/Classes/Templates.cs index 6c2fc75..27684c4 100644 --- a/Classes/Templates.cs +++ b/Classes/Templates.cs @@ -12,109 +12,117 @@ using System.Text.RegularExpressions; using static System.Windows.Forms.VisualStyles.VisualStyleElement.Window; -namespace UnityDotsAuthoringGenerator.Classes -{ - internal class Templates +namespace UnityDotsAuthoringGenerator.Classes { +internal class Templates { + public Templates() { - public Templates() { - } - public void GenerateDefaultInstallation() { - ThreadHelper.ThrowIfNotOnUIThread(); - var basePath = Path.Combine(Path.GetDirectoryName(DteHelper.GetProject().FileName), "CodeTemplates"); + } + public void GenerateDefaultInstallation() + { + ThreadHelper.ThrowIfNotOnUIThread(); + var basePath = Path.Combine(Path.GetDirectoryName(DteHelper.GetProject().FileName), "CodeTemplates"); - var filePath = Path.Combine(basePath, "Files"); - filePath += Path.DirectorySeparatorChar; - GenerateExampleFileTemplates(filePath); - SettingsManager.Instance.Set(SettingsManager.FILES_PATH, filePath); + var filePath = Path.Combine(basePath, "Files"); + filePath += Path.DirectorySeparatorChar; + GenerateExampleFileTemplates(filePath); + SettingsManager.Instance.Set(SettingsManager.FILES_PATH, filePath); - var snippetsPath = Path.Combine(basePath, "Snippets"); - snippetsPath += Path.DirectorySeparatorChar; - GenerateExampleSnippets(snippetsPath); - SettingsManager.Instance.Set(SettingsManager.SNIPPETS_PATH, snippetsPath); - SettingsManager.Instance.SaveSettings(); + var snippetsPath = Path.Combine(basePath, "Snippets"); + snippetsPath += Path.DirectorySeparatorChar; + GenerateExampleSnippets(snippetsPath); + SettingsManager.Instance.Set(SettingsManager.SNIPPETS_PATH, snippetsPath); + SettingsManager.Instance.SaveSettings(); + } + + public void GenerateExampleFileTemplates(string path = "") + { + if (path == "") { + path = Utils.AskUserForPath("Provide a location for your template files. Note: All files in and below given folder" + " will be made available as pastable template!", "", SettingsManager.FILES_PATH); + } + if (path != "") { + generateExampleFiles("Templates.Files", path); } + } - public void GenerateExampleFileTemplates(string path = "") { - if (path == "") { - path = Utils.AskUserForPath("Provide a location for your template files. Note: All files in and below given folder" + - " will be made available as pastable template!", "", SettingsManager.FILES_PATH); - } - if (path != "") { - generateExampleFiles("Templates.Files", path); - } + public void GenerateExampleSnippets(string path = "") + { + if (path == "") { + path = Utils.AskUserForPath("Provide a location for your template snippets. Note: All files in and below given folder" + " will be processed and made be available as pastable snippets!", "", SettingsManager.SNIPPETS_PATH); } + if (path != "") { + generateExampleFiles("Templates.Snippets", path); + } + } - public void GenerateExampleSnippets(string path = "") { - if (path == "") { - path = Utils.AskUserForPath("Provide a location for your template snippets. Note: All files in and below given folder" + - " will be processed and made be available as pastable snippets!", "", SettingsManager.SNIPPETS_PATH); + private void generateExampleFiles(string resourceFolder, string generationPath) + { + ThreadHelper.ThrowIfNotOnUIThread(); + var fileTemplates = ResourceHelper.GetResourcesFromFolder(resourceFolder); + foreach ((string fileName, string content) in fileTemplates) { + if (!Directory.Exists(generationPath)) { + Directory.CreateDirectory(generationPath); } - if (path != "") { - generateExampleFiles("Templates.Snippets", path); + var fullFilePath = generationPath + fileName; + if (!File.Exists(fullFilePath)) { + File.WriteAllText(fullFilePath, content); + DteHelper.GetProject().ProjectItems.AddFromFile(fullFilePath); + } else { + MessageBox.Show(string.Format("File with name '{0}' already exists, skipped generating!", fileName), "Generation skipped", + MessageBoxButtons.OK, MessageBoxIcon.Warning); } } + } - private void generateExampleFiles(string resourceFolder, string generationPath) { - ThreadHelper.ThrowIfNotOnUIThread(); - var fileTemplates = ResourceHelper.GetResourcesFromFolder(resourceFolder); - foreach ((string fileName, string content) in fileTemplates) { - if(!Directory.Exists(generationPath)) { - Directory.CreateDirectory(generationPath); - } - var fullFilePath = generationPath + fileName; - if (!File.Exists(fullFilePath)) { - File.WriteAllText(fullFilePath, content); - DteHelper.GetProject().ProjectItems.AddFromFile(fullFilePath); - } - else { - MessageBox.Show(string.Format("File with name '{0}' already exists, skipped generating!", fileName), "Generation skipped", - MessageBoxButtons.OK, MessageBoxIcon.Warning); - } - } + public List<(string name, string content)> LoadFiles(string path) + { + if (path == "") { + return null; } + return loadFiles(path); + } - public List<(string name, string content)> LoadFiles(string path) { - if (path == "") { - return null; - } - return loadFiles(path); + public List<(string name, string content)> LoadSnippets(string path) + { + if (path == "") { + return null; } + var snippets = new List<(string name, string content)>(); + var files = loadFiles(path); - public List<(string name, string content)> LoadSnippets(string path) { - if (path == "") { - return null; - } - var snippets = new List<(string name, string content)>(); - var files = loadFiles(path); - - foreach ((string fileName, string fileContent) in files) { - var fileSnippets = Regex.Matches(fileContent, "\\/\\/ *snippet +(\\w*) +start\\r\\n((?:.*?|\\r\\n)*?).*\\/\\/ *snippet +stop", RegexOptions.IgnoreCase); + foreach ((string fileName, string fileContent) in files) { + var fileSnippets = Regex.Matches(fileContent, "\\/\\/ *snippet +(\\w*) +start\\r\\n((?:.*?|\\r\\n)*?).*\\/\\/ *snippet +stop", RegexOptions.IgnoreCase); - foreach (Match snippet in fileSnippets) { - var snippetName = snippet.Groups[1].Value; - var snippetContent = snippet.Groups[2].Value; - snippets.Add((snippetName, snippetContent)); - } + foreach (Match snippet in fileSnippets) { + var snippetName = snippet.Groups[1].Value; + var snippetContent = snippet.Groups[2].Value; + snippets.Add((snippetName, snippetContent)); } - return snippets; } + return snippets; + } - private List<(string name, string content)> loadFiles(string path) { - var fileContents = new List<(string name, string content)>(); - var filePaths = Directory.GetFiles(path, "*.cs", SearchOption.AllDirectories); - foreach (var filePath in filePaths) { - string content; - try { - content = File.ReadAllText(filePath); - } - catch (Exception ex) { - MessageBox.Show(string.Format("Failed reading file: {0}", ex.Message), "Failed reading file"); - continue; - } - var name = Path.GetFileName(filePath); - fileContents.Add((name, content)); - } + private List<(string name, string content)> loadFiles(string path) + { + var fileContents = new List<(string name, string content)>(); + string[] filePaths; + try { + filePaths = Directory.GetFiles(path, "*.cs", SearchOption.AllDirectories); + } catch (Exception ex) { + MessageBox.Show(string.Format("Failed reading directory: {0}", ex.Message), "Failed reading directory"); return fileContents; } + foreach (var filePath in filePaths) { + string content; + try { + content = File.ReadAllText(filePath); + } catch (Exception ex) { + MessageBox.Show(string.Format("Failed reading file: {0}", ex.Message), "Failed reading file"); + continue; + } + var name = Path.GetFileName(filePath); + fileContents.Add((name, content)); + } + return fileContents; } } +} diff --git a/Classes/Utils.cs b/Classes/Utils.cs index 26d0b05..05ddde1 100644 --- a/Classes/Utils.cs +++ b/Classes/Utils.cs @@ -3,44 +3,62 @@ using System.Windows.Forms; using System.IO; using Microsoft.VisualStudio.Shell; +using System.Media; -namespace UnityDotsAuthoringGenerator.Classes -{ - internal class Utils +namespace UnityDotsAuthoringGenerator.Classes { +internal class Utils { + // returns given path/file path as directory, ensuring it ends on path seperator and there is no file extension in it + public static string GetAsDirectory(string path) { - // returns given path/file path as directory, ensuring it ends on path seperator and there is no file extension in it - public static string GetAsDirectory(string path) { - if (path == "") { - return ""; - } - return Path.GetDirectoryName(path) + Path.DirectorySeparatorChar.ToString(); + if (path == "") { + return ""; } + return Path.GetDirectoryName(path) + Path.DirectorySeparatorChar.ToString(); + } - public static string AskUserForPath(string description, string openAt, string safeTo) { - ThreadHelper.ThrowIfNotOnUIThread(); - if (openAt == "" || (!Directory.Exists(openAt) && !File.Exists(openAt))) { - openAt = Path.GetDirectoryName(DteHelper.GetProject().FileName); + public static void PlaySound() + { + string soundFilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), @"Media\ding.wav"); + if (File.Exists(soundFilePath)) { + try { + using (SoundPlayer player = new SoundPlayer(soundFilePath)) + { + player.Play(); + } + } catch (Exception ex) { + Utils.ShowErrorBox(string.Format("Can't play sound: ", ex.Message)); } - FolderBrowserDialog dialog = new FolderBrowserDialog(); - dialog.Description = description; + } + } - dialog.SelectedPath = openAt; - var result = dialog.ShowDialog(); - if (result != DialogResult.OK) { - return ""; - } + public static string AskUserForPath(string description, string openAt, string safeTo) + { + ThreadHelper.ThrowIfNotOnUIThread(); + if (openAt == "" || (!Directory.Exists(openAt) && !File.Exists(openAt))) { + openAt = Path.GetDirectoryName(DteHelper.GetProject().FileName); + } - if (safeTo != "") { - SettingsManager.Instance.Set(safeTo, dialog.SelectedPath + Path.DirectorySeparatorChar); - SettingsManager.Instance.SaveSettings(); - } + var dialog = new FolderPicker(); + dialog.InputPath = openAt; + dialog.Title = description; - return dialog.SelectedPath + Path.DirectorySeparatorChar; + var result = dialog.ShowDialog(); + if (result != true) { + return ""; } - public static void ShowErrorBox(string error) { - MessageBox.Show(string.Format("An error occured: {0}", error), "Error", - MessageBoxButtons.OK, MessageBoxIcon.Error); + if (safeTo != "") { + SettingsManager.Instance.Set(safeTo, dialog.ResultName + Path.DirectorySeparatorChar); + SettingsManager.Instance.SaveSettings(); } + + return dialog.ResultName + Path.DirectorySeparatorChar; } + + public static void ShowErrorBox(string error) + { + MessageBox.Show(string.Format(error), "Error", + MessageBoxButtons.OK, MessageBoxIcon.Error); + } +} } diff --git a/Commands/GenerateAuthoringCommand.cs b/Commands/GenerateAuthoringCommand.cs index 8360b60..d2a25c7 100644 --- a/Commands/GenerateAuthoringCommand.cs +++ b/Commands/GenerateAuthoringCommand.cs @@ -1,9 +1,11 @@ using EnvDTE; using EnvDTE80; +using Microsoft.VisualStudio.Debugger.Interop; using Microsoft.VisualStudio.Settings; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.Shell.Settings; +using Microsoft.VisualStudio.TextTemplating.VSHost; using System; using System.ComponentModel.Design; using System.Globalization; @@ -13,6 +15,7 @@ using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; +using System.Windows.Input; using UnityDotsAuthoringGenerator.Classes; using Task = System.Threading.Tasks.Task; @@ -21,6 +24,9 @@ namespace UnityDotsAuthoringGenerator { /// Command handler /// internal sealed class GenerateAuthoringCommand { + + Checkbox chkBx_relativeGen; + Checkbox chkBx_playGenNotification; /// /// Command ID. /// @@ -49,7 +55,10 @@ private GenerateAuthoringCommand(AsyncPackage package, OleMenuCommandService com var menuCommandID = new CommandID(CommandSet, CommandId); var menuItem = new MenuCommand(this.Execute, menuCommandID); + commandService.AddCommand(menuItem); + chkBx_relativeGen = new Checkbox(SettingsManager.GENERATE_RELATIVE, true); + chkBx_playGenNotification = new Checkbox(SettingsManager.PLAY_GENERATED_SOUND, true); } /// @@ -187,20 +196,26 @@ private void Execute(object sender, EventArgs e) var addCompType = componentType.Contains("Shared") ? "AddSharedComponent" : "AddComponent"; fileContent.Append(string.Format(bakerBlueprint, name, addCompType, bakerValues.ToString())); } else { - fileContent.Append(string.Format(authoringBlueprint, name)); + fileContent.Append(string.Format(authoringBlueprint, name, "")); fileContent.Append(string.Format(bakerBufferBlueprint, name)); } // write file, add to VS project - var targetPath = SettingsManager.Instance.TryGet(SettingsManager.GENERATOR_PATH); - if (targetPath == "") { - targetPath = Path.GetDirectoryName(clickedFilePath) + Path.DirectorySeparatorChar; + string generatePath = SettingsManager.Instance.TryGet(SettingsManager.GENERATOR_PATH); + if (generatePath == "" | chkBx_relativeGen.Checked) { + generatePath = Utils.GetAsDirectory(clickedFilePath); + } else { + generatePath = SettingsManager.Instance.TryGet(SettingsManager.GENERATOR_PATH); } + var targetFile = Path.GetFileName(clickedFilePath.Replace(".cs", "_Authoring.cs")); - var destination = targetPath + targetFile; + var destination = generatePath + targetFile; try { File.WriteAllText(destination, fileContent.ToString()); DteHelper.GetProject().ProjectItems.AddFromFile(destination); + if (chkBx_playGenNotification.Checked) { + Utils.PlaySound(); + } } catch (Exception ex) { Utils.ShowErrorBox(ex.Message); } diff --git a/README.md b/README.md index 023bb74..ba01612 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,14 @@ When there is no path set, eihter for files or snippets, the extension will offe This folder is then set in the settigns aswell as source for your files or snippets. Having neither a snippets path nor a file path will trigger a installation dialog, which creates all examples in a project root based directory structure. - +# Keyboard shortcuts +It is possible to use Visual Stduios built in shortcut manager to change/set shortcuts for the commands provided by this extension. +The defaults are: +- Generate Authoring: `ALT + G` + +The shortcut manager is under `Tools > Options` search for `keyboard`. +All commands start with `DOTS` and can be found over that keyword easily: + +![image](https://github.com/simon-winter/UnityDotsAuthoringGenerator/assets/34577718/bb3bcd1f-10ad-42e4-b094-7a3598439f02) All template folders can be freely added/expanded for custom content. diff --git a/UnityDotsAuthoringGenerator.csproj b/UnityDotsAuthoringGenerator.csproj index 2e9c6a6..ac86dbb 100644 --- a/UnityDotsAuthoringGenerator.csproj +++ b/UnityDotsAuthoringGenerator.csproj @@ -48,7 +48,9 @@ + + diff --git a/UnityDotsAuthoringGeneratorPackage.vsct b/UnityDotsAuthoringGeneratorPackage.vsct index 2d7d936..ab36a64 100644 --- a/UnityDotsAuthoringGeneratorPackage.vsct +++ b/UnityDotsAuthoringGeneratorPackage.vsct @@ -1,29 +1,29 @@  - - - - + + - - + + - - - - - - - - - - - - + + + + + + + + + + + - - + - - - - - - + + + - - - + + - - - - + + + + + + + + + + - - - + + + + + + + + - - - - - - - - + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Windows/SettingsWindowControl.xaml b/Windows/SettingsWindowControl.xaml index 1603da1..b2f80ba 100644 --- a/Windows/SettingsWindowControl.xaml +++ b/Windows/SettingsWindowControl.xaml @@ -36,8 +36,11 @@ public partial class SettingsWindowControl : UserControl { + Checkbox chkBx_disableCopyHint; + Checkbox chkBx_relativeGen; + Checkbox chkBx_playGenNotification; + /// /// Initializes a new instance of the class. /// public SettingsWindowControl() { + chkBx_disableCopyHint = new Checkbox(SettingsManager.DISABLE_CLIPBOARD_MESSAGE, false); + chkBx_relativeGen = new Checkbox(SettingsManager.GENERATE_RELATIVE, true); + chkBx_playGenNotification = new Checkbox(SettingsManager.PLAY_GENERATED_SOUND, true); this.InitializeComponent(); this.Loaded += (object sender, RoutedEventArgs e) => { - text_Current.Text = DteHelper.GetSelectedFilePath(); - textBox_generate.Text = SettingsManager.Instance.TryGet(SettingsManager.GENERATOR_PATH); - textBox_snippetsPath.Text = SettingsManager.Instance.TryGet(SettingsManager.SNIPPETS_PATH); - textBox_filesPath.Text = SettingsManager.Instance.TryGet(SettingsManager.FILES_PATH); + text_Current.Text = DteHelper.GetSelectedPath(); + textBox_generate.Text = SettingsManager.Instance.TryGet(SettingsManager.GENERATOR_PATH) + " "; + textBox_snippetsPath.Text = SettingsManager.Instance.TryGet(SettingsManager.SNIPPETS_PATH) + " "; + textBox_filesPath.Text = SettingsManager.Instance.TryGet(SettingsManager.FILES_PATH) + " "; + checkbox_surpressCopyMsg.IsChecked = chkBx_disableCopyHint.Checked; + checkbox_generateRelative.IsChecked = chkBx_relativeGen.Checked; + checkbox_playGenSound.IsChecked = chkBx_playGenNotification.Checked; + + textBox_generate.IsEnabled = !chkBx_relativeGen.Checked; // scroll to end of line textBox_generate.Focus(); @@ -37,11 +49,13 @@ public SettingsWindowControl() private void button1_Click(object sender, RoutedEventArgs e) { - SettingsManager.Instance.Set(SettingsManager.GENERATOR_PATH, Utils.GetAsDirectory(textBox_generate.Text)); - SettingsManager.Instance.Set(SettingsManager.SNIPPETS_PATH, Utils.GetAsDirectory(textBox_snippetsPath.Text)); - SettingsManager.Instance.Set(SettingsManager.FILES_PATH, Utils.GetAsDirectory(textBox_filesPath.Text)); - SettingsManager.Instance.Set(SettingsManager.DISABLE_CLIPBOARD_MESSAGE, - (bool)checkbox_surpressCopyMsg.IsChecked ? "true" : "false"); + SettingsManager.Instance.Set(SettingsManager.GENERATOR_PATH, Utils.GetAsDirectory(textBox_generate.Text.Trim())); + SettingsManager.Instance.Set(SettingsManager.SNIPPETS_PATH, Utils.GetAsDirectory(textBox_snippetsPath.Text.Trim())); + SettingsManager.Instance.Set(SettingsManager.FILES_PATH, Utils.GetAsDirectory(textBox_filesPath.Text.Trim())); + + chkBx_disableCopyHint.Checked = (bool)checkbox_surpressCopyMsg.IsChecked; + chkBx_relativeGen.Checked = (bool)checkbox_generateRelative.IsChecked; + chkBx_playGenNotification.Checked = (bool)checkbox_playGenSound.IsChecked; SettingsManager.Instance.SaveSettings(); Window.GetWindow(this).Close(); @@ -54,17 +68,17 @@ private void button2_Click(object sender, RoutedEventArgs e) private void button_filesBrowse_Click(object sender, RoutedEventArgs e) { - textBox_filesPath.Text = Utils.AskUserForPath("",textBox_filesPath.Text, SettingsManager.FILES_PATH); + textBox_filesPath.Text = Utils.AskUserForPath("", textBox_filesPath.Text, SettingsManager.FILES_PATH) + " "; } private void button_snippetsBrowse_Click(object sender, RoutedEventArgs e) { - textBox_snippetsPath.Text = Utils.AskUserForPath("",textBox_snippetsPath.Text, SettingsManager.SNIPPETS_PATH); + textBox_snippetsPath.Text = Utils.AskUserForPath("", textBox_snippetsPath.Text, SettingsManager.SNIPPETS_PATH) + " "; } private void button_generateBrowse_Click(object sender, RoutedEventArgs e) { - textBox_generate.Text = Utils.AskUserForPath("", textBox_generate.Text, SettingsManager.GENERATOR_PATH); + textBox_generate.Text = Utils.AskUserForPath("", textBox_generate.Text, SettingsManager.GENERATOR_PATH) + " "; } private void button_snippetsCreate_Click(object sender, RoutedEventArgs e) @@ -74,5 +88,14 @@ private void button_snippetsCreate_Click(object sender, RoutedEventArgs e) private void button_filesCreate_Click(object sender, RoutedEventArgs e) { } + + private void CheckBox_Checked(object sender, RoutedEventArgs e) + { + textBox_generate.IsEnabled = !(bool)checkbox_generateRelative.IsChecked; + } + + private void checkbox_playGenSound_Checked(object sender, RoutedEventArgs e) + { + } } } diff --git a/Windows/TemplateWindowControl.xaml b/Windows/TemplateWindowControl.xaml index 5c234fe..514b341 100644 --- a/Windows/TemplateWindowControl.xaml +++ b/Windows/TemplateWindowControl.xaml @@ -10,14 +10,12 @@ d:DesignHeight="500" d:DesignWidth="300" Name="MyToolWindow"> - + - diff --git a/Windows/TemplateWindowControl.xaml.cs b/Windows/TemplateWindowControl.xaml.cs index 976507d..a300e31 100644 --- a/Windows/TemplateWindowControl.xaml.cs +++ b/Windows/TemplateWindowControl.xaml.cs @@ -6,96 +6,93 @@ using System.Windows.Controls; using UnityDotsAuthoringGenerator.Classes; -namespace UnityDotsAuthoringGenerator -{ +namespace UnityDotsAuthoringGenerator { +/// +/// Interaction logic for TemplateWindowControl. +/// +public partial class TemplateWindowControl : UserControl { /// - /// Interaction logic for TemplateWindowControl. + /// Initializes a new instance of the class. /// - public partial class TemplateWindowControl : UserControl + public TemplateWindowControl() { - /// - /// Initializes a new instance of the class. - /// - public TemplateWindowControl() { - this.InitializeComponent(); - var m_templates = new Templates(); + this.InitializeComponent(); + var m_templates = new Templates(); - this.Loaded += (object sender, RoutedEventArgs e) => { - wrapPanel_Files.Children.Clear(); - wrapPanel_Snippets.Children.Clear(); + this.Loaded += (object sender, RoutedEventArgs e) => + { + wrapPanel_Files.Children.Clear(); + wrapPanel_Snippets.Children.Clear(); - var filesPath = SettingsManager.Instance.TryGet(SettingsManager.FILES_PATH); - var snippetsPath = SettingsManager.Instance.TryGet(SettingsManager.SNIPPETS_PATH); - if (filesPath == "" && snippetsPath == "") { - var result = MessageBox.Show("No template paths in settings configured.\nDo you want to generate the default installation?\n\n" + - "This will generate a folder with some data in the root of the project and set all links in settings accordingly.", + var filesPath = SettingsManager.Instance.TryGet(SettingsManager.FILES_PATH); + var snippetsPath = SettingsManager.Instance.TryGet(SettingsManager.SNIPPETS_PATH); + if (filesPath == "" && snippetsPath == "") { + var result = MessageBox.Show("No template paths in settings configured.\nDo you want to generate the default installation?\n\n" + "This will generate a folder with some data in the root of the project and set all links in settings accordingly.", "Default installation?", MessageBoxButton.OKCancel, MessageBoxImage.Question); - if (result == MessageBoxResult.OK) { - m_templates.GenerateDefaultInstallation(); - Window.GetWindow(this).Close(); - return; - } + if (result == MessageBoxResult.OK) { + m_templates.GenerateDefaultInstallation(); + Window.GetWindow(this).Close(); + return; } + } - if (filesPath == "") { - var result = MessageBox.Show("No file template path in settings configured.\nDo you want to generate the default examples?", - "Template file path not set", MessageBoxButton.OKCancel, MessageBoxImage.Question); - if (result == MessageBoxResult.OK) { - m_templates.GenerateExampleFileTemplates(); - Window.GetWindow(this).Close(); - } - } - if (filesPath != "") { - foreach (var file in m_templates.LoadFiles(filesPath)) { - wrapPanel_Files.Children.Add(new Button() { - Height = 30, - Width = 120, - Margin = new Thickness(2, 2, 2, 2), - Content = Path.GetFileNameWithoutExtension(file.name), - Command = new CreateFile(DteHelper.GetSelectedFileDirectory(), Path.GetExtension(file.name), file.content) - }); - }; + if (filesPath == "") { + var result = MessageBox.Show("No file template path in settings configured.\nDo you want to generate the default examples?", + "Template file path not set", MessageBoxButton.OKCancel, MessageBoxImage.Question); + if (result == MessageBoxResult.OK) { + m_templates.GenerateExampleFileTemplates(); + Window.GetWindow(this).Close(); } + } + if (filesPath != "") { + foreach (var file in m_templates.LoadFiles(filesPath)) { + wrapPanel_Files.Children.Add(new Button() { + Height = 30, + Width = 120, + Margin = new Thickness(2, 2, 2, 2), + Content = Path.GetFileNameWithoutExtension(file.name), + Command = new CreateFile(Path.GetExtension(file.name), file.content) + }); + }; + } - if (snippetsPath == "") { - var result = MessageBox.Show("No snippets template path in settings configured.\nDo you want to generate the default examples?", - "Snippets path not set", MessageBoxButton.OKCancel, MessageBoxImage.Question); - if (result == MessageBoxResult.OK) { - m_templates.GenerateExampleSnippets(); - Window.GetWindow(this).Close(); - } - } - if (snippetsPath != "") { - foreach (var snippet in m_templates.LoadSnippets(snippetsPath)) { - wrapPanel_Snippets.Children.Add(new Button() { - Height = 30, - Width = 120, - Margin = new Thickness(2, 2, 2, 2), - Content = snippet.name, - Command = new Snippet(snippet.name, snippet.content) - }); - }; + if (snippetsPath == "") { + var result = MessageBox.Show("No snippets template path in settings configured.\nDo you want to generate the default examples?", + "Snippets path not set", MessageBoxButton.OKCancel, MessageBoxImage.Question); + if (result == MessageBoxResult.OK) { + m_templates.GenerateExampleSnippets(); + Window.GetWindow(this).Close(); } - }; - } + } + if (snippetsPath != "") { + foreach (var snippet in m_templates.LoadSnippets(snippetsPath)) { + wrapPanel_Snippets.Children.Add(new Button() { + Height = 30, + Width = 120, + Margin = new Thickness(2, 2, 2, 2), + Content = snippet.name, + Command = new Snippet(snippet.name, snippet.content) + }); + }; + } + }; + } - /// - /// Handles click on the button by displaying a message box. - /// - /// The event sender. - /// The event args. - [SuppressMessage("Microsoft.Globalization", "CA1300:SpecifyMessageBoxOptions", Justification = "Sample code")] - [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:ElementMustBeginWithUpperCaseLetter", Justification = "Default event handler naming pattern")] - private void button1_Click(object sender, RoutedEventArgs e) { - Window.GetWindow(this).Close(); - } + /// + /// Handles click on the button by displaying a message box. + /// + /// The event sender. + /// The event args. + [SuppressMessage("Microsoft.Globalization", "CA1300:SpecifyMessageBoxOptions", Justification = "Sample code")] + [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:ElementMustBeginWithUpperCaseLetter", Justification = "Default event handler naming pattern")] + private void button1_Click(object sender, RoutedEventArgs e) + { + Window.GetWindow(this).Close(); + } - private void button2_Click(object sender, RoutedEventArgs e) { - Window.GetWindow(this).Close(); - } - private void checkbox_surpressCopyMsg_Changed(object sender, RoutedEventArgs e) { - SettingsManager.Instance.Set(SettingsManager.DISABLE_CLIPBOARD_MESSAGE, - (bool)checkbox_surpressCopyMsg.IsChecked ? "true" : "false"); - } + private void button2_Click(object sender, RoutedEventArgs e) + { + Window.GetWindow(this).Close(); } } +} diff --git a/source.extension.vsixmanifest b/source.extension.vsixmanifest index 8338283..b70ec16 100644 --- a/source.extension.vsixmanifest +++ b/source.extension.vsixmanifest @@ -1,23 +1,14 @@ - + Unity Dots Authoring Generator - # Automatic Authoring+Baker generation from source file -- right click a file containing a single ComponentData or BufferElementData -- select 'Generate DOTS Authoring/Baker' command -- file gets generated nexto source file (configurable in settings) - -# Template posting -- right click any file or folder -- select 'Create from template...' command -- accept default installation -- use 'Create from template...' command to post templates -- empty path in settings to regenerate examples - -snippets are stored to clipboard. -Files are generated with provided file name and posted nexto selected location. -All template folders can be freely added/expanded for custom content. + - Automatic Authoring+Baker generation from source file +- Templated file generation (ISystem, IComponentData, ...) +- Snippet posting +All configurable and expandable by editing/adding files + https://github.com/simon-winter/UnityDotsAuthoringGenerator/tree/main + https://github.com/simon-winter/UnityDotsAuthoringGenerator/tree/main