using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel.Design;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Management.Instrumentation;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using TWVSExtension.Commands;
using TWVSExtension.Controls;
using TWVSExtension.Enums;
using TWVSExtension.Exceptions;
using TWVSExtension.Model;
using TWVSExtension.Package;
using TWVSExtension.Repository;
using TWVSExtension.Service;
using TWVSExtension.ToolWindow;
using TWVSExtension.Utils;
using System.Text.Json;
using Task = System.Threading.Tasks.Task;
namespace TWVSExtension
{
///
/// This is the class that implements the package exposed by this assembly.
///
///
///
/// The minimum requirement for a class 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 classes defined inside the Managed Package Framework (MPF)
/// to do it: it derives from the Package class 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 <Asset Type="Microsoft.VisualStudio.VsPackage" ...> in .vsixmanifest file.
///
///
[PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)]
[InstalledProductRegistration("TWVSSCM", "TallyWorld Source and Tools Control Management", "18.8.4")] // Info on this package for Help/About
[Guid(TWVSSCMPackage.PackageGuidString)]
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "pkgdef, VS and vsixmanifest are valid VS terms")]
[ProvideMenuResource("Menus.ctmenu", 1)]
[ProvideAutoLoad(UIContextGuids80.NoSolution, PackageAutoLoadFlags.BackgroundLoad)]
[ProvideService(typeof(STWExtnOptionsService))]
[ProvideOptionPage(typeof(TWVSExtensionsOptionPageGrid), "TWVSExtensions", "General Settings", 0, 0, true)]
[ProvideToolWindow(typeof(TWRepoExplorerWindow), Style = VsDockStyle.Tabbed, Window = EnvDTE.Constants.vsWindowKindOutput)]
[ProvideToolWindow(typeof(TWRepoDetailsWindow), Window = EnvDTE.Constants.vsWindowKindSolutionExplorer,
Style = VsDockStyle.Tabbed, Transient = true)]
[ProvideToolWindow(typeof(TWRepoFileHistoryWindow), Window = "DocumentWell", Style = VsDockStyle.Tabbed, Orientation = ToolWindowOrientation.none, Transient = true, MultiInstances = true)]
[ProvideToolWindow(typeof(TWRepoFileBlameWindow), Window = "DocumentWell", Style = VsDockStyle.Tabbed, Orientation = ToolWindowOrientation.none, Transient = true, MultiInstances = true)]
public class TWVSSCMPackage : TWAsyncPackage
{
///
/// TWVSExtensionPackage GUID string.
///
public const string PackageGuidString = "55d531bf-08bf-449e-a46a-96916825372e";
public TWVSExtensionsOptionPageGrid OptionPageInstance { get; set; }
public TWRepoDetailsWindowData TWRepoDetailsWindowData { get; set; }
public TWRepoFileData TWRepoFileData { get; set; }
public TWRepoExplorerWindowData TWRepoExplorerWindowData { get; }
private bool CommandsInitialized;
private bool disabled;
public CancellationToken CancellationToken { get; private set; }
public bool IsExtensionDisabled { get => disabled; }
///
/// Initializes a new instance of the class.
///
public TWVSSCMPackage() : base()
{
// 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.
//This is overall Extension service which provide loglevel and pmt functionality flag settings vai VSSTCM extension only
//if this extension is not installed, log level is set to minimal and PMT functionality is disabled
CurrentInstance = this;
TWRepoExplorerWindowData = new TWRepoExplorerWindowData(this);
}
#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)
{
this.CancellationToken = cancellationToken;
// 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 TWVSSCMInitializeAsync(cancellationToken, progress);
}
public async Task TWVSSCMInitializeAsync(CancellationToken cancellationToken, IProgress progress)
{
//string settingsKey = "TWRepoViews";
//int settingExists = 0;
//int propertyExists = 0;
//string repoView = "";
//string json = "";
//var storeManager = ServiceProvider.GlobalProvider.GetService(typeof(SVsSettingsManager)) as IVsSettingsManager;
//
//if (storeManager != null)
//{
// storeManager.GetWritableSettingsStore((uint)__VsSettingsScope.SettingsScope_UserSettings, out IVsWritableSettingsStore settingsStore);
// settingsStore.CollectionExists(settingsKey, out settingExists);
//
// if (settingExists == 1)
// {
// settingsStore.PropertyExists(settingsKey, "TWRepoView", out propertyExists);
// if (propertyExists == 1)
// {
// settingsStore.GetString(settingsKey, "TWRepoView", out json);
// repoView = json;
// }
// }
//}
// Was cancellation already requested?
if (cancellationToken.IsCancellationRequested)
{
cancellationToken.ThrowIfCancellationRequested();
}
try
{
progress.Report(new ServiceProgressData("Preparing TWVSSCM..."));
TWTemporaryFileStorage.Init();
IProgress init_progress = null;
// Was cancellation already requested?
if (cancellationToken.IsCancellationRequested)
{
cancellationToken.ThrowIfCancellationRequested();
}
try
{
ReportProgress(progress, init_progress, "Initializing Extension Data...");
await TWOutputPane.TWVSExtension.FocusAsync();
await TWVSSCMUtils.InitializeDataAsync(this, async (level, context, line, color) =>
{
await TWUtils.ShowTextToExtensionPaneAsync(level, context, line, color);
if (level < TWLogLevel.Diagnostic)
{
ReportProgress(progress, init_progress, context + " | " + line);
}
});
disabled = false;
}
catch (TWVSExtensionException e)
{
await TWUtils.PrintExceptionToOutputPaneAndErrorLogAsync(e, false);
await TWUtils.ShowErrorAsync(InfoBarTarget.MainWindow, $"TWVSSCM menu items will be disabled till the following error is rectified : {e.Message} \\n\n Use menu 'Extensions->TWVSSCM->Re-Initialize Extension' to enable after fixing the error.", e);
disabled = true;
}
catch (Exception e)
{
await TWUtils.PrintExceptionToOutputPaneAndErrorLogAsync(e, false);
await TWUtils.ShowAlertBoxBeforeAbortAsync($"Unable to load TWVSSCM : {e.Message}.", e);
disabled = true;
}
if (!CommandsInitialized)
{
ReportProgress(progress, init_progress, "Initializing command TWRepoExplorerToolWindowCommand ...");
await TWRepoExplorerToolWindowCommand.InitializeAsync(this);
ReportProgress(progress, init_progress, "Initializing file context commands ...");
await TWFileContextCommands.InitializeAsync(this);
CommandsInitialized = true;
}
ReportProgress(progress, init_progress, "Done.");
}
catch (Exception ex)
{
await TWUtils.PrintExceptionToOutputPaneAndErrorLogAsync("Package Init", ex, false);
await TWUtils.ShowAlertBoxBeforeAbortAsync($"Unable to load TWVSSCM : {ex.Message}.", ex);
}
}
public object CreateService(IServiceContainer container, Type serviceType)
{
if (typeof(STWExtnOptionsService) == serviceType)
return new TWExtnOptionsService(this);
return null;
}
private VSIXManifest currentManifest = null;
public override VSIXManifest GetManifest()
{
if (currentManifest == null)
{
Assembly assembly = Assembly.GetExecutingAssembly();
currentManifest = TWUtils.CreateManifest(assembly);
}
return currentManifest;
}
private void ReportProgress(IProgress progress, IProgress init_progress, string report)
{
progress.Report(new ServiceProgressData(report));
if (init_progress != null)
{
ThreadedWaitDialogProgressData data = new ThreadedWaitDialogProgressData("Initializing TWVSSCM, please wait...", report, report);
init_progress.Report(data);
}
}
#endregion Package Members
private TWVSExtensionsOptionPageGrid OptionPage
{
get
{
if (OptionPageInstance == null)
{
if (ThreadHelper.CheckAccess())
{
OptionPageInstance = (TWVSExtensionsOptionPageGrid)GetDialogPage(typeof(TWVSExtensionsOptionPageGrid));
}
}
if (OptionPageInstance != null)
{
return OptionPageInstance;
}
else
{
TWVSExtensionsOptionPageGrid optionPage = new TWVSExtensionsOptionPageGrid(this);
optionPage.LoadSettingsFromStorage();
return optionPage;
}
}
}
public void SetOptionPageInstance()
{
OptionPageInstance = (TWVSExtensionsOptionPageGrid)GetDialogPage(typeof(TWVSExtensionsOptionPageGrid));
OptionPageInstance.SetPackage(this);
}
public void SetOptionPageInstance(TWVSExtensionsOptionPageGrid option)
{
OptionPageInstance = option;
}
public void DisposeOptionPageInstance()
{
if (OptionPageInstance != null)
{
OptionPageInstance.Dispose();
OptionPageInstance = null; // Clear the reference to avoid memory leaks
}
}
public void ShowOptionPageTask()
{
ThreadHelper.ThrowIfNotOnUIThread();
Type optionsPageType = typeof(TWVSExtensionsOptionPageGrid);
OptionPageInstance.package.ShowOptionPage(optionsPageType);
}
public void ResetExtensionSettingFolder()
{
OptionPageInstance.ResetExtensionSettingFolder();
}
public int WaitForPRCompletion
{
get
{
return OptionPage.WaitForPRCompletion;
}
}
public GitPullRequestMergeType DefaultMergeType
{
get
{
return TWVSSCMUtils.DefaultMergeType;
}
}
public string DevelopmentTrunk
{
get
{
return TWVSSCMUtils.DevelopmentTrunk;
}
}
public TWLogLevel LogLevel
{
get
{
return OptionPage.LogLevel;
}
}
public string BootstrapURL
{
get
{
return OptionPage.BootstrapURL;
}
}
public bool UseGitCLI
{
get
{
return File.Exists(Path.Combine(TWUtils.WorkspaceHome, ".cli"));
}
}
public CreatePR CreatePR
{
get
{
return OptionPage.CreatePR;
}
set
{
OptionPage.CreatePR = value;
OptionPage.SaveSettingsToStorage();
}
}
public int SyncAllThreads
{
get
{
return OptionPage.SyncAllThreads;
}
}
public SortingOrder[] RepoExpSorting => OptionPage.RepoExpSorting;
public int CloneWaitTime
{
get
{
return OptionPage.CloneWaitTime;
}
}
public WebOpenType OpeningURLTypeForPR
{
get
{
return OptionPage.OpeningURLTypeForPR;
}
}
public ChangesView DefaultView
{
get
{
return OptionPage.DefaultView;
}
set
{
OptionPage.DefaultView = value;
OptionPage.SaveSettingsToStorage();
}
}
private ConcurrentDictionary listToMonitor = new ConcurrentDictionary();
private void ObjectCreatedExecutor(FileSystemWatcher watcher, ObservableObject obj, FileSystemEventArgs e)
{
ObjectChangedExecutor(watcher, obj, e);
}
private void ObjectDeletedExecutor(FileSystemWatcher watcher, ObservableObject obj, FileSystemEventArgs e)
{
ObjectChangedExecutor(watcher, obj, e);
}
private void ObjectRenamedExecutor(FileSystemWatcher watcher, ObservableObject obj, RenamedEventArgs e)
{
ObjectChangedExecutor(watcher, obj, e);
}
private void ObjectChangedExecutor(FileSystemWatcher watcher, ObservableObject obj, FileSystemEventArgs e)
{
try
{
if (CancellationToken.IsCancellationRequested)
{
return;
}
if (OptionPage.DisableAutoRefresh)
{
return;
}
if (obj == null || obj.Observed == null || obj.Observed.Repository == null || obj.Observed.Repository.LocalDir == null)
{
return;
}
if (LibGit2Sharp.Repository.IsValid(obj.Observed.Repository.LocalDir) == false)
{
return;
}
DateTime dateTime = DateTime.Now;
if (e.FullPath.Contains("\\.vs\\") || ".vs".Equals(e.Name))
{
//ignore all changes under .vs folder, if the file belongs to any folder under ".vs" or .vs folder itself changed
return;
}
if (".git".Equals(e.Name) || e.FullPath.StartsWith(Path.Combine(obj.Observed.Repository.LocalDir, ".git\\")) || e.FullPath.StartsWith(Path.Combine(obj.Observed.Repository.LocalDir, ".git/")))
{
//Ignore all changes inside .git directory, excpet index changes
if (
e.Name.Equals(Path.Combine(".git", "index"))
|| e.Name.Equals(Path.Combine(".git", "FETCH_HEAD"))
|| e.Name.Equals(Path.Combine(".git", "HEAD"))
|| e.Name.StartsWith(Path.Combine(".git", "refs", "heads")) //branch changes
|| e.Name.StartsWith(Path.Combine(".git", "refs", "remotes")) //remotes changes
|| e.Name.StartsWith(Path.Combine(".git", "refs", "tags")) //tags changes
|| e.Name.Equals(Path.Combine(".git", "refs", "stash")) //stash changes
|| e.Name.Equals(Path.Combine(".git", "packed-refs"))
|| e.Name.Equals(Path.Combine(".git", "packed-refs.lock")) //ref change is completed, lock file will be deleted
)
{
//Staging/commit/add changes index file
//checkout changes HEAD, packed-refs and heads file
//continue ...
}
else
{
//Ignore all other changes inside .git folder
return;
}
}
//Refresh sequentially
obj.Observed.QueueJob(async () =>
{
string extra = "";
if(e.ChangeType == WatcherChangeTypes.Renamed)
{
RenamedEventArgs r = ((RenamedEventArgs)e);
extra = " from " + r.OldName;
}
await TWUtils.ShowTextToExtensionPaneAsync(TWLogLevel.Diagnostic, "Observer", $"{watcher.Path}\\{e.Name} {e.ChangeType}{extra}");
if (CancellationToken.IsCancellationRequested || (LibGit2Sharp.Repository.IsValid(obj.Observed.Repository.LocalDir) == false))
{
return;
}
//watcher.EnableRaisingEvents = false;
int newHashcode = 0;
try
{
obj.Observed.Repository.UpdateProperties(false);
newHashcode = obj.Observed.ComputeContentHashCode();
}
finally
{
//watcher.EnableRaisingEvents = true;
}
if (obj.Hashcode != newHashcode)
{
try
{
//Object content changed
obj.Observed.OnChange(DisposalToken);
obj.Hashcode = newHashcode;
if (obj.ParentList != null) obj.ParentList.RefreshList(obj.Observed);
}
catch (Exception ex)
{
await TWUtils.PrintExceptionToOutputPaneAndErrorLogAsync("Change Monitor", ex, false);
}
}
});
}
catch (Exception ex)
{
TWUtils.PrintExceptionToOutputPaneAndErrorLog("Change Monitor", ex, false);
}
}
public void StartWatcher(ObservableObject obj)
{
FileSystemWatcher watcher = new FileSystemWatcher(obj.Observed.Repository.LocalDir);
watcher.InternalBufferSize = 64 * 1024; //64Kb
watcher.NotifyFilter =
// NotifyFilters.Attributes //not interested in attribute
NotifyFilters.CreationTime
| NotifyFilters.DirectoryName
| NotifyFilters.FileName
// | NotifyFilters.LastAccess //not interested in read
| NotifyFilters.LastWrite
// | NotifyFilters.Security //not interested in security attribute changes
// | NotifyFilters.Size; //Size event need not be monitored, last write is enough
;
watcher.Changed += (sender, e) => ObjectChangedExecutor(sender as FileSystemWatcher, obj, e);
watcher.Created += (sender, e) => ObjectCreatedExecutor(sender as FileSystemWatcher, obj, e);
watcher.Deleted += (sender, e) => ObjectDeletedExecutor(sender as FileSystemWatcher, obj, e);
watcher.Renamed += (sender, e) => ObjectRenamedExecutor(sender as FileSystemWatcher, obj, e);
watcher.Error += (sender, e) => TWUtils.PrintExceptionToOutputPaneAndErrorLog(e.GetException(), false);
watcher.Filter = obj.Observed.FilePattern;
watcher.IncludeSubdirectories = true;
watcher.EnableRaisingEvents = true;
obj.Watcher = watcher;
}
public Guid RegisterMonitor(TWObservableList objectsToMonitor)
{
Guid id = Guid.NewGuid();
listToMonitor[id] = objectsToMonitor;
return id;
}
public void PauseMonitors()
{
foreach(TWObservableList objectsToMonitor in listToMonitor.Values)
{
foreach (ObservableObject obj in objectsToMonitor.ObservableItems)
{
if (obj.Watcher != null)
{
obj.Watcher.EnableRaisingEvents = false;
}
}
}
}
public void ResumeMonitors()
{
foreach (TWObservableList objectsToMonitor in listToMonitor.Values)
{
foreach (ObservableObject obj in objectsToMonitor.ObservableItems)
{
if (obj.Watcher != null)
{
obj.Watcher.EnableRaisingEvents = true;
}
}
}
}
public void UnregisterMonitor(Guid id)
{
if (listToMonitor.TryRemove(id, out TWObservableList objectsToMonitor))
{
foreach (ObservableObject obj in objectsToMonitor.ObservableItems)
{
if (obj.Watcher != null)
{
obj.Watcher.EnableRaisingEvents = false;
obj.Watcher.Dispose();
obj.Watcher = null;
}
}
}
}
public override IVsAsyncToolWindowFactory GetAsyncToolWindowFactory(Guid toolWindowType)
{
return this;
}
protected override async Task