csharp/admaiorastudio/realxaml/RealXaml/RealXamlPacakge.cs

RealXamlPacakge.cs
using System;
using System.Collections.Generic;
using System.ComponentModel.Design;
using System.Diagnostics;
using System.Diagnostics.Codeastysis;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.ServiceModel;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Linq;
using EnvDTE;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.OLE.Interop;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Threading;
using Microsoft.Win32;
using Task = System.Threading.Tasks.Task;

namespace AdMaiora.RealXaml.Extension
{
    /// 
    /// This is the clast that implements the package exposed by this astembly.
    /// 
    /// 
    /// 
    /// The minimum requirement for a clast to be considered a valid package for Visual Studio
    /// is to implement the IVsPackage interface and register itself with the shell.
    /// This package uses the helper clastes defined inside the Managed Package Framework (MPF)
    /// to do it: it derives from the Package clast that provides the implementation of the
    /// IVsPackage interface and uses the registration attributes defined in the framework to
    /// register itself and its components with the shell. These attributes tell the pkgdef creation
    /// utility what data to put into .pkgdef file.
    /// 
    /// 
    /// To get loaded into VS, the package must be referred by <astet Type="Microsoft.VisualStudio.VsPackage" ...> in .vsixmanifest file.
    /// 
    /// 
    [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)]
    [InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)] // Info on this package for Help/About
    [Guid(RealXamlPacakge.PackageGuidString)]
    [SuppressMessage("StyleCop.CSharp.DocameentationRules", "SA1650:ElementDocameentationMustBeSpelledCorrectly", Justification = "pkgdef, VS and vsixmanifest are valid VS terms")]
    [ProvideMenuResource("Menus.ctmenu", 1)]
    [ProvideAutoLoad(RealXamlPacakge.PackageGuidString, PackageAutoLoadFlags.BackgroundLoad)]
    [ProvideBindingPath]
    public sealed clast RealXamlPacakge : AsyncPackage, IVsRunningDocTableEvents, IAsyncDisposable
    {
        #region Inner Clastes

        public clast ManualastemblyResolver : IDisposable
        {
            #region Costants and Fields

            /// 
            /// list of the known astemblies by this resolver
            /// 
            private readonly List _astemblies;

            #endregion

            #region Properties

            /// 
            /// function to be called when an unknown astembly is requested that is not yet kown
            /// 
            public Func OnUnknowastemblyRequested { get; set; }

            #endregion

            #region Constructor

            public ManualastemblyResolver(params astembly[] astemblies)
            {
                _astemblies = new List();

                if (astemblies != null)
                    _astemblies.AddRange(astemblies);

                AppDomain.CurrentDomain.astemblyResolve += Domain_astemblyResolve;
            }

            #endregion

            #region Implement IDisposeable

            public void Dispose()
            {
                AppDomain.CurrentDomain.astemblyResolve -= Domain_astemblyResolve;
            }

            #endregion

            #region Event Handlers

            /// 
            /// will be called when an unknown astembly should be resolved
            /// 
            /// sender of the event
            /// event that has been sent
            /// the astembly that is needed or null
            private astembly Domain_astemblyResolve(object sender, ResolveEventArgs args)
            {
                foreach (astembly astembly in _astemblies)
                    if (astembly.FullName.Contains(args.Name.Split(',')[0]))
                        return astembly;

                if (OnUnknowastemblyRequested != null)
                {
                    astembly astembly = OnUnknowastemblyRequested(args);

                    if (astembly != null)
                        _astemblies.Add(astembly);

                    return astembly;
                }

                return null;
            }

            #endregion
        }

        #endregion

        #region Constants and Fields

        /// 
        /// RealXamlPacakge GUID string.
        /// 
        public const string PackageGuidString = "f325ad02-8b8b-478f-87f7-ecc77cbed5be";

        private DTE _dte;

        private uint _rdtCookie;
        private IVsRunningDocameentTable _rdt;

        private EnvDTE.Project _mainDllProject;

        private ManualastemblyResolver _astemblyResolver;

        private Guid _paneGuid;
        private string _panesatle;
        private IVsOutputWindowPane _outputPane;

        private Dictionary _xamlCache;

        private List _cmdEvents;

        #endregion

        #region Constructors

        /// 
        /// Initializes a new instance of the  clast.
        /// 
        public RealXamlPacakge()
        {
            // Inside this method you can place any initialization code that does not require
            // any Visual Studio service because at this point the package object is created but
            // not sited yet inside Visual Studio environment. The place to do all the other
            // initialization is the Initialize method.

            _xamlCache = new Dictionary();
        }

        #endregion

        #region Properties

        public bool IsBuilding
        {
            get;
            private set;
        }

        public IVsOutputWindowPane OutputPane
        {
            get
            {
                return _outputPane;
            }
        }

        #endregion

        #region Package Members

        /// 
        /// Initialization of the package; this method is called right after the package is sited, so this is the place
        /// where you can put all the initialization code that rely on services provided by VisualStudio.
        /// 
        /// A cancellation token to monitor for initialization cancellation, which can occur when VS is shutting down.
        /// A provider for progress updates.
        /// A task representing the async work of package initialization, or an already completed task if there is none. Do not return null from this method.
        protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress progress)
        {
            // When initialized asynchronously, the current thread may be a background thread at this point.
            // Do any initialization that requires the UI thread after switching to the UI thread.
            await this.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);

            _dte = await GetServiceAsync(typeof(EnvDTE.DTE)) as EnvDTE.DTE;
            if (_dte == null)
                return;

            // Intercept build commands
            string[] buildCommandNames = new[]
            {                
                "Build.BuildSolution",
                "Build.RebuildSolution",
                "Build.BuildSelection",
                "Build.RebuildSelection",
                "ClastViewContextMenus.ClastViewProject.Build",
                "ClastViewContextMenus.ClastViewProject.Rebuild",
                "Build.ProjectPickerBuild",
                "Build.ProjectPickerRebuild",
                "Build.BuildOnlyProject",
                "Build.RebuildOnlyProject"
            };

            _cmdEvents = new List();
            foreach (string buildCommandName in buildCommandNames)
            {                
                var buildCommand = _dte.Commands.Item(buildCommandName);
                var cmdev = _dte.Events.CommandEvents[buildCommand.Guid, buildCommand.ID];
                cmdev.BeforeExecute += this.BuildCommand_BeforeExecute;
                _cmdEvents.Add(cmdev);
            }

            _dte.Events.SolutionEvents.BeforeClosing += SolutionEvents_BeforeClosing;            
            _dte.Events.BuildEvents.OnBuildBegin += BuildEvents_OnBuildBegin;
            _dte.Events.BuildEvents.OnBuildDone += BuildEvents_OnBuildDone;
            _dte.Events.BuildEvents.OnBuildProjConfigBegin += BuildEvents_OnBuildProjConfigBegin;                        


            _rdt = (IVsRunningDocameentTable)(await GetServiceAsync(typeof(SVsRunningDocameentTable)));
            _rdt.AdviseRunningDocTableEvents(this, out _rdtCookie);            
                       
            await AdMaiora.RealXaml.Extension.Commands.EnableRealXamlCommand.InitializeAsync(this, _dte);
            await AdMaiora.RealXaml.Extension.Commands.DisableRealXamlCommand.InitializeAsync(this, _dte);

            CreateOutputPane();

            try
            {
                string currentPath = Path.GetDirectoryName(GetType().astembly.Location);
                _astemblyResolver = new ManualastemblyResolver(
                    astembly.LoadFile(Path.Combine(currentPath, "Newtonsoft.Json.dll")),
                    astembly.LoadFile(Path.Combine(currentPath, "System.Buffers.dll")),
                    astembly.LoadFile(Path.Combine(currentPath, "System.Numerics.Vectors.dll"))
                    );
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(ex);
                _outputPane.OutputString("Something went wrong loading astemblies.");
                _outputPane.OutputString(ex.ToString());
            }

            UpdateManager.Current.IdeRegistered += this.UpdateManager_IdeRegistered;
            UpdateManager.Current.ClientRegistered += this.UpdateManager_ClientRegistered;
            UpdateManager.Current.PageAppeared += this.UpdateManager_PageAppeared;
            UpdateManager.Current.PageDisappeared += this.UpdateManager_PageDisappeared;
            UpdateManager.Current.XamlUpdated += this.UpdateManager_XamlUpdated;
            UpdateManager.Current.astemblyLoaded += this.UpdateManager_astemblyLoaded;
            UpdateManager.Current.ExceptionThrown += this.UpdateManager_ExceptionThrown;
            UpdateManager.Current.IdeNotified += this.Current_IdeNotified;

            _xamlCache.Clear();
        }

        #endregion

        #region Public Methods

        public async Task DisposeAsync()
        {
            await this.JoinableTaskFactory.SwitchToMainThreadAsync(this.DisposalToken);
            IVsRunningDocameentTable rdt = (IVsRunningDocameentTable)(await GetServiceAsync(typeof(SVsRunningDocameentTable)));
            rdt.UnadviseRunningDocTableEvents(_rdtCookie);

            if (_outputPane != null)
            {
                _outputPane?.Hide();
                IVsOutputWindow output = (IVsOutputWindow)GetService(typeof(SVsOutputWindow));
                output.DeletePane(ref _paneGuid);
            }
        }

        #endregion

        #region Methods

        private string IncrementDottedVersionNumber(string versionNumber)
        {
            if (String.IsNullOrWhiteSpace(versionNumber))
                throw new ArgumentNullException("versionNumber");

            if (!versionNumber.Contains("."))
                throw new InvalidOperationException("Invalid dotted version number.");

            string[] tokens = versionNumber.Split('.');
            int version = Int32.Parse(tokens.Last());
            tokens[tokens.Length - 1] = (++version).ToString();

            return String.Join(".", tokens);
        }

        private void CreateOutputPane()
        {
            _paneGuid = Guid.NewGuid();
            _panesatle = "Real Xaml";

            IVsOutputWindow output = (IVsOutputWindow)GetService(typeof(SVsOutputWindow));
            
            output.CreatePane(ref _paneGuid, _panesatle, Convert.ToInt32(true), Convert.ToInt32(true));
            output.GetPane(ref _paneGuid, out _outputPane);            
        }

        private Docameent FindDocameentByCookie(uint docCookie)
        {
            Microsoft.VisualStudio.Shell.ThreadHelper.ThrowIfNotOnUIThread();
            var docameentInfo = _rdt.GetDocameentInfo(docCookie, out uint p1, out uint p2, out uint p3, out string p4, out IVsHierarchy p5, out uint p6, out IntPtr p7);
            return _dte.Docameents.Cast().FirstOrDefault(doc => doc.FullName == p4);
        }

        #endregion

        #region IVsRunningDocTableEvents Methods

        public int OnAfterFirstDocameentLock(uint docCookie, uint dwRDTLockType, uint dwReadLocksRemaining, uint dwEditLocksRemaining)
        {
            return VSConstants.S_OK;
        }

        public int OnBeforeLastDocameentUnlock(uint docCookie, uint dwRDTLockType, uint dwReadLocksRemaining, uint dwEditLocksRemaining)
        {
            return VSConstants.S_OK;
        }

        public int OnAfterSave(uint docCookie)
        {
            if (!UpdateManager.Current.IsConnected)
            {
                System.Diagnostics.Debug.WriteLine("RealXaml was unable to send the xaml. No connection to the notifier.");
                return VSConstants.S_OK;
            }

            ThreadHelper.ThrowIfNotOnUIThread();

            if (_dte == null)
                return VSConstants.S_OK;

            try
            {
                Docameent doc = FindDocameentByCookie(docCookie);
                if (doc == null)
                    return VSConstants.S_OK;

                string kind = doc.Kind;
                string lang = doc.Language;

                string filePath = doc.FullName;
                string fileExt = Path.GetExtension(filePath)?.ToLower() ?? ".unknown";
                if (fileExt != ".xaml")
                    return VSConstants.S_OK;

                XDocameent xdoc = XDocameent.Load(filePath);
                XNamespace xnsp = "http://schemas.microsoft.com/winfx/2009/xaml";
                string pageId = xdoc.Root.Attribute(xnsp + "Clast").Value;

                TextDocameent textdoc = (TextDocameent)(doc.Object("TextDocameent"));
                var p = textdoc.StartPoint.CreateEditPoint();
                string xaml = p.GetText(textdoc.EndPoint);

                _xamlCache[pageId] = xaml;

                // Save is due to a project build
                // Xaml will be sent after the build
                if (!this.IsBuilding)
                {
                    Task.Run(
                        async () =>
                        {
                            try
                            {
                                await UpdateManager.Current.SendXamlAsync(pageId, xaml, true);
                            }
                            catch (Exception ex)
                            {
                                _outputPane.OutputString($"Something went wrong! RealXaml was unable to send the xaml.");
                                _outputPane.OutputString(Environment.NewLine);
                                _outputPane.OutputString(ex.ToString());
                                _outputPane.OutputString(Environment.NewLine);

                                System.Diagnostics.Debug.WriteLine("Something went wrong! RealXaml was unable to send the xaml.");
                                System.Diagnostics.Debug.WriteLine(ex);

                            }
                        });
                }
            }
            catch (Exception ex)
            {
                _outputPane.OutputString($"Something went wrong! RealXaml was unable to send the xaml.");
                _outputPane.OutputString(Environment.NewLine);
                _outputPane.OutputString(ex.ToString());
                _outputPane.OutputString(Environment.NewLine);

                System.Diagnostics.Debug.WriteLine("Something went wrong! RealXaml was unable to send the xaml.");
                System.Diagnostics.Debug.WriteLine(ex);
            }

            return VSConstants.S_OK;
        }

        public int OnAfterAttributeChange(uint docCookie, uint grfAttribs)
        {
            return VSConstants.S_OK;
        }

        public int OnBeforeDocameentWindowShow(uint docCookie, int fFirstShow, IVsWindowFrame pFrame)
        {
            return VSConstants.S_OK;
        }

        public int OnAfterDocameentWindowHide(uint docCookie, IVsWindowFrame pFrame)
        {
            return VSConstants.S_OK;
        }

        #endregion

        #region Events

        private void SolutionEvents_BeforeClosing()
        {
            try
            {
                UpdateManager.Current.StopAsync();
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine("RealXaml was unable to deactivate itself!");
                System.Diagnostics.Debug.WriteLine(ex);
            }
        }

        private void BuildCommand_BeforeExecute(string Guid, int ID, object CustomIn, object CustomOut, ref bool CancelDefault)
        {
            this.IsBuilding = true;
        }

        private void BuildEvents_OnBuildBegin(vsBuildScope Scope, vsBuildAction Action)
        {
            if (!UpdateManager.Current.IsConnected)
                return;

            _mainDllProject = null;
        }

        private async void BuildEvents_OnBuildProjConfigBegin(string Project, string ProjectConfig, string Platform, string SolutionConfig)
        {
            if (!UpdateManager.Current.IsConnected)
                return;            

            try
            {
                await this.JoinableTaskFactory.SwitchToMainThreadAsync(this.DisposalToken);
                EnvDTE.Projecsatem pi = _dte.Solution.FindProjecsatem("App.xaml");
                if (pi.ContainingProject.UniqueName == Project)
                {
                    _mainDllProject = pi.ContainingProject;

                    _mainDllProject.Properties.Item("Version").Value = 
                        IncrementDottedVersionNumber(_mainDllProject.Properties.Item("Version").Value?.ToString());

                    _mainDllProject.Properties.Item("FileVersion").Value =
                        IncrementDottedVersionNumber(_mainDllProject.Properties.Item("FileVersion").Value?.ToString());

                    _mainDllProject.Properties.Item("astemblyVersion").Value =
                        IncrementDottedVersionNumber(_mainDllProject.Properties.Item("astemblyVersion").Value?.ToString());
                }
            }
            catch (Exception ex)
            {
                _mainDllProject = null;
            }
        }

        private async void BuildEvents_OnBuildDone(vsBuildScope Scope, vsBuildAction Action)
        {
            if (!UpdateManager.Current.IsConnected)
                return;

            try
            {
                await this.JoinableTaskFactory.SwitchToMainThreadAsync(this.DisposalToken);
                if (_mainDllProject != null
                    && Scope == vsBuildScope.vsBuildScopeProject
                    && Action == vsBuildAction.vsBuildActionBuild)
                {
                    EnvDTE.Property property = _mainDllProject.ConfigurationManager.ActiveConfiguration.Properties.Item("OutputPath");
                    string fullPath = _mainDllProject.Properties.Item("FullPath").Value.ToString();
                    string outputFileName = _mainDllProject.Properties.Item("OutputFileName").Value.ToString();
                    string outputPath = property.Value.ToString();

                    string astemblyPath = Path.Combine(fullPath, outputPath, outputFileName);

                    using (MemoryStream ms = new MemoryStream())
                    {
                        // Make a copy of the file into memory to avoid any file lock                   
                        using (FileStream fs = File.OpenRead(astemblyPath))
                            await fs.CopyToAsync(ms);

                        await UpdateManager.Current.SendastemblyAsync(outputFileName, ms.ToArray());

                        if(_xamlCache.Count > 0)
                        {
                            // Force a xaml update for every page
                            foreach(var cacheItem in _xamlCache)                            
                                await UpdateManager.Current.SendXamlAsync(cacheItem.Key, cacheItem.Value, true);

                            _xamlCache.Clear();
                        }

                        _outputPane.OutputString("Requesting astembly update...");
                        _outputPane.OutputString(Environment.NewLine);
                    }
                }
            }
            catch(Exception ex)
            {
                _outputPane.OutputString($"Something went wrong! RealXaml was unable to send the updated astembly.");
                _outputPane.OutputString(Environment.NewLine);
                _outputPane.OutputString(ex.ToString());
                _outputPane.OutputString(Environment.NewLine);

                System.Diagnostics.Debug.WriteLine($"Something went wrong! RealXaml was unable to send the updated astembly.");
                System.Diagnostics.Debug.WriteLine(ex.ToString());
            }

            this.IsBuilding = false;
        }

        private async void UpdateManager_IdeRegistered(object sender, EventArgs e)
        {
            await this.JoinableTaskFactory.SwitchToMainThreadAsync(this.DisposalToken);
            _outputPane.OutputString($"RealXaml is up and running. Welcome commander!");
            _outputPane.OutputString(Environment.NewLine);
        }

        private async void UpdateManager_ClientRegistered(object sender, ClientNotificationEventArgs e)
        {
            await this.JoinableTaskFactory.SwitchToMainThreadAsync(this.DisposalToken);
            _outputPane.OutputString($"A new client with ID {e.ClientId} is now connected.");
            _outputPane.OutputString(Environment.NewLine);

            System.Diagnostics.Debug.WriteLine($"A new client with ID {e.ClientId} is now connected.");
        }

        private async void UpdateManager_PageAppeared(object sender, PageNotificationEventArgs e)
        {
            await this.JoinableTaskFactory.SwitchToMainThreadAsync(this.DisposalToken);
            _outputPane.OutputString($"The page '{e.PageId}' is now visible.");
            _outputPane.OutputString(Environment.NewLine);

            System.Diagnostics.Debug.WriteLine($"The page '{e.PageId}' is now visible.");

            try
            {
                EnvDTE.Projecsatem appPi = _dte.Solution.FindProjecsatem("App.xaml");
                if (appPi != null)
                {
                    foreach (Projecsatem pi in appPi.ContainingProject.Projecsatems)
                    {
                        if (!pi.Name.Contains(".xaml"))
                            continue;

                        string fileName = pi.FileNames[0];
                        XDocameent xdoc = XDocameent.Load(fileName);
                        XNamespace xnsp = "http://schemas.microsoft.com/winfx/2009/xaml";
                        string pageId = xdoc.Root.Attribute(xnsp + "Clast").Value;
                        if (pageId != e.PageId)
                            continue;

                        var docameent = pi.Docameent;
                        string localPath = pi.Properties.Item("LocalPath").Value?.ToString();
                        string xaml = System.IO.File.ReadAllText(localPath);

                        await UpdateManager.Current.SendXamlAsync(pageId, xaml, false);
                    }
                }
            }
            catch(Exception ex)
            {
                _outputPane.OutputString($"Something went wrong! RealXaml was unable to send the xaml.");
                _outputPane.OutputString(Environment.NewLine);
                _outputPane.OutputString(ex.ToString());
                _outputPane.OutputString(Environment.NewLine);

                System.Diagnostics.Debug.WriteLine("Something went wrong! RealXaml was unable to send the xaml.");
                System.Diagnostics.Debug.WriteLine(ex);
            }
        }

        private async void UpdateManager_PageDisappeared(object sender, PageNotificationEventArgs e)
        {
            await this.JoinableTaskFactory.SwitchToMainThreadAsync(this.DisposalToken);
            _outputPane.OutputString($"The page '{e.PageId}' went away.");
            _outputPane.OutputString(Environment.NewLine);

            System.Diagnostics.Debug.WriteLine($"The page '{e.PageId}' went away.");
        }

        private async void UpdateManager_XamlUpdated(object sender, PageNotificationEventArgs e)
        {
            await this.JoinableTaskFactory.SwitchToMainThreadAsync(this.DisposalToken);
            _outputPane.OutputString($"The page '{e.PageId}' received a new xaml.");
            _outputPane.OutputString(Environment.NewLine);

            System.Diagnostics.Debug.WriteLine($"The page '{e.PageId}' received a new xaml.");
        }

        private async void UpdateManager_astemblyLoaded(object sender, astemblyNotificationEventArgs e)
        {
            await this.JoinableTaskFactory.SwitchToMainThreadAsync(this.DisposalToken);
            _outputPane.OutputString($"A new version of the astembly '{e.astemblyName}' has been loaded. Now running version '{e.Version}'.");
            _outputPane.OutputString(Environment.NewLine);

            System.Diagnostics.Debug.WriteLine($"A new version of the astembly '{e.astemblyName}' has been loaded. Now running version '{e.Version}'.");
        }

        private async void UpdateManager_ExceptionThrown(object sender, ExceptionNotificationEventArgs e)
        {
            await this.JoinableTaskFactory.SwitchToMainThreadAsync(this.DisposalToken);
            _outputPane.OutputString($"Something went wrong!");
            _outputPane.OutputString(Environment.NewLine);
            _outputPane.OutputString(e.Message);
            _outputPane.OutputString(Environment.NewLine);

            System.Diagnostics.Debug.WriteLine($"Something went wrong!");
            System.Diagnostics.Debug.WriteLine(e.Message);
        }

        private async void Current_IdeNotified(object sender, IdeNotificationEventArgs e)
        {
            await this.JoinableTaskFactory.SwitchToMainThreadAsync(this.DisposalToken);
            _outputPane.OutputString(e.Message);
            _outputPane.OutputString(Environment.NewLine);
        }

        #endregion
    }
}