From 54ea2d892516a784c55beddebe7153127aa75ba9 Mon Sep 17 00:00:00 2001 From: Alexey Andronov Date: Sun, 7 Jun 2020 12:45:52 +0700 Subject: [PATCH 1/8] Add appveyor script --- appveyor.yml | 15 +++++++++++++++ src/SaveAllTheTabs/SaveAllTheTabs.csproj | 7 +++++++ src/SaveAllTheTabs/source.extension.cs | 18 ++++++++++++++++++ 3 files changed, 40 insertions(+) create mode 100644 appveyor.yml create mode 100644 src/SaveAllTheTabs/source.extension.cs diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..f8b28ff --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,15 @@ +image: Visual Studio 2019 + +install: +- ps: (new-object Net.WebClient).DownloadString("https://raw.github.com/madskristensen/ExtensionScripts/master/AppVeyor/vsix.ps1") | iex + +before_build: + - ps: Vsix-IncrementVsixVersion | Vsix-UpdateBuildVersion + - ps: Vsix-TokenReplacement src\SaveAllTheTabs\source.extension.cs 'Version = "([0-9\\.]+)"' 'Version = "{version}"' + +build_script: + - nuget restore src\SaveAllTheTabs\SaveAllTheTabs.csproj -SolutionDirectory src -Verbosity quiet + - msbuild src\SaveAllTheTabs.sln /p:configuration=Release /p:DeployExtension=false /p:ZipPackageCompressionLevel=normal /v:m + +after_test: + - ps: Vsix-PushArtifacts | Vsix-PublishToGallery diff --git a/src/SaveAllTheTabs/SaveAllTheTabs.csproj b/src/SaveAllTheTabs/SaveAllTheTabs.csproj index 8d90030..e9dfd7a 100644 --- a/src/SaveAllTheTabs/SaveAllTheTabs.csproj +++ b/src/SaveAllTheTabs/SaveAllTheTabs.csproj @@ -96,6 +96,11 @@ + + True + True + source.extension.vsixmanifest + @@ -107,6 +112,8 @@ Designer + VsixManifestGenerator + source.extension.cs diff --git a/src/SaveAllTheTabs/source.extension.cs b/src/SaveAllTheTabs/source.extension.cs new file mode 100644 index 0000000..1a8da9b --- /dev/null +++ b/src/SaveAllTheTabs/source.extension.cs @@ -0,0 +1,18 @@ +// ------------------------------------------------------------------------------ +// +// This file was generated by VSIX Synchronizer +// +// ------------------------------------------------------------------------------ +namespace SaveAllTheTabs +{ + internal sealed partial class Vsix + { + public const string Id = "SaveAllTheTabs.a0217e5b-1dfa-4fa1-98aa-7550d6a32076"; + public const string Name = "Save All the Tabs"; + public const string Description = @"Quickly save and restore sets of document tabs."; + public const string Language = "en-US"; + public const string Version = "1.1.0"; + public const string Author = "Eric Amodio"; + public const string Tags = "tabs,tab management,documents,document management"; + } +} From 52afad40199bd8f92046dcea89b4dedc8c82887b Mon Sep 17 00:00:00 2001 From: Alexey Andronov Date: Sat, 16 May 2020 17:54:55 +0700 Subject: [PATCH 2/8] Migrate from packages.config to PackageReference As per manual: https://docs.microsoft.com/en-us/nuget/consume-packages/migrate-packages-config-to-package-reference --- src/SaveAllTheTabs/SaveAllTheTabs.csproj | 122 ++++------------------- src/SaveAllTheTabs/packages.config | 33 ------ 2 files changed, 20 insertions(+), 135 deletions(-) delete mode 100644 src/SaveAllTheTabs/packages.config diff --git a/src/SaveAllTheTabs/SaveAllTheTabs.csproj b/src/SaveAllTheTabs/SaveAllTheTabs.csproj index e9dfd7a..ad73e3b 100644 --- a/src/SaveAllTheTabs/SaveAllTheTabs.csproj +++ b/src/SaveAllTheTabs/SaveAllTheTabs.csproj @@ -107,9 +107,6 @@ Always true - - Designer - Designer VsixManifestGenerator @@ -117,11 +114,6 @@ - - False - ..\packages\VSSDK.DTE.7.0.4\lib\net20\envdte.dll - True - False @@ -132,101 +124,14 @@ False - - ..\packages\VSSDK.GraphModel.12.0.4\lib\net45\Microsoft.VisualStudio.GraphModel.dll - True - - - ..\packages\VSSDK.OLE.Interop.7.0.4\lib\net20\Microsoft.VisualStudio.OLE.Interop.dll - True - - - ..\packages\VSSDK.Shell.12.12.0.4\lib\net45\Microsoft.VisualStudio.Shell.12.0.dll - True - - - ..\packages\VSSDK.Shell.Immutable.10.10.0.4\lib\net40\Microsoft.VisualStudio.Shell.Immutable.10.0.dll - True - - - ..\packages\VSSDK.Shell.Immutable.11.11.0.4\lib\net45\Microsoft.VisualStudio.Shell.Immutable.11.0.dll - True - - - ..\packages\VSSDK.Shell.Immutable.12.12.0.4\lib\net45\Microsoft.VisualStudio.Shell.Immutable.12.0.dll - True - - - ..\packages\VSSDK.Shell.Interop.7.0.4\lib\net20\Microsoft.VisualStudio.Shell.Interop.dll - True - - - False - ..\packages\VSSDK.Shell.Interop.10.10.0.4\lib\net20\Microsoft.VisualStudio.Shell.Interop.10.0.dll - True - - - False - ..\packages\VSSDK.Shell.Interop.11.11.0.4\lib\net20\Microsoft.VisualStudio.Shell.Interop.11.0.dll - True - - - ..\packages\VSSDK.Shell.Interop.8.8.0.4\lib\net20\Microsoft.VisualStudio.Shell.Interop.8.0.dll - True - - - ..\packages\VSSDK.Shell.Interop.9.9.0.4\lib\net20\Microsoft.VisualStudio.Shell.Interop.9.0.dll - True - - - ..\packages\VSSDK.TextManager.Interop.7.0.4\lib\net20\Microsoft.VisualStudio.TextManager.Interop.dll - True - - - ..\packages\VSSDK.TextManager.Interop.8.8.0.4\lib\net20\Microsoft.VisualStudio.TextManager.Interop.8.0.dll - True - - - ..\packages\VSSDK.Threading.12.0.4\lib\net45\Microsoft.VisualStudio.Threading.dll - True - - - ..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll - True - - - False - ..\packages\VSSDK.DTE.7.0.4\lib\net20\stdole.dll - True - - - ..\packages\Rx-Core.2.2.5\lib\net45\System.Reactive.Core.dll - True - - - ..\packages\Rx-Interfaces.2.2.5\lib\net45\System.Reactive.Interfaces.dll - True - - - ..\packages\Rx-Linq.2.2.5\lib\net45\System.Reactive.Linq.dll - True - - - ..\packages\Rx-PlatformServices.2.2.5\lib\net45\System.Reactive.PlatformServices.dll - True - - - ..\packages\Rx-Xaml.2.2.5\lib\net45\System.Reactive.Windows.Threading.dll - True - @@ -281,15 +186,28 @@ false + + + 14.1.32 + + + 6.0.8 + + + 2.2.5 + + + 12.0.4 + + + 12.0.4 + + + 11.0.4 + + - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - From 6c42dcb9601d5930b553a0116781cc124ebae732 Mon Sep 17 00:00:00 2001 From: Alexey Andronov Date: Sat, 16 May 2020 19:15:19 +0700 Subject: [PATCH 5/8] Fix UI thread warnings --- .../Commands/PackageCommands.cs | 4 +++ .../Commands/RestoreTabsListCommands.cs | 1 + .../Commands/SavedTabsWindowCommands.cs | 2 ++ src/SaveAllTheTabs/DocumentManager.cs | 26 ++++++++++++++++++- src/SaveAllTheTabs/Polyfills/Extensions.cs | 14 +++++++++- src/SaveAllTheTabs/Polyfills/Interop.cs | 3 +++ src/SaveAllTheTabs/SaveAllTheTabsPackage.cs | 24 +++++++++++++++-- src/SaveAllTheTabs/SavedTabsToolWindow.cs | 1 + .../SavedTabsToolWindowControl.xaml.cs | 22 +++++++++------- 9 files changed, 83 insertions(+), 14 deletions(-) diff --git a/src/SaveAllTheTabs/Commands/PackageCommands.cs b/src/SaveAllTheTabs/Commands/PackageCommands.cs index 047f72f..d286440 100644 --- a/src/SaveAllTheTabs/Commands/PackageCommands.cs +++ b/src/SaveAllTheTabs/Commands/PackageCommands.cs @@ -58,6 +58,7 @@ public static void Initialize(SaveAllTheTabsPackage package) /// Owner package, not null. private PackageCommands(SaveAllTheTabsPackage package) { + ThreadHelper.ThrowIfNotOnUIThread(); if (package == null) { throw new ArgumentNullException(nameof(package)); @@ -74,6 +75,7 @@ private PackageCommands(SaveAllTheTabsPackage package) private void SetupCommands(OleMenuCommandService commandService) { + ThreadHelper.ThrowIfNotOnUIThread(); var guid = typeof(CommandIds).GUID; var commandId = new CommandID(guid, (int)CommandIds.SaveTabs); @@ -114,6 +116,7 @@ private void SetupCommands(OleMenuCommandService commandService) private void CommandOnBeforeQueryStatus(object sender, EventArgs eventArgs) { + ThreadHelper.ThrowIfNotOnUIThread(); var command = sender as OleMenuCommand; if (command == null) { @@ -138,6 +141,7 @@ private void ExecuteSaveTabsCommand(object sender, EventArgs e) private void ExecuteSavedTabsWindowCommand(object sender, EventArgs e) { + ThreadHelper.ThrowIfNotOnUIThread(); // Get the instance number 0 of this tool window. This window is single instance so this instance // is actually the only one. // The last flag is set to true so that if the tool window does not exists it will be created. diff --git a/src/SaveAllTheTabs/Commands/RestoreTabsListCommands.cs b/src/SaveAllTheTabs/Commands/RestoreTabsListCommands.cs index 73db40b..52dc225 100644 --- a/src/SaveAllTheTabs/Commands/RestoreTabsListCommands.cs +++ b/src/SaveAllTheTabs/Commands/RestoreTabsListCommands.cs @@ -25,6 +25,7 @@ public RestoreTabsListCommands(SaveAllTheTabsPackage package) public void SetupCommands(OleMenuCommandService commandService) { + ThreadHelper.ThrowIfNotOnUIThread(); if (Package.DocumentManager == null) { return; diff --git a/src/SaveAllTheTabs/Commands/SavedTabsWindowCommands.cs b/src/SaveAllTheTabs/Commands/SavedTabsWindowCommands.cs index c7f4cbb..44c6a35 100644 --- a/src/SaveAllTheTabs/Commands/SavedTabsWindowCommands.cs +++ b/src/SaveAllTheTabs/Commands/SavedTabsWindowCommands.cs @@ -81,6 +81,7 @@ private void SetupCommands(OleMenuCommandService commandService) private void SaveToCommandOnBeforeQueryStatus(object sender, EventArgs eventArgs) { + ThreadHelper.ThrowIfNotOnUIThread(); var command = sender as OleMenuCommand; if (command == null) { @@ -93,6 +94,7 @@ private void SaveToCommandOnBeforeQueryStatus(object sender, EventArgs eventArgs private void CloseCommandOnBeforeQueryStatus(object sender, EventArgs eventArgs) { + ThreadHelper.ThrowIfNotOnUIThread(); var command = sender as OleMenuCommand; if (command == null) { diff --git a/src/SaveAllTheTabs/DocumentManager.cs b/src/SaveAllTheTabs/DocumentManager.cs index dc8ef6b..acf0af1 100644 --- a/src/SaveAllTheTabs/DocumentManager.cs +++ b/src/SaveAllTheTabs/DocumentManager.cs @@ -8,8 +8,10 @@ using System.Linq; using System.Reactive.Disposables; using System.Reactive.Linq; +using Microsoft; using Microsoft.VisualStudio; using Microsoft.VisualStudio.Settings; +using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.Shell.Settings; using Newtonsoft.Json; @@ -71,24 +73,34 @@ internal class DocumentManager : IDocumentManager private SaveAllTheTabsPackage Package { get; } private IServiceProvider ServiceProvider => Package; private IVsUIShellDocumentWindowMgr DocumentWindowMgr { get; } - private string SolutionName => Package.Environment.Solution?.FullName; + private string SolutionName + { + get + { + ThreadHelper.ThrowIfNotOnUIThread(); + return Package.Environment.Solution?.FullName; + } + } public ObservableCollection Groups { get; private set; } public DocumentManager(SaveAllTheTabsPackage package) { + ThreadHelper.ThrowIfNotOnUIThread(); Package = package; package.SolutionChanged += (sender, args) => LoadGroups(); LoadGroups(); DocumentWindowMgr = ServiceProvider.GetService(typeof(IVsUIShellDocumentWindowMgr)) as IVsUIShellDocumentWindowMgr; + Assumes.Present(DocumentWindowMgr); } private IDisposable _changeSubscription; private void LoadGroups() { + ThreadHelper.ThrowIfNotOnUIThread(); _changeSubscription?.Dispose(); // Load presets for the current solution @@ -121,6 +133,7 @@ private void LoadGroups() public void SaveGroup(string name, int? slot = null) { + ThreadHelper.ThrowIfNotOnUIThread(); if (DocumentWindowMgr == null) { Debug.Assert(false, "IVsUIShellDocumentWindowMgr", String.Empty, 0); @@ -212,11 +225,13 @@ private void TrySetSlot(DocumentGroup group, int? slot) public void RestoreGroup(int slot) { + ThreadHelper.ThrowIfNotOnUIThread(); RestoreGroup(Groups.FindBySlot(slot)); } public void RestoreGroup(DocumentGroup group) { + ThreadHelper.ThrowIfNotOnUIThread(); if (group == null) { return; @@ -235,6 +250,7 @@ public void RestoreGroup(DocumentGroup group) public void OpenGroup(int slot) { + ThreadHelper.ThrowIfNotOnUIThread(); OpenGroup(Groups.FindBySlot(slot)); } @@ -247,6 +263,7 @@ public void OpenGroup(DocumentGroup group) using (var stream = new VsOleStream()) { + ThreadHelper.ThrowIfNotOnUIThread(); stream.Write(group.Positions, 0, group.Positions.Length); stream.Seek(0, SeekOrigin.Begin); @@ -265,6 +282,7 @@ public void CloseGroup(DocumentGroup group) return; } + ThreadHelper.ThrowIfNotOnUIThread(); var documents = from d in Package.Environment.GetDocuments() where @group.Files.Contains(d.FullName) select d; @@ -333,6 +351,7 @@ public void RemoveGroup(DocumentGroup group, bool confirm = true) private void SaveUndoGroup() { + ThreadHelper.ThrowIfNotOnUIThread(); SaveGroup(UndoGroupName); } @@ -362,16 +381,19 @@ private void SaveUndoGroup(DocumentGroup group) public void SaveStashGroup() { + ThreadHelper.ThrowIfNotOnUIThread(); SaveGroup(StashGroupName); } public void OpenStashGroup() { + ThreadHelper.ThrowIfNotOnUIThread(); OpenGroup(Groups.FindByName(StashGroupName)); } public void RestoreStashGroup() { + ThreadHelper.ThrowIfNotOnUIThread(); RestoreGroup(Groups.FindByName(StashGroupName)); } @@ -401,6 +423,7 @@ public void RestoreStashGroup() private List LoadGroupsForSolution() { + ThreadHelper.ThrowIfNotOnUIThread(); var solution = SolutionName; if (!string.IsNullOrWhiteSpace(solution)) { @@ -426,6 +449,7 @@ private List LoadGroupsForSolution() private void SaveGroupsForSolution(IList groups = null) { + ThreadHelper.ThrowIfNotOnUIThread(); var solution = SolutionName; if (string.IsNullOrWhiteSpace(solution)) { diff --git a/src/SaveAllTheTabs/Polyfills/Extensions.cs b/src/SaveAllTheTabs/Polyfills/Extensions.cs index a771543..1980e33 100644 --- a/src/SaveAllTheTabs/Polyfills/Extensions.cs +++ b/src/SaveAllTheTabs/Polyfills/Extensions.cs @@ -33,11 +33,13 @@ public static Document GetActiveDocument(this DTE2 environment) public static IEnumerable GetDocumentFiles(this DTE2 environment) { + ThreadHelper.ThrowIfNotOnUIThread(); return from d in environment.GetDocuments() select GetExactPathName(d.FullName); } public static IEnumerable GetDocuments(this DTE2 environment) { + ThreadHelper.ThrowIfNotOnUIThread(); return from w in environment.GetDocumentWindows() where w.Document != null select w.Document; } @@ -49,6 +51,7 @@ public static IEnumerable GetDocumentWindows(this DTE2 environment) { try { + ThreadHelper.ThrowIfNotOnUIThread(); return !w.Linkable; } catch (ObjectDisposedException) @@ -60,16 +63,22 @@ public static IEnumerable GetDocumentWindows(this DTE2 environment) public static IEnumerable GetBreakpoints(this DTE2 environment) { + ThreadHelper.ThrowIfNotOnUIThread(); return environment.Debugger.Breakpoints.Cast(); } public static IEnumerable GetMatchingBreakpoints(this DTE2 environment, HashSet files) { - return environment.Debugger.Breakpoints.Cast().Where(bp => files.Contains(bp.File)); + ThreadHelper.ThrowIfNotOnUIThread(); + return environment.Debugger.Breakpoints.Cast().Where(bp => { + ThreadHelper.ThrowIfNotOnUIThread(); + return files.Contains(bp.File); + }); } public static void CloseAll(this IEnumerable windows, vsSaveChanges saveChanges = vsSaveChanges.vsSaveChangesPrompt) { + ThreadHelper.ThrowIfNotOnUIThread(); foreach (var w in windows) { w.Close(saveChanges); @@ -78,6 +87,7 @@ public static void CloseAll(this IEnumerable windows, vsSaveChanges save public static void CloseAll(this IEnumerable documents, vsSaveChanges saveChanges = vsSaveChanges.vsSaveChangesPrompt) { + ThreadHelper.ThrowIfNotOnUIThread(); foreach (var d in documents) { d.Close(saveChanges); @@ -86,6 +96,7 @@ public static void CloseAll(this IEnumerable documents, vsSaveChanges public static Command GetCommand(this DTE2 environment, OleMenuCommand command) { + ThreadHelper.ThrowIfNotOnUIThread(); return environment.Commands.Item(command.CommandID.Guid, command.CommandID.ID); } @@ -96,6 +107,7 @@ public static object[] GetKeyBindings(this DTE2 environment, OleMenuCommand comm public static void SetKeyBindings(this DTE2 environment, OleMenuCommand command, params object[] bindings) { + ThreadHelper.ThrowIfNotOnUIThread(); var dteCommand = environment.GetCommand(command); if (dteCommand == null) { diff --git a/src/SaveAllTheTabs/Polyfills/Interop.cs b/src/SaveAllTheTabs/Polyfills/Interop.cs index 1b8442c..2cb4a30 100644 --- a/src/SaveAllTheTabs/Polyfills/Interop.cs +++ b/src/SaveAllTheTabs/Polyfills/Interop.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Runtime.InteropServices; using Microsoft.VisualStudio.OLE.Interop; +using Microsoft.VisualStudio.Shell; namespace SaveAllTheTabs.Polyfills { @@ -75,11 +76,13 @@ void IStream.Clone(out IStream ppstm) void ISequentialStream.Read(byte[] pv, uint cb, out uint pcbRead) { + ThreadHelper.ThrowIfNotOnUIThread(); ((IStream)this).Read(pv, cb, out pcbRead); } void ISequentialStream.Write(byte[] pv, uint cb, out uint pcbWritten) { + ThreadHelper.ThrowIfNotOnUIThread(); ((IStream)this).Write(pv, cb, out pcbWritten); } } diff --git a/src/SaveAllTheTabs/SaveAllTheTabsPackage.cs b/src/SaveAllTheTabs/SaveAllTheTabsPackage.cs index fc5c644..0f92b18 100644 --- a/src/SaveAllTheTabs/SaveAllTheTabsPackage.cs +++ b/src/SaveAllTheTabs/SaveAllTheTabsPackage.cs @@ -41,10 +41,26 @@ public sealed class SaveAllTheTabsPackage : Package { public event EventHandler SolutionChanged; - internal static DTE2 Dte => _dte ?? (_dte = ServiceProvider.GlobalProvider.GetService(typeof(DTE)) as DTE2); + internal static DTE2 Dte + { + get + { + ThreadHelper.ThrowIfNotOnUIThread(); + return _dte ?? (_dte = ServiceProvider.GlobalProvider.GetService(typeof(DTE)) as DTE2); + } + } + private static DTE2 _dte; - public DTE2 Environment => Dte; + public DTE2 Environment + { + get + { + ThreadHelper.ThrowIfNotOnUIThread(); + return Dte; + } + } + internal IDocumentManager DocumentManager { get; private set; } private PackageProviderService _packageProvider; @@ -88,22 +104,26 @@ await Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() => private void OnSolutionOpened() { SolutionChanged?.Invoke(this, EventArgs.Empty); + ThreadHelper.ThrowIfNotOnUIThread(); UpdateCommandsUI(this); } private void OnSolutionClosed() { SolutionChanged?.Invoke(this, EventArgs.Empty); + ThreadHelper.ThrowIfNotOnUIThread(); UpdateCommandsUI(this); } public void UpdateCommandsUI() { + ThreadHelper.ThrowIfNotOnUIThread(); UpdateCommandsUI(this); } public static void UpdateCommandsUI(IServiceProvider sp, bool immediate = false) { + ThreadHelper.ThrowIfNotOnUIThread(); var shell = (IVsUIShell)sp.GetService(typeof(IVsUIShell)); if (shell == null) { diff --git a/src/SaveAllTheTabs/SavedTabsToolWindow.cs b/src/SaveAllTheTabs/SavedTabsToolWindow.cs index fe6015b..0ba8cc1 100644 --- a/src/SaveAllTheTabs/SavedTabsToolWindow.cs +++ b/src/SaveAllTheTabs/SavedTabsToolWindow.cs @@ -27,6 +27,7 @@ public class SavedTabsToolWindow : ToolWindowPane /// public SavedTabsToolWindow() : base(null) { + ThreadHelper.ThrowIfNotOnUIThread(); Caption = "Saved Tabs"; // Set the image that will appear on the tab of the window frame diff --git a/src/SaveAllTheTabs/SavedTabsToolWindowControl.xaml.cs b/src/SaveAllTheTabs/SavedTabsToolWindowControl.xaml.cs index cbee471..891cf1c 100644 --- a/src/SaveAllTheTabs/SavedTabsToolWindowControl.xaml.cs +++ b/src/SaveAllTheTabs/SavedTabsToolWindowControl.xaml.cs @@ -13,6 +13,7 @@ using SaveAllTheTabs.Commands; using SaveAllTheTabs.Polyfills; using Task = System.Threading.Tasks.Task; +using Microsoft.VisualStudio.Shell; namespace SaveAllTheTabs { @@ -128,11 +129,11 @@ private void OnTabsListItemPreviewKeyDown(object sender, KeyEventArgs e) // Reset focus to current group, otherwise selection get reset to the first item var delay = Task.Run(async () => await Task.Delay(TimeSpan.FromMilliseconds(1))); - delay.ContinueWith(t => - { - list.SelectedItem = group; - list.GetListViewItem(group)?.Focus(); - }, TaskScheduler.FromCurrentSynchronizationContext()); + _ = delay.ContinueWith(t => + { + list.SelectedItem = group; + list.GetListViewItem(group)?.Focus(); + }, TaskScheduler.FromCurrentSynchronizationContext()); break; } case Key.Down: @@ -160,11 +161,11 @@ private void OnTabsListItemPreviewKeyDown(object sender, KeyEventArgs e) // Reset focus to current group, otherwise selection get reset to the first item var delay = Task.Run(async () => await Task.Delay(TimeSpan.FromMilliseconds(1))); - delay.ContinueWith(t => - { - list.SelectedItem = group; - list.GetListViewItem(group)?.Focus(); - }, TaskScheduler.FromCurrentSynchronizationContext()); + _ = delay.ContinueWith(t => + { + list.SelectedItem = group; + list.GetListViewItem(group)?.Focus(); + }, TaskScheduler.FromCurrentSynchronizationContext()); break; } default: @@ -183,6 +184,7 @@ private void OnTabsListItemPreviewKeyDown(object sender, KeyEventArgs e) private void OnTabsListSelectionChanged(object sender, SelectionChangedEventArgs e) { + ThreadHelper.ThrowIfNotOnUIThread(); var list = (sender as ListView); if (list == null) { From 7435d5ae8577a9f998338fc24a771815c60c38e9 Mon Sep 17 00:00:00 2001 From: Alexey Andronov Date: Sat, 16 May 2020 18:50:52 +0700 Subject: [PATCH 6/8] Implement extension async load --- src/SaveAllTheTabs/SaveAllTheTabsPackage.cs | 26 ++++++++++----------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/SaveAllTheTabs/SaveAllTheTabsPackage.cs b/src/SaveAllTheTabs/SaveAllTheTabsPackage.cs index 0f92b18..55cbe96 100644 --- a/src/SaveAllTheTabs/SaveAllTheTabsPackage.cs +++ b/src/SaveAllTheTabs/SaveAllTheTabsPackage.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; +using System.Threading; using System.Windows.Threading; using EnvDTE; using EnvDTE80; @@ -29,15 +30,15 @@ namespace SaveAllTheTabs /// To get loaded into VS, the package must be referred by <Asset Type="Microsoft.VisualStudio.VsPackage" ...> in .vsixmanifest file. /// /// - [PackageRegistration(UseManagedResourcesOnly = true)] + [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)] [InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)] // Info on this package for Help/About - [ProvideAutoLoad(UIContextGuids80.SolutionExists)] + [ProvideAutoLoad(UIContextGuids80.SolutionExists, PackageAutoLoadFlags.BackgroundLoad)] [ProvideMenuResource("Menus.ctmenu", 1)] [Guid(PackageGuids.PackageGuidString)] [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "pkgdef, VS and vsixmanifest are valid VS terms")] [ProvideToolWindow(typeof(SavedTabsToolWindow), Style = VsDockStyle.Tabbed, Window = PackageGuids.SolutionExploreWindowGuidString)] [ProvideService(typeof(PackageProviderService))] - public sealed class SaveAllTheTabsPackage : Package + public sealed class SaveAllTheTabsPackage : AsyncPackage { public event EventHandler SolutionChanged; @@ -81,24 +82,23 @@ public SaveAllTheTabsPackage() /// 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. /// - protected async override void Initialize() + protected override async System.Threading.Tasks.Task InitializeAsync(CancellationToken cancellationToken, IProgress progress) { + // Switches to the UI thread in order to consume some services used in command initialization + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + _packageProvider = new PackageProviderService(this); DocumentManager = new DocumentManager(this); PackageCommands.Initialize(this); - base.Initialize(); + await base.InitializeAsync(cancellationToken, progress); // Hook up event handlers - await Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() => - { - // Must save the solution events, otherwise it seems to get GC'd - _solutionEvents = Environment.Events.SolutionEvents; - _solutionEvents.Opened += OnSolutionOpened; - _solutionEvents.AfterClosing += OnSolutionClosed; - - }), DispatcherPriority.ApplicationIdle, null); + // Must save the solution events, otherwise it seems to get GC'd + _solutionEvents = Environment.Events.SolutionEvents; + _solutionEvents.Opened += OnSolutionOpened; + _solutionEvents.AfterClosing += OnSolutionClosed; } private void OnSolutionOpened() From 36b3380202339158ece6ac2dc7ca0383eebe1b64 Mon Sep 17 00:00:00 2001 From: Alexey Andronov Date: Sun, 14 Jun 2020 15:25:57 +0700 Subject: [PATCH 7/8] Fix exceptions on project open/close --- src/SaveAllTheTabs/DocumentManager.cs | 3 ++- src/SaveAllTheTabs/Polyfills/Extensions.cs | 9 ++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/SaveAllTheTabs/DocumentManager.cs b/src/SaveAllTheTabs/DocumentManager.cs index acf0af1..d7da945 100644 --- a/src/SaveAllTheTabs/DocumentManager.cs +++ b/src/SaveAllTheTabs/DocumentManager.cs @@ -243,7 +243,8 @@ public void RestoreGroup(DocumentGroup group) SaveUndoGroup(); } - Package.Environment.Documents.CloseAll(); + if (windows.Any()) + windows.CloseAll(); OpenGroup(group); } diff --git a/src/SaveAllTheTabs/Polyfills/Extensions.cs b/src/SaveAllTheTabs/Polyfills/Extensions.cs index 1980e33..47049d1 100644 --- a/src/SaveAllTheTabs/Polyfills/Extensions.cs +++ b/src/SaveAllTheTabs/Polyfills/Extensions.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Windows.Controls; @@ -114,7 +115,13 @@ public static void SetKeyBindings(this DTE2 environment, OleMenuCommand command, return; } - dteCommand.Bindings = bindings; + try + { + dteCommand.Bindings = bindings; + } catch (System.Runtime.InteropServices.COMException ex) + { + Debug.Assert(false, nameof(SetKeyBindings), ex.ToString()); + } } public static void SetKeyBindings(this DTE2 environment, OleMenuCommand command, IEnumerable bindings) From 2549cdb7811c06d8ea19f3cad414f45cad28cd7d Mon Sep 17 00:00:00 2001 From: Alexey Andronov Date: Sun, 14 Jun 2020 16:11:35 +0700 Subject: [PATCH 8/8] Store each group individually in registry Resolves eamodio/SaveAllTheTabs#27 --- src/SaveAllTheTabs/DocumentManager.cs | 74 ++++++++++++++++++++------- 1 file changed, 55 insertions(+), 19 deletions(-) diff --git a/src/SaveAllTheTabs/DocumentManager.cs b/src/SaveAllTheTabs/DocumentManager.cs index d7da945..3ca3740 100644 --- a/src/SaveAllTheTabs/DocumentManager.cs +++ b/src/SaveAllTheTabs/DocumentManager.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; @@ -8,6 +9,8 @@ using System.Linq; using System.Reactive.Disposables; using System.Reactive.Linq; +using System.Text; +using System.Windows.Forms; using Microsoft; using Microsoft.VisualStudio; using Microsoft.VisualStudio.Settings; @@ -69,6 +72,7 @@ internal class DocumentManager : IDocumentManager private const string StorageCollectionPath = "SaveAllTheTabs"; private const string SavedTabsStoragePropertyFormat = "SavedTabs.{0}"; + private const string ProjectGroupsKeyPlaceholder = StorageCollectionPath + "\\{0}\\groups"; private SaveAllTheTabsPackage Package { get; } private IServiceProvider ServiceProvider => Package; @@ -426,27 +430,43 @@ private List LoadGroupsForSolution() { ThreadHelper.ThrowIfNotOnUIThread(); var solution = SolutionName; - if (!string.IsNullOrWhiteSpace(solution)) - { - try - { - var settingsMgr = new ShellSettingsManager(ServiceProvider); - var store = settingsMgr.GetReadOnlySettingsStore(SettingsScope.UserSettings); + List groups = new List(); + if (string.IsNullOrWhiteSpace(solution)) { + return groups; + } + + try { + var settingsMgr = new ShellSettingsManager(ServiceProvider); + var store = settingsMgr.GetReadOnlySettingsStore(SettingsScope.UserSettings); + + string projectKeyHash = GetHashString(solution); + string projectGroupsKey = string.Format(ProjectGroupsKeyPlaceholder, projectKeyHash); + if (!store.CollectionExists(projectGroupsKey)) + { + // try load tabs from older versions var propertyName = String.Format(SavedTabsStoragePropertyFormat, solution); if (store.PropertyExists(StorageCollectionPath, propertyName)) { var tabs = store.GetString(StorageCollectionPath, propertyName); - return JsonConvert.DeserializeObject>(tabs); + groups = JsonConvert.DeserializeObject>(tabs); } + + return groups; } - catch (Exception ex) + + var groupProperties = store.GetPropertyNamesAndValues(projectGroupsKey); + foreach (var groupProperty in groupProperties) { - Debug.Assert(false, nameof(LoadGroupsForSolution), ex.ToString()); + DocumentGroup group = JsonConvert.DeserializeObject(groupProperty.Value.ToString()); + groups.Add(group); } + } catch (Exception ex) { + Debug.Assert(false, nameof(LoadGroupsForSolution), ex.ToString()); + } + + return groups; } - return new List(); - } private void SaveGroupsForSolution(IList groups = null) { @@ -465,20 +485,36 @@ private void SaveGroupsForSolution(IList groups = null) var settingsMgr = new ShellSettingsManager(ServiceProvider); var store = settingsMgr.GetWritableSettingsStore(SettingsScope.UserSettings); - if (!store.CollectionExists(StorageCollectionPath)) + string projectKeyHash = GetHashString(solution); + string projectGroupsKey = string.Format(ProjectGroupsKeyPlaceholder, projectKeyHash); + + if (store.CollectionExists(projectGroupsKey)) { - store.CreateCollection(StorageCollectionPath); + store.DeleteProperty(StorageCollectionPath, projectGroupsKey); } + store.CreateCollection(projectGroupsKey); - var propertyName = String.Format(SavedTabsStoragePropertyFormat, solution); - if (!groups.Any()) + foreach (DocumentGroup group in groups) { - store.DeleteProperty(StorageCollectionPath, propertyName); - return; + var serializedGroup = JsonConvert.SerializeObject(group); + store.SetString(projectGroupsKey, group.Name, serializedGroup); } + } - var tabs = JsonConvert.SerializeObject(groups); - store.SetString(StorageCollectionPath, propertyName, tabs); + private static string GetHashString(string source) + { + using (System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create()) + { + byte[] inputBytes = System.Text.Encoding.ASCII.GetBytes(source); + byte[] hashBytes = md5.ComputeHash(inputBytes); + // Convert the byte array to hexadecimal string + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < hashBytes.Length; i++) + { + sb.Append(hashBytes [i].ToString("X2")); + } + return sb.ToString(); + } } public static bool IsStashGroup(string name)