├── VSDocumentReopen ├── VSDocumentReopen │ ├── Key.snk │ ├── Resources │ │ ├── icon.png │ │ ├── preview.png │ │ ├── FileOK_16x.png │ │ ├── VsToolsMenu.png │ │ ├── FileError_16x.png │ │ ├── OpenFile_16x.png │ │ ├── VsToolsWindow.png │ │ ├── RemoveGuide_16x.png │ │ ├── VsToolsMenu_Full.png │ │ ├── OpenFile_16x_Gray.png │ │ ├── RemoveGuide_16x_Gray.png │ │ ├── RemoveNonExisting_16x.png │ │ ├── ClearWindowContent_16x.png │ │ ├── RemoveNonExisting_16x_Gray.png │ │ └── ClearWindowContent_16x_Gray.png │ ├── Infrastructure │ │ ├── HistoryCommands │ │ │ ├── IHistoryCommand.cs │ │ │ ├── IHistoryCommandFactory.cs │ │ │ ├── ClearHistoryCommand.cs │ │ │ ├── RemoveLastCommand.cs │ │ │ ├── RemoveSomeCommand.cs │ │ │ └── HistoryCommandFactory.cs │ │ ├── Version │ │ │ ├── IVsVersionProvider.cs │ │ │ ├── VsVersionContext.cs │ │ │ ├── VsDteVersionProvider.cs │ │ │ └── VsDteVersionContext.cs │ │ ├── Document │ │ │ ├── Commands │ │ │ │ ├── IDocumentCommand.cs │ │ │ │ ├── DoNothingCommand.cs │ │ │ │ └── ReopenDocumentCommand.cs │ │ │ ├── Tracking │ │ │ │ ├── SolutionStates.cs │ │ │ │ ├── IDocumentHistoryManager.cs │ │ │ │ ├── IDocumentHistoryQueries.cs │ │ │ │ ├── IDocumentHistoryCommands.cs │ │ │ │ ├── DocumentHistoryManager.cs │ │ │ │ └── DocumentEventsTracker.cs │ │ │ └── Factories │ │ │ │ ├── IDocumentCommandFactory.cs │ │ │ │ ├── DoNothingDocumentCommandFactory.cs │ │ │ │ └── ReopenDocumentCommandFactory.cs │ │ ├── Helpers │ │ │ ├── IJsonSerializer.cs │ │ │ ├── ServiceStackJsonSerializer.cs │ │ │ ├── ImageConverterHelper.cs │ │ │ └── PathFormatter.cs │ │ ├── Logging │ │ │ ├── DefaultLoggerContext.cs │ │ │ ├── Logentries │ │ │ │ ├── LogentriesSerilogLoggerContext.cs │ │ │ │ ├── VisualStudioVersionEnricher.cs │ │ │ │ ├── ExtensionVersionEnricher.cs │ │ │ │ ├── LogentriesSerilogLogger.cs │ │ │ │ └── MemoryInfoEnricher.cs │ │ │ ├── LoggerContext.cs │ │ │ ├── ILogger.cs │ │ │ └── NullLogger.cs │ │ ├── IHistoryToolWindowRepositoryFactory.cs │ │ ├── FileIcons │ │ │ ├── IFileExtensionIconResolver.cs │ │ │ ├── WindowsFileExtensionIconResolver.cs │ │ │ ├── VisualStudioFileExtensionIconResolver.cs │ │ │ └── CachedFileExtensionIconResolver.cs │ │ ├── IHistoryRepositoryFactory.cs │ │ ├── Repositories │ │ │ ├── IHistoryToolWindowRepository.cs │ │ │ ├── IHistoryRepository.cs │ │ │ ├── JsonHistoryToolWindowRepository.cs │ │ │ ├── JsonHistoryRepository.cs │ │ │ └── JsonHistoryRepositoryBase.cs │ │ ├── ConfigurationManager.cs │ │ ├── JsonIHistoryToolWindowRepositoryFactory.cs │ │ ├── DefaultConfigurationManager.cs │ │ └── JsonHistoryRepositoryFactory.cs │ ├── VS │ │ ├── MessageBox │ │ │ ├── IMessageBox.cs │ │ │ └── VSMessageBox.cs │ │ ├── ToolWindows │ │ │ ├── GridViewExtensions.cs │ │ │ ├── IconHandling │ │ │ │ ├── ButtonStates │ │ │ │ │ ├── ImageButtonState.cs │ │ │ │ │ ├── ButtonStateExtensions.cs │ │ │ │ │ ├── ButtonEnabledState.cs │ │ │ │ │ └── ButtonDisabledState.cs │ │ │ │ └── WpfImageSourceConverter.cs │ │ │ ├── ClosedDocumentsHistoryControl_Types.cs │ │ │ ├── SortAdorner.cs │ │ │ └── ClosedDocumentsHistory.cs │ │ └── Commands │ │ │ ├── RemoveClosedDocumentsCommand.cs │ │ │ ├── ReopenClosedDocumentsCommand.cs │ │ │ ├── ClearDocumentsHistoryCommand.cs │ │ │ ├── ShowDocumentsHIstoryCommand.cs │ │ │ └── DocumentsHistoryCommand.cs │ ├── Domain │ │ ├── HistoryControl │ │ │ ├── ColumnInfo.cs │ │ │ └── HistoryControlData.cs │ │ ├── Documents │ │ │ ├── IClosedDocument.cs │ │ │ ├── ClosedDocumentComparer.cs │ │ │ ├── ClosedDocument.cs │ │ │ └── NullDocument.cs │ │ ├── SolutionInfo.cs │ │ ├── IConfiguration.cs │ │ └── Configuration.cs │ ├── appConfig.json │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── source.extension.vsixmanifest │ ├── packages.config │ ├── VSDocumentReopen.ruleset │ ├── ReopenPackage.vsct │ ├── Resources.Designer.cs │ └── VSPackage.resx ├── 3rd Party │ ├── SearchTextBox.dll │ └── SearchTextBox.rar ├── VSDocumentReopen.Test │ ├── Infrastructure │ │ ├── Logging │ │ │ ├── DefaultLoggerContextTest.cs │ │ │ ├── LoggerContextTest.cs │ │ │ └── NullLoggerTest.cs │ │ ├── Document │ │ │ ├── Commands │ │ │ │ ├── DoNothingCommandTest.cs │ │ │ │ └── ReopenDocumentCommandTest.cs │ │ │ ├── Factories │ │ │ │ ├── ReopenDocumentCommandFactoryTest.cs │ │ │ │ └── DoNothingDocumentCommandFactoryTest.cs │ │ │ └── Tracking │ │ │ │ └── DocumentHistoryManagerTest.cs │ │ ├── Version │ │ │ ├── VsDteVersionContextTest.cs │ │ │ ├── VsVersionContextTest.cs │ │ │ └── VsDteVersionProviderTest.cs │ │ ├── FileIcons │ │ │ ├── WindowsFileExtensionIconResolverTest.cs │ │ │ ├── CachedFileExtensionIconResolverTest.cs │ │ │ └── VisualStudioFileExtensionIconResolverTest.cs │ │ ├── HistoryCommands │ │ │ ├── ClearHistoryCommandTest.cs │ │ │ ├── RemoveLastCommandTest.cs │ │ │ ├── RemoveSomeCommandTest.cs │ │ │ └── HistoryCommandFactoryTest.cs │ │ ├── JsonIHistoryToolWindowRepositoryFactoryTest.cs │ │ ├── JsonHistoryRepositoryFactoryTest.cs │ │ └── Helpers │ │ │ ├── ImageConverterHelperTest.cs │ │ │ ├── PathFormatterTest.cs │ │ │ └── ServiceStackJsonSerializerTest.cs │ ├── VS │ │ ├── Commands │ │ │ ├── VisualStudioCommandTestBase.cs │ │ │ ├── ShowDocumentsHIstoryCommandTest.cs │ │ │ ├── ClearDocumentsHistoryCommandTest.cs │ │ │ ├── RemoveClosedDocumentsCommandTest.cs │ │ │ ├── ReopenClosedDocumentsCommandTest.cs │ │ │ └── DocumentsHistoryCommandTest.cs │ │ └── ToolWindows │ │ │ ├── ClosedDocumentsHistoryTest.cs │ │ │ └── IconHandling │ │ │ ├── WpfImageSourceConverterTest.cs │ │ │ └── ButtonStates │ │ │ ├── ButtonEnabledStateTest.cs │ │ │ ├── ButtonDisabledStateTest.cs │ │ │ └── ImageButtonStateTest.cs │ ├── Domain │ │ ├── SolutionInfoTest.cs │ │ └── Documents │ │ │ ├── NullDocumentTest.cs │ │ │ ├── ClosedDocumentTest.cs │ │ │ └── ClosedDocumentComparerTest.cs │ ├── AssemblyFictures │ │ └── ConfigContext.cs │ ├── app.config │ ├── Properties │ │ └── AssemblyInfo.cs │ └── packages.config └── VSDocumentReopen.sln ├── azure-pipelines.yml ├── .gitattributes ├── README.md ├── ReleaseNotes.md └── .gitignore /VSDocumentReopen/VSDocumentReopen/Key.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/majorimi/vs-reopen/HEAD/VSDocumentReopen/VSDocumentReopen/Key.snk -------------------------------------------------------------------------------- /VSDocumentReopen/3rd Party/SearchTextBox.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/majorimi/vs-reopen/HEAD/VSDocumentReopen/3rd Party/SearchTextBox.dll -------------------------------------------------------------------------------- /VSDocumentReopen/3rd Party/SearchTextBox.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/majorimi/vs-reopen/HEAD/VSDocumentReopen/3rd Party/SearchTextBox.rar -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/majorimi/vs-reopen/HEAD/VSDocumentReopen/VSDocumentReopen/Resources/icon.png -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Resources/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/majorimi/vs-reopen/HEAD/VSDocumentReopen/VSDocumentReopen/Resources/preview.png -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Resources/FileOK_16x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/majorimi/vs-reopen/HEAD/VSDocumentReopen/VSDocumentReopen/Resources/FileOK_16x.png -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Resources/VsToolsMenu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/majorimi/vs-reopen/HEAD/VSDocumentReopen/VSDocumentReopen/Resources/VsToolsMenu.png -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Resources/FileError_16x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/majorimi/vs-reopen/HEAD/VSDocumentReopen/VSDocumentReopen/Resources/FileError_16x.png -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Resources/OpenFile_16x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/majorimi/vs-reopen/HEAD/VSDocumentReopen/VSDocumentReopen/Resources/OpenFile_16x.png -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Resources/VsToolsWindow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/majorimi/vs-reopen/HEAD/VSDocumentReopen/VSDocumentReopen/Resources/VsToolsWindow.png -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Resources/RemoveGuide_16x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/majorimi/vs-reopen/HEAD/VSDocumentReopen/VSDocumentReopen/Resources/RemoveGuide_16x.png -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Resources/VsToolsMenu_Full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/majorimi/vs-reopen/HEAD/VSDocumentReopen/VSDocumentReopen/Resources/VsToolsMenu_Full.png -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Resources/OpenFile_16x_Gray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/majorimi/vs-reopen/HEAD/VSDocumentReopen/VSDocumentReopen/Resources/OpenFile_16x_Gray.png -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Resources/RemoveGuide_16x_Gray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/majorimi/vs-reopen/HEAD/VSDocumentReopen/VSDocumentReopen/Resources/RemoveGuide_16x_Gray.png -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Resources/RemoveNonExisting_16x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/majorimi/vs-reopen/HEAD/VSDocumentReopen/VSDocumentReopen/Resources/RemoveNonExisting_16x.png -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Resources/ClearWindowContent_16x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/majorimi/vs-reopen/HEAD/VSDocumentReopen/VSDocumentReopen/Resources/ClearWindowContent_16x.png -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Resources/RemoveNonExisting_16x_Gray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/majorimi/vs-reopen/HEAD/VSDocumentReopen/VSDocumentReopen/Resources/RemoveNonExisting_16x_Gray.png -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Resources/ClearWindowContent_16x_Gray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/majorimi/vs-reopen/HEAD/VSDocumentReopen/VSDocumentReopen/Resources/ClearWindowContent_16x_Gray.png -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Infrastructure/HistoryCommands/IHistoryCommand.cs: -------------------------------------------------------------------------------- 1 | namespace VSDocumentReopen.Infrastructure.HistoryCommands 2 | { 3 | public interface IHistoryCommand 4 | { 5 | void Execute(); 6 | } 7 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Infrastructure/Version/IVsVersionProvider.cs: -------------------------------------------------------------------------------- 1 | namespace VSDocumentReopen.Infrastructure.Version 2 | { 3 | public interface IVsVersionProvider 4 | { 5 | string GetVersion(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Infrastructure/Document/Commands/IDocumentCommand.cs: -------------------------------------------------------------------------------- 1 | namespace VSDocumentReopen.Infrastructure.Document.Commands 2 | { 3 | public interface IDocumentCommand 4 | { 5 | void Execute(); 6 | } 7 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Infrastructure/Document/Tracking/SolutionStates.cs: -------------------------------------------------------------------------------- 1 | namespace VSDocumentReopen.Infrastructure.Document.Tracking 2 | { 3 | public enum SolutionStates 4 | { 5 | None = 0, 6 | Opened = 1, 7 | StartedToClose = 2 8 | } 9 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Infrastructure/Helpers/IJsonSerializer.cs: -------------------------------------------------------------------------------- 1 | namespace VSDocumentReopen.Infrastructure.Helpers 2 | { 3 | public interface IJsonSerializer 4 | { 5 | string Serialize(T obj); 6 | 7 | T Deserialize(string s); 8 | } 9 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/VS/MessageBox/IMessageBox.cs: -------------------------------------------------------------------------------- 1 | namespace VSDocumentReopen.VS.MessageBox 2 | { 3 | public interface IMessageBox 4 | { 5 | void ShowInfo(string title, string message); 6 | void ShowError(string title, string message); 7 | } 8 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Infrastructure/Document/Tracking/IDocumentHistoryManager.cs: -------------------------------------------------------------------------------- 1 | namespace VSDocumentReopen.Infrastructure.Document.Tracking 2 | { 3 | public interface IDocumentHistoryManager : IDocumentHistoryQueries, IDocumentHistoryCommands 4 | { 5 | } 6 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Infrastructure/Logging/DefaultLoggerContext.cs: -------------------------------------------------------------------------------- 1 | namespace VSDocumentReopen.Infrastructure.Logging 2 | { 3 | public sealed class DefaultLoggerContext : LoggerContext 4 | { 5 | public override ILogger Logger => NullLogger.Instance; 6 | } 7 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Infrastructure/IHistoryToolWindowRepositoryFactory.cs: -------------------------------------------------------------------------------- 1 | using VSDocumentReopen.Infrastructure.Repositories; 2 | 3 | namespace VSDocumentReopen.Infrastructure 4 | { 5 | public interface IHistoryToolWindowRepositoryFactory 6 | { 7 | IHistoryToolWindowRepository Create(); 8 | } 9 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Infrastructure/Version/VsVersionContext.cs: -------------------------------------------------------------------------------- 1 | namespace VSDocumentReopen.Infrastructure.Version 2 | { 3 | public abstract class VsVersionContext 4 | { 5 | public static VsVersionContext Current { get; set; } 6 | 7 | public abstract IVsVersionProvider VersionProvider { get; } 8 | } 9 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Infrastructure/FileIcons/IFileExtensionIconResolver.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using VSDocumentReopen.Domain.Documents; 3 | 4 | namespace VSDocumentReopen.Infrastructure.FileIcons 5 | { 6 | public interface IFileExtensionIconResolver 7 | { 8 | Icon GetIcon(IClosedDocument document); 9 | } 10 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Infrastructure/HistoryCommands/IHistoryCommandFactory.cs: -------------------------------------------------------------------------------- 1 | using VSDocumentReopen.Domain.Documents; 2 | 3 | namespace VSDocumentReopen.Infrastructure.HistoryCommands 4 | { 5 | public interface IHistoryCommandFactory 6 | { 7 | IHistoryCommand CreateCommand(params IClosedDocument[] closedDocuments); 8 | } 9 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Domain/HistoryControl/ColumnInfo.cs: -------------------------------------------------------------------------------- 1 | namespace VSDocumentReopen.Domain.HistoryControl 2 | { 3 | public class ColumnInfo 4 | { 5 | public int Id { get; set; } 6 | 7 | public int Position { get; set; } 8 | 9 | public double Width { get; set; } 10 | 11 | public bool Visible { get; set; } 12 | } 13 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Domain/HistoryControl/HistoryControlData.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace VSDocumentReopen.Domain.HistoryControl 4 | { 5 | public class HistoryControlData 6 | { 7 | public IEnumerable SearchHistory { get; set; } 8 | 9 | public IEnumerable ColumnsInfo { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Infrastructure/IHistoryRepositoryFactory.cs: -------------------------------------------------------------------------------- 1 | using VSDocumentReopen.Domain; 2 | using VSDocumentReopen.Infrastructure.Repositories; 3 | 4 | namespace VSDocumentReopen.Infrastructure 5 | { 6 | public interface IHistoryRepositoryFactory 7 | { 8 | IHistoryRepository CreateHistoryRepository(SolutionInfo solutionInfo); 9 | } 10 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Infrastructure/Repositories/IHistoryToolWindowRepository.cs: -------------------------------------------------------------------------------- 1 | using VSDocumentReopen.Domain.HistoryControl; 2 | 3 | namespace VSDocumentReopen.Infrastructure.Repositories 4 | { 5 | public interface IHistoryToolWindowRepository 6 | { 7 | HistoryControlData GetSettings(); 8 | 9 | bool SaveSettings(HistoryControlData data); 10 | } 11 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Domain/Documents/IClosedDocument.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace VSDocumentReopen.Domain.Documents 4 | { 5 | public interface IClosedDocument 6 | { 7 | DateTime ClosedAt { get; } 8 | string FullName { get; } 9 | string Kind { get; } 10 | string Language { get; } 11 | string Name { get; } 12 | 13 | bool IsValid(); 14 | } 15 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/VS/ToolWindows/GridViewExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | namespace VSDocumentReopen.VS.ToolWindows 4 | { 5 | public static class GridViewExtensions 6 | { 7 | public static string GetGridViewHeaderText(this GridViewColumn column) 8 | { 9 | return (column.Header as GridViewColumnHeader)?.Content?.ToString().Trim(); 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Infrastructure/Document/Factories/IDocumentCommandFactory.cs: -------------------------------------------------------------------------------- 1 | using VSDocumentReopen.Domain.Documents; 2 | using VSDocumentReopen.Infrastructure.Document.Commands; 3 | 4 | namespace VSDocumentReopen.Infrastructure.Document.Factories 5 | { 6 | public interface IDocumentCommandFactory 7 | { 8 | IDocumentCommand CreateCommand(IClosedDocument closedDocument); 9 | } 10 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Infrastructure/Repositories/IHistoryRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using VSDocumentReopen.Domain.Documents; 3 | 4 | namespace VSDocumentReopen.Infrastructure.Repositories 5 | { 6 | public interface IHistoryRepository 7 | { 8 | bool SaveHistory(IEnumerable closedDocumentHistories); 9 | 10 | IEnumerable GetHistory(); 11 | } 12 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Infrastructure/Version/VsDteVersionProvider.cs: -------------------------------------------------------------------------------- 1 | using EnvDTE; 2 | 3 | namespace VSDocumentReopen.Infrastructure.Version 4 | { 5 | public class VsDteVersionProvider : IVsVersionProvider 6 | { 7 | private readonly _DTE _dte; 8 | 9 | public VsDteVersionProvider(_DTE dte) 10 | { 11 | _dte = dte; 12 | } 13 | 14 | public string GetVersion() 15 | { 16 | return _dte?.Version; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Infrastructure/Document/Commands/DoNothingCommand.cs: -------------------------------------------------------------------------------- 1 | namespace VSDocumentReopen.Infrastructure.Document.Commands 2 | { 3 | public class DoNothingCommand : IDocumentCommand 4 | { 5 | public static DoNothingCommand Instance { get; } 6 | 7 | static DoNothingCommand() 8 | { 9 | Instance = new DoNothingCommand(); 10 | } 11 | 12 | private DoNothingCommand() 13 | {} 14 | 15 | public void Execute() 16 | {} 17 | } 18 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Infrastructure/Document/Factories/DoNothingDocumentCommandFactory.cs: -------------------------------------------------------------------------------- 1 | using VSDocumentReopen.Domain.Documents; 2 | using VSDocumentReopen.Infrastructure.Document.Commands; 3 | 4 | namespace VSDocumentReopen.Infrastructure.Document.Factories 5 | { 6 | public class DoNothingDocumentCommandFactory : IDocumentCommandFactory 7 | { 8 | public IDocumentCommand CreateCommand(IClosedDocument closedDocument) => DoNothingCommand.Instance; 9 | } 10 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Infrastructure/Logging/Logentries/LogentriesSerilogLoggerContext.cs: -------------------------------------------------------------------------------- 1 | namespace VSDocumentReopen.Infrastructure.Logging.Logentries 2 | { 3 | public sealed class LogentriesSerilogLoggerContext : LoggerContext 4 | { 5 | private static readonly ILogger _logger; 6 | 7 | static LogentriesSerilogLoggerContext() 8 | { 9 | _logger = new LogentriesSerilogLogger(); 10 | } 11 | 12 | public override ILogger Logger => _logger; 13 | } 14 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Domain/SolutionInfo.cs: -------------------------------------------------------------------------------- 1 | namespace VSDocumentReopen.Domain 2 | { 3 | public class SolutionInfo 4 | { 5 | public string FullPath { get; } 6 | public string Name { get; } 7 | 8 | public SolutionInfo(string fullPath, string name) 9 | { 10 | FullPath = fullPath; 11 | Name = name; 12 | } 13 | 14 | public void Deconstruct(out string fullPath, out string name) 15 | { 16 | name = Name; 17 | fullPath = FullPath; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Infrastructure/Helpers/ServiceStackJsonSerializer.cs: -------------------------------------------------------------------------------- 1 | using ServiceStack.Text; 2 | 3 | namespace VSDocumentReopen.Infrastructure.Helpers 4 | { 5 | public class ServiceStackJsonSerializer : IJsonSerializer 6 | { 7 | public string Serialize(T obj) 8 | { 9 | return JsonSerializer.SerializeToString(obj); 10 | } 11 | 12 | public T Deserialize(string json) 13 | { 14 | return JsonSerializer.DeserializeFromString(json); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen.Test/Infrastructure/Logging/DefaultLoggerContextTest.cs: -------------------------------------------------------------------------------- 1 | using VSDocumentReopen.Infrastructure.Logging; 2 | using Xunit; 3 | 4 | namespace VSDocumentReopen.Test.Infrastructure.Logging 5 | { 6 | public class DefaultLoggerContextTest 7 | { 8 | [Fact] 9 | public void ItShould_Return_NullLogger() 10 | { 11 | Assert.NotNull(DefaultLoggerContext.Current.Logger); 12 | Assert.Equal(NullLogger.Instance, DefaultLoggerContext.Current.Logger); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Infrastructure/Logging/LoggerContext.cs: -------------------------------------------------------------------------------- 1 | namespace VSDocumentReopen.Infrastructure.Logging 2 | { 3 | public abstract class LoggerContext 4 | { 5 | private static readonly LoggerContext Default = new DefaultLoggerContext(); 6 | 7 | private static LoggerContext _current; 8 | 9 | public static LoggerContext Current 10 | { 11 | get => _current ?? Default; 12 | set => _current = value; 13 | } 14 | 15 | public abstract ILogger Logger { get; } 16 | } 17 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/appConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "VSTempFolderName": ".vs", 3 | "PackageWorkingDirName": "VSDocumentReopen", 4 | "HistoryFileName": "history.json", 5 | "ToolWindowSettingsFileName": "ToolWindowSettings.json", 6 | "MaxNumberOfHistoryItemsOnMenu": 5, 7 | "MaxAllowedHistoryItems": 200, 8 | "MaxAllowedHistoryItemAgeInDays": 30, 9 | "ReopenCommandBinding": "::Ctrl+Shift+T", 10 | "ShowMoreCommandBinding": "::Ctrl+Shift+R", 11 | "RemoveCommandBinding": "::Ctrl+Shift+D" 12 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Domain/Documents/ClosedDocumentComparer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace VSDocumentReopen.Domain.Documents 4 | { 5 | public class ClosedDocumentComparer : IEqualityComparer 6 | { 7 | public bool Equals(IClosedDocument x, IClosedDocument y) 8 | { 9 | return x?.FullName == y?.FullName; 10 | } 11 | 12 | public int GetHashCode(IClosedDocument obj) 13 | { 14 | return obj?.FullName?.GetHashCode() ?? 0; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Infrastructure/Document/Tracking/IDocumentHistoryQueries.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using VSDocumentReopen.Domain.Documents; 4 | 5 | namespace VSDocumentReopen.Infrastructure.Document.Tracking 6 | { 7 | public interface IDocumentHistoryQueries 8 | { 9 | event EventHandler HistoryChanged; 10 | 11 | int Count { get; } 12 | 13 | IEnumerable Get(int number); 14 | 15 | IEnumerable GetAll(); 16 | } 17 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Infrastructure/Version/VsDteVersionContext.cs: -------------------------------------------------------------------------------- 1 | using EnvDTE; 2 | 3 | namespace VSDocumentReopen.Infrastructure.Version 4 | { 5 | public sealed class VsDteVersionContext : VsVersionContext 6 | { 7 | private readonly IVsVersionProvider _dteVsVersionProvider; 8 | 9 | public VsDteVersionContext(_DTE dte) 10 | { 11 | _dteVsVersionProvider = new VsDteVersionProvider(dte); 12 | } 13 | 14 | public override IVsVersionProvider VersionProvider => _dteVsVersionProvider; 15 | } 16 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Infrastructure/FileIcons/WindowsFileExtensionIconResolver.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using VSDocumentReopen.Domain.Documents; 3 | 4 | namespace VSDocumentReopen.Infrastructure.FileIcons 5 | { 6 | public class WindowsFileExtensionIconResolver : IFileExtensionIconResolver 7 | { 8 | public Icon GetIcon(IClosedDocument document) 9 | { 10 | if (document.IsValid()) 11 | { 12 | var iconForFile = Icon.ExtractAssociatedIcon(document.FullName); 13 | return iconForFile; 14 | } 15 | 16 | return null; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Infrastructure/Logging/ILogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace VSDocumentReopen.Infrastructure.Logging 4 | { 5 | public interface ILogger 6 | { 7 | void Trace(string message); 8 | 9 | void Info(string message); 10 | 11 | void Warning(string message); 12 | 13 | void Warning(string message, Exception exception); 14 | 15 | void Error(string message); 16 | 17 | void Error(string message, Exception exception); 18 | 19 | void Critical(string message); 20 | 21 | void Critical(string message, Exception exception); 22 | } 23 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Infrastructure/ConfigurationManager.cs: -------------------------------------------------------------------------------- 1 | using VSDocumentReopen.Domain; 2 | 3 | namespace VSDocumentReopen.Infrastructure 4 | { 5 | public abstract class ConfigurationManager 6 | { 7 | private static readonly ConfigurationManager Default = new DefaultConfigurationManager(); 8 | 9 | private static ConfigurationManager _current; 10 | 11 | public static ConfigurationManager Current 12 | { 13 | get => _current ?? (_current = Default); 14 | set => _current = value ?? Default; 15 | } 16 | 17 | public abstract IConfiguration Config { get; } 18 | } 19 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Domain/IConfiguration.cs: -------------------------------------------------------------------------------- 1 | namespace VSDocumentReopen.Domain 2 | { 3 | public interface IConfiguration 4 | { 5 | string HistoryFileName { get; } 6 | string ToolWindowSettingsFileName { get; } 7 | int MaxAllowedHistoryItems { get; } 8 | int MaxAllowedHistoryItemAgeInDays { get; } 9 | int MaxNumberOfHistoryItemsOnMenu { get; } 10 | string PackageWorkingDirName { get; } 11 | string ReopenCommandBinding { get; } 12 | string RemoveCommandBinding { get; } 13 | string ShowMoreCommandBinding { get; } 14 | string VSTempFolderName { get; } 15 | } 16 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Infrastructure/Document/Tracking/IDocumentHistoryCommands.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using VSDocumentReopen.Domain.Documents; 3 | 4 | namespace VSDocumentReopen.Infrastructure.Document.Tracking 5 | { 6 | public interface IDocumentHistoryCommands 7 | { 8 | void Clear(); 9 | 10 | void Add(IClosedDocument document); 11 | 12 | IClosedDocument RemoveLast(); 13 | 14 | void Remove(IClosedDocument closedDocument); 15 | 16 | void Remove(IEnumerable closedDocuments); 17 | 18 | void Initialize(IEnumerable closedDocuments); 19 | } 20 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Infrastructure/HistoryCommands/ClearHistoryCommand.cs: -------------------------------------------------------------------------------- 1 | using VSDocumentReopen.Infrastructure.Document.Tracking; 2 | 3 | namespace VSDocumentReopen.Infrastructure.HistoryCommands 4 | { 5 | public class ClearHistoryCommand : IHistoryCommand 6 | { 7 | private readonly IDocumentHistoryCommands _documentHistoryCommands; 8 | 9 | public ClearHistoryCommand(IDocumentHistoryCommands documentHistoryCommands) 10 | { 11 | _documentHistoryCommands = documentHistoryCommands; 12 | } 13 | 14 | public void Execute() 15 | { 16 | _documentHistoryCommands?.Clear(); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Infrastructure/Document/Factories/ReopenDocumentCommandFactory.cs: -------------------------------------------------------------------------------- 1 | using EnvDTE; 2 | using VSDocumentReopen.Domain.Documents; 3 | using VSDocumentReopen.Infrastructure.Document.Commands; 4 | 5 | namespace VSDocumentReopen.Infrastructure.Document.Factories 6 | { 7 | public class ReopenDocumentCommandFactory : IDocumentCommandFactory 8 | { 9 | private readonly _DTE _dte; 10 | 11 | public ReopenDocumentCommandFactory(_DTE dte) 12 | { 13 | _dte = dte; 14 | } 15 | 16 | public IDocumentCommand CreateCommand(IClosedDocument closedDocument) 17 | { 18 | return new ReopenDocumentCommand(_dte, closedDocument); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Infrastructure/Logging/Logentries/VisualStudioVersionEnricher.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Core; 2 | using Serilog.Events; 3 | using VSDocumentReopen.Infrastructure.Version; 4 | 5 | namespace VSDocumentReopen.Infrastructure.Logging.Logentries 6 | { 7 | public class VisualStudioVersionEnricher : ILogEventEnricher 8 | { 9 | public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) 10 | { 11 | if (logEvent != null) 12 | { 13 | logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("Visual Studio Version", 14 | VsVersionContext.Current.VersionProvider.GetVersion(), 15 | false)); 16 | } 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Domain/Documents/ClosedDocument.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace VSDocumentReopen.Domain.Documents 5 | { 6 | public sealed class ClosedDocument : IClosedDocument 7 | { 8 | public string FullName { get; set; } 9 | 10 | public string Name { get; set; } 11 | 12 | public string Kind { get; set; } 13 | 14 | public string Language { get; set; } 15 | 16 | public DateTime ClosedAt { get; set; } 17 | 18 | public bool IsValid() 19 | { 20 | return File.Exists(FullName); 21 | } 22 | 23 | public override string ToString() 24 | { 25 | return $"ClosedDocument FullName: {FullName}, ClosedAt: {ClosedAt}"; 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/VS/ToolWindows/IconHandling/ButtonStates/ImageButtonState.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | namespace VSDocumentReopen.VS.ToolWindows.IconHandling.ButtonStates 4 | { 5 | public abstract class ImageButtonState 6 | { 7 | protected readonly Button _button; 8 | protected readonly Image _enabledImage; 9 | protected readonly Image _disabledImage; 10 | 11 | protected ImageButtonState(Button button, Image enabledImage, Image disabledImage) 12 | { 13 | _button = button; 14 | _enabledImage = enabledImage; 15 | _disabledImage = disabledImage; 16 | } 17 | 18 | public abstract void Enable(); 19 | 20 | public abstract void Disable(); 21 | } 22 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen.Test/Infrastructure/Logging/LoggerContextTest.cs: -------------------------------------------------------------------------------- 1 | using VSDocumentReopen.Infrastructure.Logging; 2 | using Xunit; 3 | 4 | namespace VSDocumentReopen.Test.Infrastructure.Logging 5 | { 6 | public class LoggerContextTest 7 | { 8 | [Fact] 9 | public void ItShould_Have_Default_Context() 10 | { 11 | Assert.NotNull(LoggerContext.Current); 12 | Assert.IsType(LoggerContext.Current); 13 | } 14 | 15 | [Fact] 16 | public void ItShould_Not_Allow_To_Set_Context_To_Null() 17 | { 18 | LoggerContext.Current = null; 19 | 20 | Assert.NotNull(LoggerContext.Current); 21 | Assert.IsType(LoggerContext.Current); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen.Test/Infrastructure/Document/Commands/DoNothingCommandTest.cs: -------------------------------------------------------------------------------- 1 | using VSDocumentReopen.Infrastructure.Document.Commands; 2 | using Xunit; 3 | 4 | namespace VSDocumentReopen.Test.Infrastructure.Document.Commands 5 | { 6 | public class DoNothingCommandTest 7 | { 8 | [Fact] 9 | public void ItShouldDo_Nothing() 10 | { 11 | var doNothing = DoNothingCommand.Instance; 12 | 13 | doNothing.Execute(); 14 | Assert.NotNull(doNothing); 15 | } 16 | 17 | [Fact] 18 | public void ItShouldBe_Singleton() 19 | { 20 | var doNothing = DoNothingCommand.Instance; 21 | var doNothing2 = DoNothingCommand.Instance; 22 | 23 | Assert.Equal(doNothing, doNothing2); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Infrastructure/Document/Commands/ReopenDocumentCommand.cs: -------------------------------------------------------------------------------- 1 | using EnvDTE; 2 | using VSDocumentReopen.Domain.Documents; 3 | 4 | namespace VSDocumentReopen.Infrastructure.Document.Commands 5 | { 6 | public class ReopenDocumentCommand : IDocumentCommand 7 | { 8 | private readonly _DTE _dte; 9 | private readonly IClosedDocument _closedDocument; 10 | 11 | public ReopenDocumentCommand(_DTE dte, IClosedDocument closedDocument) 12 | { 13 | _dte = dte; 14 | _closedDocument = closedDocument; 15 | } 16 | 17 | public void Execute() 18 | { 19 | if (_closedDocument.IsValid()) 20 | { 21 | _dte?.ItemOperations?.OpenFile(_closedDocument.FullName, _closedDocument.Kind); 22 | } 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Infrastructure/Repositories/JsonHistoryToolWindowRepository.cs: -------------------------------------------------------------------------------- 1 | using VSDocumentReopen.Domain.HistoryControl; 2 | using VSDocumentReopen.Infrastructure.Helpers; 3 | 4 | namespace VSDocumentReopen.Infrastructure.Repositories 5 | { 6 | public sealed class JsonHistoryToolWindowRepository : JsonHistoryRepositoryBase, IHistoryToolWindowRepository 7 | { 8 | public JsonHistoryToolWindowRepository(IJsonSerializer serializer, string storageFile) 9 | : base(serializer, storageFile) 10 | {} 11 | 12 | public HistoryControlData GetSettings() 13 | { 14 | return Load(); 15 | } 16 | 17 | public bool SaveSettings(HistoryControlData data) 18 | { 19 | return Save(data); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Infrastructure/Helpers/ImageConverterHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using System.Drawing.Imaging; 3 | using System.IO; 4 | 5 | namespace VSDocumentReopen.Infrastructure.Helpers 6 | { 7 | public class ImageConverterHelper 8 | { 9 | public static byte[] BitmapToByteArray(Bitmap bmp, ImageFormat format) 10 | { 11 | if (bmp == null) 12 | { 13 | return null; 14 | } 15 | 16 | using (var ms = new MemoryStream()) 17 | { 18 | bmp.Save(ms, format); 19 | return ms.ToArray(); 20 | } 21 | } 22 | 23 | public static Bitmap ByteArrayToBitmap(byte[] bytes) 24 | { 25 | using (var ms = new MemoryStream(bytes)) 26 | { 27 | var _bmp = Bitmap.FromStream(ms) as Bitmap; 28 | return _bmp; 29 | } 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Domain/Configuration.cs: -------------------------------------------------------------------------------- 1 | namespace VSDocumentReopen.Domain 2 | { 3 | public sealed class Configuration : IConfiguration 4 | { 5 | public string VSTempFolderName { get; set; } 6 | 7 | public string PackageWorkingDirName { get; set; } 8 | 9 | public string HistoryFileName { get; set; } 10 | 11 | public int MaxNumberOfHistoryItemsOnMenu { get; set; } 12 | 13 | public int MaxAllowedHistoryItems { get; set; } 14 | 15 | public int MaxAllowedHistoryItemAgeInDays { get; set; } 16 | 17 | public string ReopenCommandBinding { get; set; } 18 | 19 | public string RemoveCommandBinding { get; set; } 20 | 21 | public string ShowMoreCommandBinding { get; set; } 22 | 23 | public string ToolWindowSettingsFileName { get; set; } 24 | } 25 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Domain/Documents/NullDocument.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace VSDocumentReopen.Domain.Documents 4 | { 5 | public sealed class NullDocument : IClosedDocument 6 | { 7 | public static readonly NullDocument Instance; 8 | 9 | static NullDocument() 10 | { 11 | Instance = new NullDocument(); 12 | } 13 | 14 | public DateTime ClosedAt { get; } 15 | public string FullName { get;} 16 | public string Kind { get; } 17 | public string Language { get; } 18 | public string Name { get; } 19 | 20 | private NullDocument() 21 | { 22 | FullName = string.Empty; 23 | Kind = string.Empty; 24 | Language = string.Empty; 25 | Name = string.Empty; 26 | ClosedAt = DateTime.MinValue; 27 | } 28 | 29 | public bool IsValid() => false; 30 | } 31 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Infrastructure/Repositories/JsonHistoryRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using VSDocumentReopen.Domain.Documents; 3 | using VSDocumentReopen.Infrastructure.Helpers; 4 | 5 | namespace VSDocumentReopen.Infrastructure.Repositories 6 | { 7 | public sealed class JsonHistoryRepository : JsonHistoryRepositoryBase>, IHistoryRepository 8 | { 9 | public JsonHistoryRepository(IJsonSerializer serializer, string storageFile) 10 | : base(serializer, storageFile) 11 | {} 12 | 13 | public bool SaveHistory(IEnumerable closedDocumentHistories) 14 | { 15 | return Save(closedDocumentHistories); 16 | } 17 | 18 | public IEnumerable GetHistory() 19 | { 20 | return Load(); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen.Test/Infrastructure/Version/VsDteVersionContextTest.cs: -------------------------------------------------------------------------------- 1 | using EnvDTE; 2 | using Moq; 3 | using VSDocumentReopen.Infrastructure.Version; 4 | using Xunit; 5 | 6 | namespace VSDocumentReopen.Test.Infrastructure.Version 7 | { 8 | public class VsDteVersionContextTest 9 | { 10 | private readonly Mock<_DTE> _dteMock; 11 | private readonly VsDteVersionContext _vsDteVersionContext; 12 | 13 | public VsDteVersionContextTest() 14 | { 15 | _dteMock = new Mock<_DTE>(); 16 | 17 | _vsDteVersionContext = new VsDteVersionContext(_dteMock.Object); 18 | } 19 | 20 | [Fact] 21 | public void ItShould_Encapsulate_VsDteVersionProvider() 22 | { 23 | var provider = _vsDteVersionContext.VersionProvider; 24 | 25 | Assert.IsType(provider); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/VS/ToolWindows/IconHandling/ButtonStates/ButtonStateExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Controls; 3 | 4 | namespace VSDocumentReopen.VS.ToolWindows.IconHandling.ButtonStates 5 | { 6 | public static class ButtonStateExtensions 7 | { 8 | public static readonly DependencyProperty ImageButtonStateProperty = 9 | DependencyProperty.RegisterAttached("MenuButtonState", typeof(ImageButtonState), typeof(Button)); 10 | 11 | public static void SetImageButtonState(this Button element, ImageButtonState state) 12 | { 13 | element.SetValue(ImageButtonStateProperty, state); 14 | } 15 | 16 | public static ImageButtonState GetImageButtonState(this Button element) 17 | { 18 | return (ImageButtonState)element.GetValue(ImageButtonStateProperty); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen.Test/VS/Commands/VisualStudioCommandTestBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using Microsoft.VisualStudio.Shell; 4 | using Moq; 5 | 6 | namespace VSDocumentReopen.Test.VS.Commands 7 | { 8 | public abstract class VisualStudioCommandTestBase 9 | { 10 | protected readonly Mock _asyncPackageMock; 11 | 12 | protected VisualStudioCommandTestBase() 13 | { 14 | _asyncPackageMock = new Mock(); 15 | } 16 | 17 | protected void InvokeCommand(TCommand command, string method = "Execute", object sender = null) 18 | { 19 | var commandType = typeof(TCommand); 20 | var methodInfo = commandType.GetMethod(method, BindingFlags.NonPublic | BindingFlags.Instance); 21 | 22 | methodInfo?.Invoke(command, new object[] { sender, new EventArgs() }); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen.Test/Domain/SolutionInfoTest.cs: -------------------------------------------------------------------------------- 1 | using VSDocumentReopen.Domain; 2 | using Xunit; 3 | 4 | namespace VSDocumentReopen.Test.Domain 5 | { 6 | public class SolutionInfoTest 7 | { 8 | [Theory] 9 | [InlineData(null, null)] 10 | [InlineData("", "")] 11 | [InlineData("path", "name")] 12 | public void ItShould_SetGivenData(string path, string name) 13 | { 14 | var si = new SolutionInfo(path, name); 15 | 16 | Assert.Equal(path, si.FullPath); 17 | Assert.Equal(name, si.Name); 18 | } 19 | 20 | [Theory] 21 | [InlineData(null, null)] 22 | [InlineData("", "")] 23 | [InlineData("path", "name")] 24 | public void ItShould_CorrectlyDeconstruct(string path, string name) 25 | { 26 | var si = new SolutionInfo(path, name); 27 | 28 | var (p, n) = si; 29 | 30 | Assert.Equal(p, si.FullPath); 31 | Assert.Equal(n, si.Name); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen.Test/Infrastructure/Version/VsVersionContextTest.cs: -------------------------------------------------------------------------------- 1 | using Moq; 2 | using System; 3 | using VSDocumentReopen.Infrastructure.Version; 4 | using Xunit; 5 | 6 | namespace VSDocumentReopen.Test.Infrastructure.Version 7 | { 8 | public class VsVersionContextTest : IDisposable 9 | { 10 | private readonly Mock _versionContextMock; 11 | 12 | public VsVersionContextTest() 13 | { 14 | _versionContextMock = new Mock(); 15 | } 16 | 17 | [Fact] 18 | public void ItShould_Not_Initialized() 19 | { 20 | Assert.Null(VsVersionContext.Current); 21 | } 22 | 23 | [Fact] 24 | public void ItCouuld_be_Initialized() 25 | { 26 | VsVersionContext.Current = _versionContextMock.Object; 27 | 28 | Assert.NotNull(VsVersionContext.Current); 29 | } 30 | 31 | public void Dispose() 32 | { 33 | VsVersionContext.Current = null; 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen.Test/AssemblyFictures/ConfigContext.cs: -------------------------------------------------------------------------------- 1 | using Moq; 2 | using System; 3 | using VSDocumentReopen.Domain; 4 | using VSDocumentReopen.Infrastructure; 5 | 6 | namespace VSDocumentReopen.Test.AssemblyFictures 7 | { 8 | public class ConfigContext : IDisposable 9 | { 10 | private readonly Mock _configurationMock; 11 | private readonly Mock _configurationManagerMock; 12 | 13 | public Mock Configuration => _configurationMock; 14 | 15 | public ConfigContext() 16 | { 17 | _configurationMock = new Mock(); 18 | _configurationManagerMock = new Mock(); 19 | _configurationManagerMock.SetupGet(g => g.Config).Returns(_configurationMock.Object); 20 | 21 | ConfigurationManager.Current = _configurationManagerMock.Object; 22 | } 23 | 24 | public void Dispose() 25 | { 26 | ConfigurationManager.Current = null; 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen.Test/Infrastructure/Document/Factories/ReopenDocumentCommandFactoryTest.cs: -------------------------------------------------------------------------------- 1 | using VSDocumentReopen.Domain.Documents; 2 | using VSDocumentReopen.Infrastructure.Document.Commands; 3 | using VSDocumentReopen.Infrastructure.Document.Factories; 4 | using Xunit; 5 | 6 | namespace VSDocumentReopen.Test.Infrastructure.Document.Factories 7 | { 8 | public class ReopenDocumentCommandFactoryTest 9 | { 10 | [Fact] 11 | public void ItShouldHandle_Nulls() 12 | { 13 | var factory = new ReopenDocumentCommandFactory(null); 14 | var command = factory.CreateCommand(null); 15 | 16 | Assert.NotNull(factory); 17 | Assert.NotNull(command); 18 | } 19 | 20 | [Fact] 21 | public void ItShouldCreate_ReopenDocumentCommand() 22 | { 23 | var factory = new ReopenDocumentCommandFactory(null); 24 | var command = factory.CreateCommand(NullDocument.Instance); 25 | 26 | Assert.IsType(command); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen.Test/Infrastructure/FileIcons/WindowsFileExtensionIconResolverTest.cs: -------------------------------------------------------------------------------- 1 | using VSDocumentReopen.Domain.Documents; 2 | using VSDocumentReopen.Infrastructure.FileIcons; 3 | using Xunit; 4 | 5 | namespace VSDocumentReopen.Test.Infrastructure.FileIcons 6 | { 7 | public class WindowsFileExtensionIconResolverTest 8 | { 9 | private readonly WindowsFileExtensionIconResolver _fileExtensionIconResolver; 10 | 11 | public WindowsFileExtensionIconResolverTest() 12 | { 13 | _fileExtensionIconResolver = new WindowsFileExtensionIconResolver(); 14 | } 15 | 16 | [Fact] 17 | public void ItShouldHandle_NullDocument() 18 | { 19 | var icon = _fileExtensionIconResolver.GetIcon(NullDocument.Instance); 20 | 21 | Assert.Null(icon); 22 | } 23 | 24 | [Fact] 25 | public void ItShouldHandle_DocumentDoesNotExist() 26 | { 27 | var icon = _fileExtensionIconResolver.GetIcon(new ClosedDocument()); 28 | 29 | Assert.Null(icon); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Infrastructure/HistoryCommands/RemoveLastCommand.cs: -------------------------------------------------------------------------------- 1 | using VSDocumentReopen.Infrastructure.Document.Factories; 2 | using VSDocumentReopen.Infrastructure.Document.Tracking; 3 | 4 | namespace VSDocumentReopen.Infrastructure.HistoryCommands 5 | { 6 | public class RemoveLastCommand : IHistoryCommand 7 | { 8 | private readonly IDocumentHistoryCommands _documentHistoryCommands; 9 | private readonly IDocumentCommandFactory _documentCommandFactory; 10 | 11 | public RemoveLastCommand(IDocumentHistoryCommands documentHistoryCommands, 12 | IDocumentCommandFactory documentCommandFactory) 13 | { 14 | _documentHistoryCommands = documentHistoryCommands; 15 | _documentCommandFactory = documentCommandFactory; 16 | } 17 | 18 | public void Execute() 19 | { 20 | var document = _documentHistoryCommands?.RemoveLast(); 21 | 22 | var command = _documentCommandFactory?.CreateCommand(document); 23 | command?.Execute(); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # .NET Desktop 2 | # Build and run tests for .NET Desktop or Windows classic desktop solutions. 3 | # Add steps that publish symbols, save build artifacts, and more: 4 | # https://docs.microsoft.com/azure/devops/pipelines/apps/windows/dot-net 5 | 6 | trigger: 7 | batch: true 8 | branches: 9 | include: 10 | - master 11 | paths: 12 | include: 13 | - VSDocumentReopen/* 14 | 15 | pool: 16 | vmImage: 'VS2017-Win2016' 17 | 18 | variables: 19 | solution: '**/*.sln' 20 | buildPlatform: 'Any CPU' 21 | buildConfiguration: 'Release' 22 | 23 | steps: 24 | - task: NuGetToolInstaller@0 25 | 26 | - task: NuGetCommand@2 27 | inputs: 28 | restoreSolution: '$(solution)' 29 | 30 | - task: VSBuild@1 31 | inputs: 32 | solution: '$(solution)' 33 | platform: '$(buildPlatform)' 34 | configuration: '$(buildConfiguration)' 35 | 36 | # - task: VSTest@2 37 | # inputs: 38 | # platform: '$(buildPlatform)' 39 | # configuration: '$(buildConfiguration)' 40 | -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Infrastructure/JsonIHistoryToolWindowRepositoryFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using VSDocumentReopen.Infrastructure.Helpers; 4 | using VSDocumentReopen.Infrastructure.Repositories; 5 | 6 | namespace VSDocumentReopen.Infrastructure 7 | { 8 | public class JsonIHistoryToolWindowRepositoryFactory : IHistoryToolWindowRepositoryFactory 9 | { 10 | private readonly IJsonSerializer _jsonSerializer; 11 | 12 | public JsonIHistoryToolWindowRepositoryFactory(IJsonSerializer jsonSerializer) 13 | { 14 | _jsonSerializer = jsonSerializer; 15 | } 16 | 17 | public IHistoryToolWindowRepository Create() 18 | { 19 | var historyFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), 20 | ConfigurationManager.Current.Config.PackageWorkingDirName, 21 | ConfigurationManager.Current.Config.ToolWindowSettingsFileName); 22 | 23 | return new JsonHistoryToolWindowRepository(_jsonSerializer, historyFile); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/VS/ToolWindows/IconHandling/ButtonStates/ButtonEnabledState.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | namespace VSDocumentReopen.VS.ToolWindows.IconHandling.ButtonStates 4 | { 5 | public class ButtonEnabledState : ImageButtonState 6 | { 7 | public ButtonEnabledState(Button button, Image enabledImage, Image disabledImage) 8 | : base(button, enabledImage, disabledImage) 9 | {} 10 | 11 | public override void Disable() 12 | { 13 | if (_button is null) 14 | { 15 | return; 16 | } 17 | 18 | _button.IsEnabled = false; 19 | _button.Content = _disabledImage; 20 | 21 | var state = new ButtonDisabledState(_button, _enabledImage, _disabledImage); 22 | _button.SetImageButtonState(state); 23 | } 24 | 25 | public override void Enable() 26 | { 27 | if (_button is null) 28 | { 29 | return; 30 | } 31 | 32 | _button.IsEnabled = true; 33 | _button.Content = _enabledImage; 34 | _button.SetImageButtonState(this); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/VS/ToolWindows/IconHandling/ButtonStates/ButtonDisabledState.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | namespace VSDocumentReopen.VS.ToolWindows.IconHandling.ButtonStates 4 | { 5 | public class ButtonDisabledState : ImageButtonState 6 | { 7 | public ButtonDisabledState(Button button, Image enabledImage, Image disabledImage) 8 | : base(button, enabledImage, disabledImage) 9 | {} 10 | 11 | public override void Disable() 12 | { 13 | if (_button is null) 14 | { 15 | return; 16 | } 17 | 18 | _button.IsEnabled = false; 19 | _button.Content = _disabledImage; 20 | 21 | _button.SetImageButtonState(this); 22 | } 23 | 24 | public override void Enable() 25 | { 26 | if (_button is null) 27 | { 28 | return; 29 | } 30 | 31 | _button.IsEnabled = true; 32 | _button.Content = _enabledImage; 33 | 34 | var state = new ButtonEnabledState(_button, _enabledImage, _disabledImage); 35 | _button.SetImageButtonState(state); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Infrastructure/DefaultConfigurationManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Reflection; 4 | using VSDocumentReopen.Domain; 5 | using VSDocumentReopen.Infrastructure.Helpers; 6 | 7 | namespace VSDocumentReopen.Infrastructure 8 | { 9 | public class DefaultConfigurationManager : ConfigurationManager 10 | { 11 | private const string ConfigFileName = "AppConfig.json"; 12 | 13 | private Lazy _loadConfig; 14 | 15 | public DefaultConfigurationManager() 16 | { 17 | _loadConfig = new Lazy(LoadConfig); 18 | } 19 | 20 | public override IConfiguration Config => _loadConfig.Value; 21 | 22 | private static IConfiguration LoadConfig() 23 | { 24 | var workingDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); 25 | var config = Path.Combine(workingDir, ConfigFileName); 26 | 27 | var json = File.ReadAllText(config); 28 | return new ServiceStackJsonSerializer().Deserialize(json); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/VS/ToolWindows/IconHandling/WpfImageSourceConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using System.Windows; 4 | using System.Windows.Interop; 5 | using System.Windows.Media.Imaging; 6 | using VSDocumentReopen.Infrastructure.Helpers; 7 | 8 | namespace VSDocumentReopen.VS.ToolWindows.IconHandling 9 | { 10 | public class WpfImageSourceConverter 11 | { 12 | public static BitmapSource CreateBitmapSource(byte[] img) 13 | { 14 | if (img is null || img.Length == 0) 15 | { 16 | return null; 17 | } 18 | 19 | var image = ImageConverterHelper.ByteArrayToBitmap(img); 20 | 21 | return CreateBitmapSource(image); 22 | } 23 | 24 | public static BitmapSource CreateBitmapSource(Bitmap img) 25 | { 26 | if(img is null) 27 | { 28 | return null; 29 | } 30 | 31 | var bitmapSource = Imaging.CreateBitmapSourceFromHBitmap( 32 | img.GetHbitmap(), 33 | IntPtr.Zero, 34 | Int32Rect.Empty, 35 | BitmapSizeOptions.FromEmptyOptions()); 36 | 37 | return bitmapSource; 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Infrastructure/Logging/Logentries/ExtensionVersionEnricher.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Core; 2 | using Serilog.Events; 3 | using System; 4 | using System.Diagnostics; 5 | using System.Reflection; 6 | 7 | namespace VSDocumentReopen.Infrastructure.Logging.Logentries 8 | { 9 | public class ExtensionVersionEnricher : ILogEventEnricher 10 | { 11 | private readonly Lazy _versionReader; 12 | 13 | public ExtensionVersionEnricher() 14 | { 15 | _versionReader = new Lazy(GetPackageVersion, true); 16 | } 17 | 18 | public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) 19 | { 20 | if (logEvent != null) 21 | { 22 | logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("VSIX Version", 23 | _versionReader.Value, 24 | false)); 25 | } 26 | } 27 | 28 | private string GetPackageVersion() 29 | { 30 | var assembly = Assembly.GetExecutingAssembly(); 31 | var fvi = FileVersionInfo.GetVersionInfo(assembly.Location); 32 | return fvi.FileVersion; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/VS/MessageBox/VSMessageBox.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.Shell; 2 | using Microsoft.VisualStudio.Shell.Interop; 3 | using System; 4 | using System.Diagnostics.CodeAnalysis; 5 | 6 | namespace VSDocumentReopen.VS.MessageBox 7 | { 8 | [ExcludeFromCodeCoverage] 9 | public sealed class VSMessageBox : IMessageBox 10 | { 11 | private readonly IServiceProvider _serviceProvider; 12 | 13 | public VSMessageBox(IServiceProvider serviceProvider) 14 | { 15 | _serviceProvider = serviceProvider; 16 | } 17 | 18 | public void ShowError(string title, string message) 19 | { 20 | Show(title, message, OLEMSGICON.OLEMSGICON_CRITICAL); 21 | } 22 | 23 | public void ShowInfo(string title, string message) 24 | { 25 | Show(title, message, OLEMSGICON.OLEMSGICON_INFO); 26 | } 27 | 28 | private void Show(string title, string message, OLEMSGICON icon) 29 | { 30 | VsShellUtilities.ShowMessageBox( 31 | _serviceProvider, 32 | message, 33 | title, 34 | icon, 35 | OLEMSGBUTTON.OLEMSGBUTTON_OK, 36 | OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen.Test/Domain/Documents/NullDocumentTest.cs: -------------------------------------------------------------------------------- 1 | using VSDocumentReopen.Domain.Documents; 2 | using Xunit; 3 | 4 | namespace VSDocumentReopen.Test.Domain.Documents 5 | { 6 | public class NullDocumentTest 7 | { 8 | [Fact] 9 | public void NullDocument_AllPublicProperties_ShouldNotBeSettable() 10 | { 11 | var doc = NullDocument.Instance; 12 | 13 | var props = doc.GetType().GetProperties(); 14 | 15 | Assert.All(props, (p) => Assert.False(p.CanWrite)); 16 | } 17 | 18 | [Fact] 19 | public void NullDocument_AlwaysInvalid() 20 | { 21 | var doc = NullDocument.Instance; 22 | 23 | Assert.False(doc.IsValid()); 24 | } 25 | 26 | [Fact] 27 | public void NullDocument_Singleton() 28 | { 29 | var doc = NullDocument.Instance; 30 | var doc2 = NullDocument.Instance; 31 | 32 | Assert.Equal(doc, doc2); 33 | } 34 | 35 | [Fact] 36 | public void NullDocument_PropertiesValues_NotNull() 37 | { 38 | var doc = NullDocument.Instance; 39 | 40 | var props = doc.GetType().GetProperties(); 41 | 42 | Assert.All(props, (p) => Assert.True(p.GetValue(doc) != null)); 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen.Test/Infrastructure/HistoryCommands/ClearHistoryCommandTest.cs: -------------------------------------------------------------------------------- 1 | using Moq; 2 | using VSDocumentReopen.Infrastructure.Document.Tracking; 3 | using VSDocumentReopen.Infrastructure.HistoryCommands; 4 | using Xunit; 5 | 6 | namespace VSDocumentReopen.Test.Infrastructure.HistoryCommands 7 | { 8 | public class ClearHistoryCommandTest 9 | { 10 | private readonly Mock _documentHistoryCommandMock; 11 | 12 | public ClearHistoryCommandTest() 13 | { 14 | _documentHistoryCommandMock = new Mock(); 15 | _documentHistoryCommandMock.Setup(s => s.Clear()); 16 | } 17 | 18 | [Fact] 19 | public void ItShould_Handle_Null() 20 | { 21 | var command = new ClearHistoryCommand(null); 22 | 23 | command.Execute(); 24 | 25 | _documentHistoryCommandMock.Verify(v => v.Clear(), Times.Never); 26 | } 27 | 28 | [Fact] 29 | public void ItShould_Call_Clear() 30 | { 31 | var command = new ClearHistoryCommand(_documentHistoryCommandMock.Object); 32 | 33 | command.Execute(); 34 | 35 | _documentHistoryCommandMock.Verify(v => v.Clear(), Times.Once); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Infrastructure/FileIcons/VisualStudioFileExtensionIconResolver.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.Shell.Interop; 2 | using System.Drawing; 3 | using VSDocumentReopen.Domain.Documents; 4 | using GelUtilities = Microsoft.Internal.VisualStudio.PlatformUI.Utilities; 5 | 6 | namespace VSDocumentReopen.Infrastructure.FileIcons 7 | { 8 | public class VisualStudioFileExtensionIconResolver : IFileExtensionIconResolver 9 | { 10 | private readonly IVsImageService2 _vsImageService2; 11 | 12 | public VisualStudioFileExtensionIconResolver(IVsImageService2 vsImageService2) 13 | { 14 | _vsImageService2 = vsImageService2; 15 | } 16 | 17 | //https://msdn.microsoft.com/en-us/library/mt628927.aspx?f=255&MSPPError=-2147217396 18 | public Icon GetIcon(IClosedDocument document) 19 | { 20 | Icon icon = null; 21 | if (!string.IsNullOrWhiteSpace(document?.Name)) 22 | { 23 | IVsUIObject uIObj = _vsImageService2.GetIconForFile(document.Name, __VSUIDATAFORMAT.VSDF_WINFORMS); 24 | if (uIObj != null) 25 | { 26 | icon = (Icon) GelUtilities.GetObjectData(uIObj); 27 | } 28 | } 29 | 30 | return icon; 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen.Test/Infrastructure/Version/VsDteVersionProviderTest.cs: -------------------------------------------------------------------------------- 1 | using EnvDTE; 2 | using Moq; 3 | using VSDocumentReopen.Infrastructure.Version; 4 | using Xunit; 5 | 6 | namespace VSDocumentReopen.Test.Infrastructure.Version 7 | { 8 | public class VsDteVersionProviderTest 9 | { 10 | private readonly Mock<_DTE> _dteMock; 11 | private readonly VsDteVersionProvider _vsDteVersionProvider; 12 | 13 | public VsDteVersionProviderTest() 14 | { 15 | _dteMock = new Mock<_DTE>(); 16 | 17 | _vsDteVersionProvider = new VsDteVersionProvider(_dteMock.Object); 18 | } 19 | 20 | [Fact] 21 | public void ItShould_AcceptNull() 22 | { 23 | var provider = new VsDteVersionProvider(null); 24 | 25 | Assert.Null(provider.GetVersion()); 26 | _dteMock.VerifyGet(g => g.Version, Times.Never); 27 | } 28 | 29 | [Fact] 30 | public void ItSould_Return_Version_From_DTE() 31 | { 32 | _dteMock.SetupGet(g => g.Version).Returns("15"); 33 | 34 | var version = _vsDteVersionProvider.GetVersion(); 35 | 36 | Assert.NotNull(version); 37 | Assert.Equal("15", version); 38 | _dteMock.VerifyGet(g => g.Version, Times.Once); 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Infrastructure/Helpers/PathFormatter.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | 4 | namespace VSDocumentReopen.Infrastructure.Helpers 5 | { 6 | public static class PathFormatter 7 | { 8 | public static string ShrinkPath(string absolutePath, int limit, string spacer = "…") 9 | { 10 | if (string.IsNullOrWhiteSpace(absolutePath)) 11 | { 12 | return string.Empty; 13 | } 14 | if (absolutePath.Length <= limit) 15 | { 16 | return absolutePath; 17 | } 18 | 19 | var parts = new List(); 20 | 21 | var fi = new FileInfo(absolutePath); 22 | string drive = Path.GetPathRoot(fi.FullName); 23 | 24 | 25 | parts.Add(drive.TrimEnd('\\')); 26 | parts.Add(spacer); 27 | parts.Add(fi.Name); 28 | 29 | var ret = string.Join("\\", parts); 30 | var dir = fi.Directory; 31 | 32 | while (ret.Length < limit && dir != null) 33 | { 34 | if (ret.Length + dir.Name.Length > limit) 35 | { 36 | break; 37 | } 38 | 39 | parts.Insert(2, dir.Name); 40 | 41 | dir = dir.Parent; 42 | ret = string.Join("\\", parts); 43 | } 44 | 45 | return ret; 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen.Test/VS/ToolWindows/ClosedDocumentsHistoryTest.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Moq; 3 | using VSDocumentReopen.Infrastructure; 4 | using VSDocumentReopen.Infrastructure.Document.Tracking; 5 | using VSDocumentReopen.VS.ToolWindows; 6 | using Xunit; 7 | 8 | namespace VSDocumentReopen.Test.VS.ToolWindows 9 | { 10 | public class ClosedDocumentsHistoryTest 11 | { 12 | private readonly Mock _docuemntQueriesMock; 13 | private readonly Mock _historyToolWindowRepositoryFactoryMock; 14 | 15 | public ClosedDocumentsHistoryTest() 16 | { 17 | _docuemntQueriesMock = new Mock(); 18 | _historyToolWindowRepositoryFactoryMock = new Mock(); 19 | } 20 | 21 | [StaFact] 22 | public async Task ItShould_Initialize() 23 | { 24 | await ClosedDocumentsHistory.InitializeAsync(_docuemntQueriesMock.Object, 25 | null, 26 | null, 27 | null, 28 | null, 29 | null, 30 | _historyToolWindowRepositoryFactoryMock.Object); 31 | 32 | Assert.NotNull(ClosedDocumentsHistory.ContentWindow); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Infrastructure/JsonHistoryRepositoryFactory.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.IO; 3 | using VSDocumentReopen.Domain; 4 | using VSDocumentReopen.Infrastructure.Helpers; 5 | using VSDocumentReopen.Infrastructure.Repositories; 6 | 7 | namespace VSDocumentReopen.Infrastructure 8 | { 9 | public sealed class JsonHistoryRepositoryFactory : IHistoryRepositoryFactory 10 | { 11 | private readonly IJsonSerializer _jsonSerializer; 12 | 13 | public JsonHistoryRepositoryFactory(IJsonSerializer jsonSerializer) 14 | { 15 | _jsonSerializer = jsonSerializer; 16 | } 17 | 18 | public IHistoryRepository CreateHistoryRepository(SolutionInfo solutionInfo) 19 | { 20 | if (solutionInfo is null) 21 | { 22 | throw new InvalidEnumArgumentException($"Parameter: {nameof(solutionInfo)} cannot be null"); 23 | } 24 | 25 | var historyFile = Path.Combine(solutionInfo.FullPath, 26 | ConfigurationManager.Current.Config.VSTempFolderName, 27 | ConfigurationManager.Current.Config.PackageWorkingDirName, 28 | ConfigurationManager.Current.Config.HistoryFileName); 29 | 30 | return new JsonHistoryRepository(_jsonSerializer, historyFile); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Infrastructure/HistoryCommands/RemoveSomeCommand.cs: -------------------------------------------------------------------------------- 1 | using VSDocumentReopen.Domain.Documents; 2 | using VSDocumentReopen.Infrastructure.Document.Factories; 3 | using VSDocumentReopen.Infrastructure.Document.Tracking; 4 | 5 | namespace VSDocumentReopen.Infrastructure.HistoryCommands 6 | { 7 | public class RemoveSomeCommand : IHistoryCommand 8 | { 9 | private readonly IDocumentHistoryCommands _documentHistoryCommands; 10 | private readonly IDocumentCommandFactory _documentCommandFactory; 11 | private readonly IClosedDocument[] _closedDocuments; 12 | 13 | public RemoveSomeCommand(IDocumentHistoryCommands documentHistoryCommands, 14 | IDocumentCommandFactory documentCommandFactory, 15 | params IClosedDocument[] closedDocuments) 16 | { 17 | _documentHistoryCommands = documentHistoryCommands; 18 | _documentCommandFactory = documentCommandFactory; 19 | _closedDocuments = closedDocuments; 20 | } 21 | 22 | public void Execute() 23 | { 24 | foreach (var doc in _closedDocuments) 25 | { 26 | var cmd = _documentCommandFactory?.CreateCommand(doc); 27 | cmd?.Execute(); 28 | } 29 | 30 | _documentHistoryCommands?.Remove(_closedDocuments); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen.Test/Domain/Documents/ClosedDocumentTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using VSDocumentReopen.Domain.Documents; 3 | using Xunit; 4 | 5 | namespace VSDocumentReopen.Test.Domain.Documents 6 | { 7 | public class ClosedDocumentTest 8 | { 9 | [Fact] 10 | public void ClosedDocument_AllPublicProperties_ShouldBeSettable() 11 | { 12 | var doc = new ClosedDocument(); 13 | 14 | var props = doc.GetType().GetProperties(); 15 | 16 | Assert.All(props, (p) => Assert.True(p.CanWrite)); 17 | } 18 | 19 | [Theory] 20 | [InlineData(null)] 21 | [InlineData("")] 22 | [InlineData(" ")] 23 | [InlineData("invalid")] 24 | public void ClosedDocument_Handle_InvalidPath(string path) 25 | { 26 | var doc = new ClosedDocument() 27 | { 28 | FullName = path 29 | }; 30 | 31 | Assert.False(doc.IsValid()); 32 | } 33 | 34 | [Fact] 35 | public void ClosedDocument_ToString_Should_Contins_Data() 36 | { 37 | var doc = new ClosedDocument() 38 | { 39 | FullName = "c:\\test.cs", 40 | ClosedAt = DateTime.Now 41 | }; 42 | 43 | var str = doc.ToString(); 44 | 45 | Assert.Contains(doc.FullName, str); 46 | Assert.Contains(doc.ClosedAt.ToString(), str); 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen.Test/Infrastructure/Document/Factories/DoNothingDocumentCommandFactoryTest.cs: -------------------------------------------------------------------------------- 1 | using VSDocumentReopen.Domain.Documents; 2 | using VSDocumentReopen.Infrastructure.Document.Commands; 3 | using VSDocumentReopen.Infrastructure.Document.Factories; 4 | using Xunit; 5 | 6 | namespace VSDocumentReopen.Test.Infrastructure.Document.Factories 7 | { 8 | public class DoNothingDocumentCommandFactoryTest 9 | { 10 | [Fact] 11 | public void ItShouldHandle_Null() 12 | { 13 | var factory = new DoNothingDocumentCommandFactory(); 14 | var command = factory.CreateCommand(null); 15 | 16 | Assert.NotNull(factory); 17 | Assert.NotNull(command); 18 | } 19 | 20 | [Fact] 21 | public void ItShouldCreate_DoNothingCommand() 22 | { 23 | var factory = new DoNothingDocumentCommandFactory(); 24 | var command = factory.CreateCommand(NullDocument.Instance); 25 | 26 | Assert.IsType(command); 27 | } 28 | 29 | [Fact] 30 | public void ItShouldReturn_Singleton() 31 | { 32 | var factory = new DoNothingDocumentCommandFactory(); 33 | var command = factory.CreateCommand(NullDocument.Instance); 34 | var command2 = factory.CreateCommand(null); 35 | 36 | Assert.Equal(command, command2); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen.Test/Infrastructure/JsonIHistoryToolWindowRepositoryFactoryTest.cs: -------------------------------------------------------------------------------- 1 | using VSDocumentReopen.Infrastructure; 2 | using VSDocumentReopen.Infrastructure.Repositories; 3 | using VSDocumentReopen.Test.AssemblyFictures; 4 | using Xunit; 5 | 6 | namespace VSDocumentReopen.Test.Infrastructure 7 | { 8 | public class JsonIHistoryToolWindowRepositoryFactoryTest : IAssemblyFixture 9 | { 10 | private readonly JsonIHistoryToolWindowRepositoryFactory _jsonHistoryRepositoryFactory; 11 | private readonly ConfigContext _configContext; 12 | 13 | public JsonIHistoryToolWindowRepositoryFactoryTest(ConfigContext configContext) 14 | { 15 | _jsonHistoryRepositoryFactory = new JsonIHistoryToolWindowRepositoryFactory(null); 16 | _configContext = configContext; 17 | } 18 | 19 | [Fact] 20 | public void ItShould_Handle_ValidObject() 21 | { 22 | _configContext.Configuration.SetupGet(g => g.PackageWorkingDirName).Returns("PackageWorkingDirName"); 23 | _configContext.Configuration.SetupGet(g => g.ToolWindowSettingsFileName).Returns("ToolWindowSettingsFileName"); 24 | 25 | var ret = _jsonHistoryRepositoryFactory.Create(); 26 | 27 | Assert.NotNull(ret); 28 | Assert.IsType(ret); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/VS/ToolWindows/ClosedDocumentsHistoryControl_Types.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Media.Imaging; 2 | using VSDocumentReopen.Domain.Documents; 3 | using VSDocumentReopen.VS.ToolWindows.IconHandling; 4 | 5 | namespace VSDocumentReopen.VS.ToolWindows 6 | { 7 | public partial class ClosedDocumentsHistoryControl 8 | { 9 | private class ClosedDocumentHistoryItem 10 | { 11 | private static BitmapSource _existsImage = WpfImageSourceConverter.CreateBitmapSource(VSDocumentReopen.Resources.FileOK_16x); 12 | private static BitmapSource _notExistsImage = WpfImageSourceConverter.CreateBitmapSource(VSDocumentReopen.Resources.FileError_16x); 13 | 14 | public int Index { get; } 15 | 16 | public bool IsExists { get; } 17 | public string IsExistsTooltip => IsExists ? "Yes" : "No"; 18 | public BitmapSource IsExistsIcon => IsExists ? _existsImage : _notExistsImage; 19 | 20 | public BitmapSource LanguageIcon { get; } 21 | 22 | public IClosedDocument ClosedDocument { get; } 23 | 24 | public ClosedDocumentHistoryItem(IClosedDocument closedDocument, int index, BitmapSource typeIcon) 25 | { 26 | Index = index; 27 | ClosedDocument = closedDocument; 28 | IsExists = ClosedDocument.IsValid(); 29 | LanguageIcon = typeIcon; 30 | } 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen.Test/VS/Commands/ShowDocumentsHIstoryCommandTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.Threading.Tasks; 4 | using VSDocumentReopen.VS.Commands; 5 | using Xunit; 6 | 7 | namespace VSDocumentReopen.Test.VS.Commands 8 | { 9 | public class ShowDocumentsHIstoryCommandTest : VisualStudioCommandTestBase 10 | { 11 | private readonly ShowDocumentsHIstoryCommand _showDocumentsHIstoryCommand; 12 | 13 | public ShowDocumentsHIstoryCommandTest() 14 | { 15 | Task.Run(() => ShowDocumentsHIstoryCommand.InitializeAsync(_asyncPackageMock.Object)).Wait(); 16 | 17 | _showDocumentsHIstoryCommand = ShowDocumentsHIstoryCommand.Instance; 18 | } 19 | 20 | [Fact] 21 | public void CommandId_ShouldBe() 22 | { 23 | Assert.Equal(0x0110, ShowDocumentsHIstoryCommand.CommandId); 24 | } 25 | 26 | [Fact] 27 | public async Task ItShould_Handle_Null_AsyncPackageAsync() 28 | { 29 | await Assert.ThrowsAsync(() => 30 | { 31 | return Task.Run(() => ShowDocumentsHIstoryCommand.InitializeAsync(null)); 32 | }); 33 | } 34 | 35 | [Fact] 36 | public void ItShould_NotWork_Without_VS() 37 | { 38 | Assert.Throws(() => InvokeCommand(_showDocumentsHIstoryCommand)); 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Infrastructure/FileIcons/CachedFileExtensionIconResolver.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Drawing; 3 | using System.IO; 4 | using VSDocumentReopen.Domain.Documents; 5 | 6 | namespace VSDocumentReopen.Infrastructure.FileIcons 7 | { 8 | public class CachedFileExtensionIconResolver : IFileExtensionIconResolver 9 | { 10 | private const string NoIconKey = ""; 11 | private static readonly Dictionary Icons; 12 | 13 | private readonly IFileExtensionIconResolver _fileExtensionIconResolver; 14 | 15 | static CachedFileExtensionIconResolver() 16 | { 17 | Icons = new Dictionary 18 | { 19 | { NoIconKey, Icon.FromHandle(Resources.FileError_16x.GetHicon()) } 20 | }; 21 | } 22 | 23 | public CachedFileExtensionIconResolver(IFileExtensionIconResolver fileExtensionIconResolver) 24 | { 25 | _fileExtensionIconResolver = fileExtensionIconResolver; 26 | } 27 | 28 | public Icon GetIcon(IClosedDocument document) 29 | { 30 | var extension = Path.GetExtension(document?.FullName)?.ToLower() ?? NoIconKey; 31 | 32 | if(!Icons.ContainsKey(extension)) 33 | { 34 | var icon = _fileExtensionIconResolver.GetIcon(document); 35 | if(icon is null) 36 | { 37 | return Icons[NoIconKey]; 38 | } 39 | 40 | Icons.Add(extension, icon); 41 | return icon; 42 | } 43 | 44 | return Icons[extension]; 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/VS/ToolWindows/SortAdorner.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Diagnostics.CodeAnalysis; 3 | using System.Windows; 4 | using System.Windows.Documents; 5 | using System.Windows.Media; 6 | 7 | namespace VSDocumentReopen.VS.ToolWindows 8 | { 9 | [ExcludeFromCodeCoverage] 10 | public class SortAdorner : Adorner 11 | { 12 | private static Geometry ascGeometry = 13 | Geometry.Parse("M 0 4 L 3.5 0 L 7 4 Z"); 14 | 15 | private static Geometry descGeometry = 16 | Geometry.Parse("M 0 0 L 3.5 4 L 7 0 Z"); 17 | 18 | public ListSortDirection Direction { get; private set; } 19 | 20 | public SortAdorner(UIElement element, ListSortDirection dir) 21 | : base(element) 22 | { 23 | Direction = dir; 24 | } 25 | 26 | protected override void OnRender(DrawingContext drawingContext) 27 | { 28 | base.OnRender(drawingContext); 29 | 30 | if (AdornedElement.RenderSize.Width < 20) 31 | { 32 | return; 33 | } 34 | 35 | var transform = new TranslateTransform 36 | ( 37 | AdornedElement.RenderSize.Width - 15, 38 | (AdornedElement.RenderSize.Height - 5) / 2 39 | ); 40 | 41 | drawingContext.PushTransform(transform); 42 | 43 | Geometry geometry = ascGeometry; 44 | if (Direction == ListSortDirection.Descending) 45 | { 46 | geometry = descGeometry; 47 | } 48 | 49 | drawingContext.DrawGeometry(Brushes.Black, null, geometry); 50 | drawingContext.Pop(); 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Infrastructure/Logging/NullLogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Runtime.CompilerServices; 4 | 5 | namespace VSDocumentReopen.Infrastructure.Logging 6 | { 7 | public sealed class NullLogger : ILogger 8 | { 9 | private static readonly NullLogger _instance; 10 | 11 | public static ILogger Instance => _instance; 12 | 13 | static NullLogger() 14 | { 15 | _instance = new NullLogger(); 16 | } 17 | 18 | private NullLogger() 19 | { } 20 | 21 | public void Trace(string message) 22 | { 23 | Log(message); 24 | } 25 | 26 | public void Info(string message) 27 | { 28 | Log(message); 29 | } 30 | 31 | public void Warning(string message) 32 | { 33 | Log(message); 34 | } 35 | 36 | public void Warning(string message, Exception exception) 37 | { 38 | Log(message, exception); 39 | } 40 | 41 | public void Error(string message) 42 | { 43 | Log(message); 44 | } 45 | 46 | public void Error(string message, Exception exception) 47 | { 48 | Log(message, exception); 49 | } 50 | public void Critical(string message) 51 | { 52 | Log(message); 53 | } 54 | public void Critical(string message, Exception exception) 55 | { 56 | Log(message, exception); 57 | } 58 | 59 | private void Log(string message, Exception exception = null, [CallerMemberName] string callerName = "") 60 | { 61 | Debug.WriteLine($"{callerName}: {message} {exception}"); 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen.Test/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Resources; 2 | using System.Reflection; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | 6 | // General Information about an assembly is controlled through the following 7 | // set of attributes. Change these attribute values to modify the information 8 | // associated with an assembly. 9 | [assembly: AssemblyTitle("VS Document Reopen")] 10 | [assembly: AssemblyDescription("VisualStudio Document Reopen")] 11 | [assembly: AssemblyConfiguration("")] 12 | [assembly: AssemblyCompany("Major")] 13 | [assembly: AssemblyProduct("VS Document Reopen")] 14 | [assembly: AssemblyCopyright("")] 15 | [assembly: AssemblyTrademark("")] 16 | [assembly: AssemblyCulture("")] 17 | 18 | // Setting ComVisible to false makes the types in this assembly not visible 19 | // to COM components. If you need to access a type in this assembly from 20 | // COM, set the ComVisible attribute to true on that type. 21 | [assembly: ComVisible(false)] 22 | 23 | // Version information for an assembly consists of the following four values: 24 | // 25 | // Major Version 26 | // Minor Version 27 | // Build Number 28 | // Revision 29 | // 30 | // You can specify all the values or you can default the Build and Revision Numbers 31 | // by using the '*' as shown below: 32 | // [assembly: AssemblyVersion("1.0.*")] 33 | [assembly: AssemblyVersion("1.4.10.0")] 34 | [assembly: AssemblyFileVersion("1.4.10.0")] 35 | [assembly: NeutralResourcesLanguage("en")] 36 | 37 | -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Infrastructure/Repositories/JsonHistoryRepositoryBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using VSDocumentReopen.Infrastructure.Helpers; 4 | using VSDocumentReopen.Infrastructure.Logging; 5 | 6 | namespace VSDocumentReopen.Infrastructure.Repositories 7 | { 8 | public abstract class JsonHistoryRepositoryBase 9 | { 10 | private readonly IJsonSerializer _serializer; 11 | private readonly string _storageFile; 12 | 13 | public JsonHistoryRepositoryBase(IJsonSerializer serializer, string storageFile) 14 | { 15 | _serializer = serializer; 16 | _storageFile = storageFile; 17 | } 18 | 19 | public bool Save(T closedDocumentHistories) 20 | { 21 | try 22 | { 23 | if (File.Exists(_storageFile)) 24 | { 25 | File.Delete(_storageFile); 26 | } 27 | 28 | var dir = Path.GetDirectoryName(_storageFile); 29 | if (!Directory.Exists(dir)) 30 | { 31 | Directory.CreateDirectory(dir); 32 | } 33 | 34 | var json = _serializer.Serialize(closedDocumentHistories); 35 | File.WriteAllText(_storageFile, json); 36 | } 37 | catch (Exception ex) 38 | { 39 | LoggerContext.Current.Logger.Error($"{nameof(JsonHistoryRepository)} was not able to save!", ex); 40 | return false; 41 | } 42 | 43 | return true; 44 | } 45 | 46 | public T Load() 47 | { 48 | if (!File.Exists(_storageFile)) 49 | { 50 | return default(T); 51 | } 52 | 53 | var history = File.ReadAllText(_storageFile); 54 | return _serializer.Deserialize(history); 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen.Test/Infrastructure/JsonHistoryRepositoryFactoryTest.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using VSDocumentReopen.Domain; 3 | using VSDocumentReopen.Infrastructure; 4 | using VSDocumentReopen.Infrastructure.Repositories; 5 | using VSDocumentReopen.Test.AssemblyFictures; 6 | using Xunit; 7 | 8 | namespace VSDocumentReopen.Test.Infrastructure 9 | { 10 | public class JsonHistoryRepositoryFactoryTest : IAssemblyFixture 11 | { 12 | private readonly JsonHistoryRepositoryFactory _jsonHistoryRepositoryFactory; 13 | private readonly ConfigContext _configContext; 14 | 15 | public JsonHistoryRepositoryFactoryTest(ConfigContext configContext) 16 | { 17 | _jsonHistoryRepositoryFactory = new JsonHistoryRepositoryFactory(null); 18 | _configContext = configContext; 19 | } 20 | 21 | [Fact] 22 | public void ItShould_Handle_Null() 23 | { 24 | Assert.Throws(() => _jsonHistoryRepositoryFactory.CreateHistoryRepository(null)); 25 | } 26 | 27 | [Fact] 28 | public void ItShould_Handle_ValidObject() 29 | { 30 | _configContext.Configuration.SetupGet(g => g.VSTempFolderName).Returns("VSTempFolderName"); 31 | _configContext.Configuration.SetupGet(g => g.PackageWorkingDirName).Returns("PackageWorkingDirName"); 32 | _configContext.Configuration.SetupGet(g => g.HistoryFileName).Returns("HistoryFileName"); 33 | 34 | var ret = _jsonHistoryRepositoryFactory.CreateHistoryRepository(new SolutionInfo("c:\\", "test")); 35 | 36 | Assert.NotNull(ret); 37 | Assert.IsType(ret); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen.Test/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("VSDocumentReopen.Test")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("VSDocumentReopen.Test")] 13 | [assembly: AssemblyCopyright("Copyright © 2018")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("a828cf9b-34df-497f-85ac-a3d3e3f18b78")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen.Test/VS/ToolWindows/IconHandling/WpfImageSourceConverterTest.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using VSDocumentReopen.VS.ToolWindows.IconHandling; 3 | using Xunit; 4 | 5 | namespace VSDocumentReopen.Test.VS.ToolWindows.IconHandling 6 | { 7 | public class WpfImageSourceConverterTest 8 | { 9 | [Fact] 10 | public void CreateBitmapSource_Should_Handle_Null_Bytes() 11 | { 12 | var ret = WpfImageSourceConverter.CreateBitmapSource(null as byte[]); 13 | 14 | Assert.Null(ret); 15 | } 16 | 17 | [Fact] 18 | public void CreateBitmapSource_Should_Handle_Empty_Bytes() 19 | { 20 | var ret = WpfImageSourceConverter.CreateBitmapSource(new byte[0]); 21 | 22 | Assert.Null(ret); 23 | } 24 | 25 | [Fact] 26 | public void CreateBitmapSource_Should_Handle_Bytes() 27 | { 28 | var converter = new ImageConverter(); 29 | var bytes = (byte[])converter.ConvertTo(Resources.OpenFile_16x, typeof(byte[])); 30 | 31 | var ret = WpfImageSourceConverter.CreateBitmapSource(bytes); 32 | 33 | Assert.NotNull(ret); 34 | Assert.Equal(16, ret.Width); 35 | Assert.Equal(16, ret.Height); 36 | } 37 | 38 | [Fact] 39 | public void CreateBitmapSource_Should_Handle_Null_Bitmap() 40 | { 41 | var ret = WpfImageSourceConverter.CreateBitmapSource(null as Bitmap); 42 | 43 | Assert.Null(ret); 44 | } 45 | 46 | [Fact] 47 | public void CreateBitmapSource_Should_Handle_Bitmap() 48 | { 49 | var ret = WpfImageSourceConverter.CreateBitmapSource(Resources.OpenFile_16x); 50 | 51 | Assert.NotNull(ret); 52 | Assert.Equal(16, ret.Width); 53 | Assert.Equal(16, ret.Height); 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Infrastructure/Logging/Logentries/LogentriesSerilogLogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Serilog; 3 | using Serilog.Core; 4 | using Serilog.Exceptions; 5 | using Serilog.Formatting.Json; 6 | 7 | namespace VSDocumentReopen.Infrastructure.Logging.Logentries 8 | { 9 | public sealed class LogentriesSerilogLogger : ILogger 10 | { 11 | private Logger _log = new LoggerConfiguration() 12 | .MinimumLevel.Information() 13 | .Enrich.FromLogContext() 14 | .Enrich.WithMachineName() 15 | .Enrich.WithEnvironmentUserName() 16 | .Enrich.WithExceptionDetails() 17 | .Enrich.With() 18 | .Enrich.With() 19 | .Enrich.With() 20 | .WriteTo.Logentries("", new JsonFormatter(renderMessage: true)) 21 | .CreateLogger(); 22 | 23 | public void Trace(string message) 24 | { 25 | _log.Debug(message); 26 | } 27 | 28 | public void Info(string message) 29 | { 30 | _log.Information(message); 31 | } 32 | 33 | public void Warning(string message) 34 | { 35 | _log.Warning(message); 36 | } 37 | 38 | public void Warning(string message, Exception exception) 39 | { 40 | _log.Warning(exception, message); 41 | } 42 | 43 | public void Error(string message) 44 | { 45 | _log.Error(message); 46 | } 47 | 48 | public void Error(string message, Exception exception) 49 | { 50 | _log.Error(exception, message); 51 | } 52 | 53 | public void Critical(string message) 54 | { 55 | _log.Fatal(message); 56 | } 57 | 58 | public void Critical(string message, Exception exception) 59 | { 60 | _log.Fatal(exception, message); 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen.Test/VS/ToolWindows/IconHandling/ButtonStates/ButtonEnabledStateTest.cs: -------------------------------------------------------------------------------- 1 | using VSDocumentReopen.VS.ToolWindows.IconHandling.ButtonStates; 2 | using Xunit; 3 | 4 | namespace VSDocumentReopen.Test.VS.ToolWindows.IconHandling.ButtonStates 5 | { 6 | public class ButtonEnabledStateTest : ImageButtonStateTest 7 | { 8 | [StaFact] 9 | public void ButtonEnabledState_Handle_Disable_Null_Button() 10 | { 11 | var state = new ButtonEnabledState(null, null, null); 12 | state.Disable(); 13 | 14 | var button = GetButton(state); 15 | 16 | Assert.Null(button); 17 | } 18 | 19 | [StaFact] 20 | public void ButtonEnabledState_Handle_Enable_Null_Button() 21 | { 22 | var state = new ButtonEnabledState(null, null, null); 23 | state.Enable(); 24 | 25 | var button = GetButton(state); 26 | 27 | Assert.Null(button); 28 | } 29 | 30 | [StaFact] 31 | public void ButtonEnabledState_Should_Disable_And_Change_State() 32 | { 33 | _imageButtonState.Disable(); 34 | 35 | var button = GetButton(_imageButtonState); 36 | var state = button.GetImageButtonState(); 37 | 38 | Assert.False(button.IsEnabled); 39 | Assert.Same(_disableImage, button.Content); 40 | Assert.IsType(state); 41 | } 42 | 43 | [StaFact] 44 | public void ButtonEnabledState_Should_Enable_No_State_Change() 45 | { 46 | _imageButtonState.Enable(); 47 | 48 | var button = GetButton(_imageButtonState); 49 | var state = button.GetImageButtonState(); 50 | 51 | Assert.True(button.IsEnabled); 52 | Assert.Same(_enableImage, button.Content); 53 | Assert.IsType(state); 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen.Test/VS/ToolWindows/IconHandling/ButtonStates/ButtonDisabledStateTest.cs: -------------------------------------------------------------------------------- 1 | using VSDocumentReopen.VS.ToolWindows.IconHandling.ButtonStates; 2 | using Xunit; 3 | 4 | namespace VSDocumentReopen.Test.VS.ToolWindows.IconHandling.ButtonStates 5 | { 6 | public class ButtonDisabledStateTest : ImageButtonStateTest 7 | { 8 | [StaFact] 9 | public void ButtonDisabledState_Handle_Disable_Null_Button() 10 | { 11 | var state = new ButtonDisabledState(null, null, null); 12 | state.Disable(); 13 | 14 | var button = GetButton(state); 15 | 16 | Assert.Null(button); 17 | } 18 | 19 | [StaFact] 20 | public void ButtonDisabledState_Handle_Enable_Null_Button() 21 | { 22 | var state = new ButtonDisabledState(null, null, null); 23 | state.Enable(); 24 | 25 | var button = GetButton(state); 26 | 27 | Assert.Null(button); 28 | } 29 | 30 | [StaFact] 31 | public void ButtonDisabledState_Should_Disable_No_State_Change() 32 | { 33 | _imageButtonState.Disable(); 34 | 35 | var button = GetButton(_imageButtonState); 36 | var state = button.GetImageButtonState(); 37 | 38 | Assert.False(button.IsEnabled); 39 | Assert.Same(_disableImage, button.Content); 40 | Assert.IsType(state); 41 | } 42 | 43 | [StaFact] 44 | public void ButtonDisabledState_Should_Enable_And_Change_State() 45 | { 46 | _imageButtonState.Enable(); 47 | 48 | var button = GetButton(_imageButtonState); 49 | var state = button.GetImageButtonState(); 50 | 51 | Assert.True(button.IsEnabled); 52 | Assert.Same(_enableImage, button.Content); 53 | Assert.IsType(state); 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Infrastructure/HistoryCommands/HistoryCommandFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using VSDocumentReopen.Domain.Documents; 4 | using VSDocumentReopen.Infrastructure.Document.Factories; 5 | using VSDocumentReopen.Infrastructure.Document.Tracking; 6 | 7 | namespace VSDocumentReopen.Infrastructure.HistoryCommands 8 | { 9 | public class HistoryCommandFactory : IHistoryCommandFactory 10 | where T : IHistoryCommand 11 | { 12 | private readonly IDocumentHistoryCommands _documentHistoryCommands; 13 | private readonly IDocumentCommandFactory _documentCommandFactory; 14 | 15 | public HistoryCommandFactory(IDocumentHistoryCommands documentHistoryCommands, 16 | IDocumentCommandFactory documentCommandFactory) 17 | { 18 | _documentHistoryCommands = documentHistoryCommands; 19 | _documentCommandFactory = documentCommandFactory; 20 | } 21 | 22 | public IHistoryCommand CreateCommand(params IClosedDocument[] closedDocuments) 23 | { 24 | Type typeParameterType = typeof(T); 25 | var constructors = typeParameterType.GetConstructors(); 26 | 27 | var expectParams = constructors.Any(x => x.GetParameters().Length == 3 28 | && x.GetParameters()[0].ParameterType == typeof(IDocumentHistoryCommands) 29 | && x.GetParameters()[1].ParameterType == typeof(IDocumentCommandFactory) 30 | && x.GetParameters()[2].ParameterType == typeof(IClosedDocument[])); 31 | 32 | if (expectParams) 33 | { 34 | return (IHistoryCommand)Activator.CreateInstance(typeParameterType, 35 | _documentHistoryCommands, _documentCommandFactory, closedDocuments); 36 | } 37 | 38 | return (IHistoryCommand)Activator.CreateInstance(typeParameterType); 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/source.extension.vsixmanifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Visual Studio Document Reopen 6 | Visual Studio Extension to reopen the last closed documents with (CTRL + SHIFT + T) 7 | https://github.com/majorimi/vs-reopen 8 | Resources\LICENSE.txt 9 | https://github.com/majorimi/vs-reopen/blob/master/ReleaseNotes.md 10 | Resources\icon.png 11 | Resources\VsToolsMenu.png 12 | VS Reopen Document Tool 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen.Test/Infrastructure/Helpers/ImageConverterHelperTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing.Imaging; 3 | using VSDocumentReopen.Infrastructure.Helpers; 4 | using Xunit; 5 | 6 | namespace VSDocumentReopen.Test.Infrastructure.Helpers 7 | { 8 | public class ImageConverterHelperTest 9 | { 10 | [Fact] 11 | public void BitmapToByteArray_Should_Handle_NullImage() 12 | { 13 | var ret = ImageConverterHelper.BitmapToByteArray(null, null); 14 | 15 | Assert.Null(ret); 16 | } 17 | 18 | [Fact] 19 | public void BitmapToByteArray_Should_Handle_NullImageFormat() 20 | { 21 | Assert.Throws(() => 22 | ImageConverterHelper.BitmapToByteArray(Resources.ClearWindowContent_16x_Gray, null)); 23 | } 24 | 25 | [Fact] 26 | public void BitmapToByteArray_Should_Handle_ValidObject() 27 | { 28 | var ret = ImageConverterHelper.BitmapToByteArray(Resources.ClearWindowContent_16x_Gray, ImageFormat.Png); 29 | 30 | Assert.NotNull(ret); 31 | Assert.Equal(210, ret.Length); 32 | } 33 | 34 | [Fact] 35 | public void ByteArrayToBitmap_Should_Handle_Null() 36 | { 37 | Assert.Throws(() => 38 | ImageConverterHelper.ByteArrayToBitmap(null)); 39 | } 40 | 41 | [Fact] 42 | public void ByteArrayToBitmap_Should_Handle_EmptyArray() 43 | { 44 | Assert.Throws(() => 45 | ImageConverterHelper.ByteArrayToBitmap(new byte[0])); 46 | } 47 | 48 | [Fact] 49 | public void ByteArrayToBitmap_Should_Handle_ValidObject() 50 | { 51 | var bytes = ImageConverterHelper.BitmapToByteArray(Resources.ClearWindowContent_16x_Gray, ImageFormat.Png); 52 | var ret = ImageConverterHelper.ByteArrayToBitmap(bytes); 53 | 54 | Assert.NotNull(ret); 55 | Assert.Equal(16, ret.Width); 56 | Assert.Equal(16, ret.Height); 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen.Test/VS/Commands/ClearDocumentsHistoryCommandTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Moq; 3 | using VSDocumentReopen.Infrastructure.HistoryCommands; 4 | using VSDocumentReopen.VS.Commands; 5 | using Xunit; 6 | using Task = System.Threading.Tasks.Task; 7 | 8 | namespace VSDocumentReopen.Test.VS.Commands 9 | { 10 | public class ClearDocumentsHistoryCommandTest : VisualStudioCommandTestBase 11 | { 12 | private readonly Mock _historyCommandMock; 13 | 14 | private readonly ClearDocumentsHistoryCommand _clearDocumentsHistoryCommand; 15 | 16 | public ClearDocumentsHistoryCommandTest() 17 | { 18 | _historyCommandMock = new Mock(); 19 | _historyCommandMock.Setup(s => s.Execute()); 20 | 21 | Task.Run(() => ClearDocumentsHistoryCommand.InitializeAsync(_asyncPackageMock.Object, _historyCommandMock.Object)).Wait(); 22 | _clearDocumentsHistoryCommand = ClearDocumentsHistoryCommand.Instance; 23 | } 24 | 25 | [Fact] 26 | public void CommandId_ShouldBe() 27 | { 28 | Assert.Equal(0x0104, ClearDocumentsHistoryCommand.CommandId); 29 | } 30 | 31 | [Fact] 32 | public async Task ItShould_Handle_Null_AsyncPackageAsync() 33 | { 34 | await Assert.ThrowsAsync(() => 35 | { 36 | return Task.Run(() => ClearDocumentsHistoryCommand.InitializeAsync(null, _historyCommandMock.Object)); 37 | }); 38 | } 39 | 40 | [Fact] 41 | public async void ItShould_Handle_Null_IHistoryCommand() 42 | { 43 | await Assert.ThrowsAsync(() => 44 | { 45 | return Task.Run(() => ClearDocumentsHistoryCommand.InitializeAsync(_asyncPackageMock.Object, null)); 46 | }); 47 | } 48 | 49 | [Fact] 50 | public void ItShould_Execute_Command() 51 | { 52 | InvokeCommand(_clearDocumentsHistoryCommand); 53 | 54 | _historyCommandMock.Verify(v => v.Execute(), Times.Once); 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen.Test/VS/Commands/RemoveClosedDocumentsCommandTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Moq; 3 | using VSDocumentReopen.Infrastructure.HistoryCommands; 4 | using VSDocumentReopen.VS.Commands; 5 | using Xunit; 6 | using Task = System.Threading.Tasks.Task; 7 | 8 | namespace VSDocumentReopen.Test.VS.Commands 9 | { 10 | public class RemoveClosedDocumentsCommandTest : VisualStudioCommandTestBase 11 | { 12 | private readonly Mock _historyCommandMock; 13 | 14 | private readonly RemoveClosedDocumentsCommand _removeClosedDocumentsCommand; 15 | 16 | public RemoveClosedDocumentsCommandTest() 17 | { 18 | _historyCommandMock = new Mock(); 19 | _historyCommandMock.Setup(s => s.Execute()); 20 | 21 | Task.Run(() => RemoveClosedDocumentsCommand.InitializeAsync(_asyncPackageMock.Object, 22 | _historyCommandMock.Object)).Wait(); 23 | 24 | _removeClosedDocumentsCommand = RemoveClosedDocumentsCommand.Instance; 25 | } 26 | 27 | [Fact] 28 | public void CommandId_ShouldBe() 29 | { 30 | Assert.Equal(0x0102, RemoveClosedDocumentsCommand.CommandId); 31 | } 32 | 33 | [Fact] 34 | public async Task ItShould_Handle_Null_AsyncPackageAsync() 35 | { 36 | await Assert.ThrowsAsync(() => 37 | { 38 | return Task.Run(() => RemoveClosedDocumentsCommand.InitializeAsync(null, _historyCommandMock.Object)); 39 | }); 40 | } 41 | 42 | [Fact] 43 | public async void ItShould_Handle_Null_IDocumentHistoryQueries() 44 | { 45 | await Assert.ThrowsAsync(() => 46 | { 47 | return Task.Run(() => RemoveClosedDocumentsCommand.InitializeAsync(_asyncPackageMock.Object, null)); 48 | }); 49 | } 50 | 51 | [Fact] 52 | public void ItShould_Execute_Command() 53 | { 54 | InvokeCommand(_removeClosedDocumentsCommand); 55 | 56 | _historyCommandMock.Verify(v => v.Execute(), Times.Once); 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen.Test/VS/Commands/ReopenClosedDocumentsCommandTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Moq; 3 | using VSDocumentReopen.Infrastructure.HistoryCommands; 4 | using VSDocumentReopen.VS.Commands; 5 | using Xunit; 6 | using Task = System.Threading.Tasks.Task; 7 | 8 | namespace VSDocumentReopen.Test.VS.Commands 9 | { 10 | public class ReopenClosedDocumentsCommandTest : VisualStudioCommandTestBase 11 | { 12 | private readonly Mock _historyCommandMock; 13 | 14 | private readonly ReopenClosedDocumentsCommand _reopenClosedDocumentsCommand; 15 | 16 | public ReopenClosedDocumentsCommandTest() 17 | { 18 | _historyCommandMock = new Mock(); 19 | _historyCommandMock.Setup(s => s.Execute()); 20 | 21 | Task.Run(() => ReopenClosedDocumentsCommand.InitializeAsync(_asyncPackageMock.Object, 22 | _historyCommandMock.Object)).Wait(); 23 | 24 | _reopenClosedDocumentsCommand = ReopenClosedDocumentsCommand.Instance; 25 | } 26 | 27 | [Fact] 28 | public void CommandId_ShouldBe() 29 | { 30 | Assert.Equal(0x0101, ReopenClosedDocumentsCommand.CommandId); 31 | } 32 | 33 | [Fact] 34 | public async Task ItShould_Handle_Null_AsyncPackageAsync() 35 | { 36 | await Assert.ThrowsAsync(() => 37 | { 38 | return Task.Run(() => ReopenClosedDocumentsCommand.InitializeAsync(null, _historyCommandMock.Object)); 39 | }); 40 | } 41 | 42 | [Fact] 43 | public async void ItShould_Handle_Null_IDocumentHistoryQueries() 44 | { 45 | await Assert.ThrowsAsync(() => 46 | { 47 | return Task.Run(() => ReopenClosedDocumentsCommand.InitializeAsync(_asyncPackageMock.Object, null)); 48 | }); 49 | } 50 | 51 | [Fact] 52 | public void ItShould_Execute_Command() 53 | { 54 | InvokeCommand(_reopenClosedDocumentsCommand); 55 | 56 | _historyCommandMock.Verify(v => v.Execute(), Times.Once); 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.28315.86 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "3rd Party", "3rd Party", "{5A1E6234-E717-4984-97BB-E27DA25704C7}" 7 | ProjectSection(SolutionItems) = preProject 8 | 3rd Party\SearchTextBox.dll = 3rd Party\SearchTextBox.dll 9 | EndProjectSection 10 | EndProject 11 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VSDocumentReopen", "VSDocumentReopen\VSDocumentReopen.csproj", "{AFDB6848-0AE2-4BA6-A1A4-3DDE43A009B8}" 12 | EndProject 13 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VSDocumentReopen.Test", "VSDocumentReopen.Test\VSDocumentReopen.Test.csproj", "{A828CF9B-34DF-497F-85AC-A3D3E3F18B78}" 14 | EndProject 15 | Global 16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 17 | Debug|Any CPU = Debug|Any CPU 18 | Release|Any CPU = Release|Any CPU 19 | EndGlobalSection 20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 21 | {AFDB6848-0AE2-4BA6-A1A4-3DDE43A009B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 22 | {AFDB6848-0AE2-4BA6-A1A4-3DDE43A009B8}.Debug|Any CPU.Build.0 = Debug|Any CPU 23 | {AFDB6848-0AE2-4BA6-A1A4-3DDE43A009B8}.Release|Any CPU.ActiveCfg = Release|Any CPU 24 | {AFDB6848-0AE2-4BA6-A1A4-3DDE43A009B8}.Release|Any CPU.Build.0 = Release|Any CPU 25 | {A828CF9B-34DF-497F-85AC-A3D3E3F18B78}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {A828CF9B-34DF-497F-85AC-A3D3E3F18B78}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {A828CF9B-34DF-497F-85AC-A3D3E3F18B78}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {A828CF9B-34DF-497F-85AC-A3D3E3F18B78}.Release|Any CPU.Build.0 = Release|Any CPU 29 | EndGlobalSection 30 | GlobalSection(SolutionProperties) = preSolution 31 | HideSolutionNode = FALSE 32 | EndGlobalSection 33 | GlobalSection(ExtensibilityGlobals) = postSolution 34 | SolutionGuid = {FF506DBB-7DAC-49FB-A4D0-C5AA6B791671} 35 | EndGlobalSection 36 | EndGlobal 37 | -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen.Test/VS/ToolWindows/IconHandling/ButtonStates/ImageButtonStateTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.Windows.Controls; 4 | using VSDocumentReopen.VS.ToolWindows.IconHandling.ButtonStates; 5 | using Xunit; 6 | 7 | namespace VSDocumentReopen.Test.VS.ToolWindows.IconHandling.ButtonStates 8 | { 9 | public abstract class ImageButtonStateTest where T : ImageButtonState 10 | { 11 | protected readonly ImageButtonState _imageButtonState; 12 | protected readonly Image _enableImage; 13 | protected readonly Image _disableImage; 14 | protected readonly Button _button; 15 | 16 | protected ImageButtonStateTest() 17 | { 18 | _button = new Button(); 19 | _enableImage = new Image(); 20 | _disableImage = new Image(); 21 | 22 | var genericType = typeof(T); 23 | 24 | _imageButtonState = (ImageButtonState)Activator.CreateInstance(genericType, _button, _enableImage, _disableImage); 25 | } 26 | 27 | protected Button GetButton(ImageButtonState imageButtonState) 28 | { 29 | var member = GetMemberValue(imageButtonState, "_button") as Button; 30 | return member; 31 | } 32 | protected Image GetEnableImage(ImageButtonState imageButtonState) 33 | { 34 | var member = GetMemberValue(imageButtonState, "_enabledImage") as Image; 35 | return member; 36 | } 37 | protected Image GetDisableImage(ImageButtonState imageButtonState) 38 | { 39 | var member = GetMemberValue(imageButtonState, "_disabledImage") as Image; 40 | return member; 41 | } 42 | 43 | private object GetMemberValue(ImageButtonState imageButtonState, string name) 44 | { 45 | return typeof(ImageButtonState).GetField(name, BindingFlags.NonPublic|BindingFlags.Instance).GetValue(imageButtonState); 46 | } 47 | 48 | [StaFact] 49 | public void ItShould_Set_Values() 50 | { 51 | Assert.Same(_button, GetButton(_imageButtonState)); 52 | Assert.Same(_enableImage, GetEnableImage(_imageButtonState)); 53 | Assert.Same(_disableImage, GetDisableImage(_imageButtonState)); 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen.Test/Infrastructure/Helpers/PathFormatterTest.cs: -------------------------------------------------------------------------------- 1 | using VSDocumentReopen.Infrastructure.Helpers; 2 | using Xunit; 3 | 4 | namespace VSDocumentReopen.Test.Infrastructure.Helpers 5 | { 6 | public class PathFormatterTest 7 | { 8 | [Theory] 9 | [InlineData(null, 50)] 10 | [InlineData("", 50)] 11 | [InlineData(" ", 50)] 12 | public void ItShouldHandleInvalidInput(string path, int limit) 13 | { 14 | var ret = PathFormatter.ShrinkPath(path, limit); 15 | 16 | Assert.Equal(string.Empty, ret); 17 | } 18 | 19 | [Theory] 20 | [InlineData("test", 50)] 21 | [InlineData("path", 5)] 22 | [InlineData("123", 3)] 23 | public void ItShouldHandleShorterPathThenTheLimit(string path, int limit) 24 | { 25 | var ret = PathFormatter.ShrinkPath(path, limit); 26 | 27 | Assert.Equal(path, ret); 28 | } 29 | 30 | [Theory] 31 | [InlineData("d:/test/subfolder/1/file1.txt", 20, "___")] 32 | [InlineData("d:/test/subfolder/1/file1.txt", 20, "??")] 33 | [InlineData("d:/test/subfolder/1/file1.txt", 20, "..")] 34 | public void ItShouldContainSpacer(string path, int limit, string spacer) 35 | { 36 | var ret = PathFormatter.ShrinkPath(path, limit, spacer); 37 | 38 | Assert.False(string.IsNullOrWhiteSpace(ret)); 39 | Assert.Contains(spacer, ret); 40 | } 41 | 42 | [Theory] 43 | [InlineData("d:/test/subfolder/1/file1.txt", 50)] 44 | [InlineData("d:/test/subfolder/2/file1.txt", 20)] 45 | [InlineData("d:/test/subfolder/3/a.txt", 10)] 46 | [InlineData("d:/test/subfolder/3/a.txt", 200)] 47 | [InlineData("d:/test/subfolder/1/subfolder/2/subfolder/3/subfolder/4/subfolder/5/subfolder/6/subfolder/7/subfolder/file1.txt", 20)] 48 | [InlineData("d:/test/subfolder/1/subfolder/2/subfolder/3/subfolder/4/subfolder/5/subfolder/6/subfolder/7/subfolder/file1.txt", 100)] 49 | public void ItShouldNotReachLimit(string path, int limit) 50 | { 51 | var ret = PathFormatter.ShrinkPath(path, limit); 52 | 53 | Assert.False(string.IsNullOrWhiteSpace(ret)); 54 | Assert.True(ret.Length <= limit, $"Result: \"{ret}\" length of result: {ret.Length} but should not more than: {limit}."); 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/VS/Commands/RemoveClosedDocumentsCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.Design; 3 | using Microsoft.VisualStudio.Shell; 4 | using VSDocumentReopen.Infrastructure.HistoryCommands; 5 | using VSDocumentReopen.Infrastructure.Logging; 6 | using Task = System.Threading.Tasks.Task; 7 | 8 | namespace VSDocumentReopen.VS.Commands 9 | { 10 | public sealed class RemoveClosedDocumentsCommand 11 | { 12 | /// 13 | /// Command ID. 14 | /// 15 | public const int CommandId = 0x0102; 16 | 17 | /// 18 | /// Command menu group (command set GUID). 19 | /// 20 | public static readonly Guid CommandSet = new Guid("d968b4de-3a69-4eb1-b676-942055da9dfd"); 21 | 22 | private readonly AsyncPackage _package; 23 | private readonly IHistoryCommand _historyCommand; 24 | 25 | private RemoveClosedDocumentsCommand(AsyncPackage package, OleMenuCommandService commandService, IHistoryCommand historyCommand) 26 | { 27 | _package = package ?? throw new ArgumentNullException(nameof(package)); 28 | commandService = commandService ?? throw new ArgumentNullException(nameof(commandService)); 29 | _historyCommand = historyCommand ?? throw new ArgumentNullException(nameof(historyCommand)); 30 | 31 | var menuCommandId = new CommandID(CommandSet, CommandId); 32 | var menuItem = new MenuCommand(Execute, menuCommandId); 33 | commandService.AddCommand(menuItem); 34 | } 35 | 36 | public static RemoveClosedDocumentsCommand Instance 37 | { 38 | get; 39 | private set; 40 | } 41 | 42 | public static async Task InitializeAsync(AsyncPackage package, IHistoryCommand historyCommand) 43 | { 44 | var commandService = package == null 45 | ? null 46 | : await package.GetServiceAsync(typeof(IMenuCommandService)) as OleMenuCommandService; 47 | Instance = new RemoveClosedDocumentsCommand(package, commandService, historyCommand); 48 | } 49 | 50 | private void Execute(object sender, EventArgs e) 51 | { 52 | _historyCommand.Execute(); 53 | LoggerContext.Current.Logger.Info($"VS Command: {nameof(RemoveClosedDocumentsCommand)} was executed with {_historyCommand.GetType()}"); 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/VS/Commands/ReopenClosedDocumentsCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.Design; 3 | using Microsoft.VisualStudio.Shell; 4 | using VSDocumentReopen.Infrastructure.HistoryCommands; 5 | using VSDocumentReopen.Infrastructure.Logging; 6 | using Task = System.Threading.Tasks.Task; 7 | 8 | namespace VSDocumentReopen.VS.Commands 9 | { 10 | public sealed class ReopenClosedDocumentsCommand 11 | { 12 | /// 13 | /// Command ID. 14 | /// 15 | public const int CommandId = 0x0101; 16 | 17 | /// 18 | /// Command menu group (command set GUID). 19 | /// 20 | public static readonly Guid CommandSet = new Guid("d968b4de-3a69-4eb1-b676-942055da9dfd"); 21 | 22 | private readonly AsyncPackage _package; 23 | private readonly IHistoryCommand _historyCommand; 24 | 25 | private ReopenClosedDocumentsCommand(AsyncPackage package, OleMenuCommandService commandService, IHistoryCommand historyCommand) 26 | { 27 | _package = package ?? throw new ArgumentNullException(nameof(package)); 28 | commandService = commandService ?? throw new ArgumentNullException(nameof(commandService)); 29 | _historyCommand = historyCommand ?? throw new ArgumentNullException(nameof(historyCommand)); 30 | 31 | var menuCommandId = new CommandID(CommandSet, CommandId); 32 | var menuItem = new MenuCommand(Execute, menuCommandId); 33 | commandService.AddCommand(menuItem); 34 | } 35 | 36 | public static ReopenClosedDocumentsCommand Instance 37 | { 38 | get; 39 | private set; 40 | } 41 | 42 | public static async Task InitializeAsync(AsyncPackage package, IHistoryCommand historyCommand) 43 | { 44 | var commandService = package == null 45 | ? null 46 | : await package.GetServiceAsync(typeof(IMenuCommandService)) as OleMenuCommandService; 47 | Instance = new ReopenClosedDocumentsCommand(package, commandService, historyCommand); 48 | } 49 | 50 | private void Execute(object sender, EventArgs e) 51 | { 52 | _historyCommand.Execute(); 53 | LoggerContext.Current.Logger.Info($"VS Command: {nameof(ReopenClosedDocumentsCommand)} was executed with {_historyCommand.GetType()}"); 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/VS/Commands/ClearDocumentsHistoryCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.Design; 3 | using Microsoft.VisualStudio.Shell; 4 | using VSDocumentReopen.Infrastructure.HistoryCommands; 5 | using VSDocumentReopen.Infrastructure.Logging; 6 | using Task = System.Threading.Tasks.Task; 7 | 8 | namespace VSDocumentReopen.VS.Commands 9 | { 10 | public sealed class ClearDocumentsHistoryCommand 11 | { 12 | /// 13 | /// Command ID. 14 | /// 15 | public const int CommandId = 0x0104; 16 | 17 | /// 18 | /// Command menu group (command set GUID). 19 | /// 20 | public static readonly Guid CommandSet = new Guid("d968b4de-3a69-4eb1-b676-942055da9dfd"); 21 | 22 | private readonly AsyncPackage _package; 23 | private readonly IHistoryCommand _clearHistoryCommand; 24 | 25 | private ClearDocumentsHistoryCommand(AsyncPackage package, OleMenuCommandService commandService, IHistoryCommand clearHistoryCommand) 26 | { 27 | _package = package ?? throw new ArgumentNullException(nameof(package)); 28 | _clearHistoryCommand = clearHistoryCommand ?? throw new ArgumentNullException(nameof(clearHistoryCommand)); 29 | commandService = commandService ?? throw new ArgumentNullException(nameof(commandService)); 30 | 31 | var menuCommandId = new CommandID(CommandSet, CommandId); 32 | var menuItem = new MenuCommand(Execute, menuCommandId); 33 | commandService.AddCommand(menuItem); 34 | } 35 | 36 | public static ClearDocumentsHistoryCommand Instance 37 | { 38 | get; 39 | private set; 40 | } 41 | 42 | public static async Task InitializeAsync(AsyncPackage package, IHistoryCommand clearHistoryCommand) 43 | { 44 | var commandService = package == null 45 | ? null 46 | : await package.GetServiceAsync(typeof(IMenuCommandService)) as OleMenuCommandService; 47 | Instance = new ClearDocumentsHistoryCommand(package, commandService, clearHistoryCommand); 48 | } 49 | 50 | private void Execute(object sender, EventArgs e) 51 | { 52 | _clearHistoryCommand.Execute(); 53 | LoggerContext.Current.Logger.Info($"VS Command: {nameof(ClearDocumentsHistoryCommand)} was executed with {_clearHistoryCommand.GetType()}"); 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen.Test/Infrastructure/HistoryCommands/RemoveLastCommandTest.cs: -------------------------------------------------------------------------------- 1 | using Moq; 2 | using VSDocumentReopen.Domain.Documents; 3 | using VSDocumentReopen.Infrastructure.Document.Commands; 4 | using VSDocumentReopen.Infrastructure.Document.Factories; 5 | using VSDocumentReopen.Infrastructure.Document.Tracking; 6 | using VSDocumentReopen.Infrastructure.HistoryCommands; 7 | using Xunit; 8 | 9 | namespace VSDocumentReopen.Test.Infrastructure.HistoryCommands 10 | { 11 | public class RemoveLastCommandTest 12 | { 13 | private readonly RemoveLastCommand _removeLastCommand; 14 | private readonly Mock _documentHistoryCommandsMock; 15 | private readonly Mock _documentCommandFactoryMock; 16 | private readonly Mock _documentCommandMock; 17 | 18 | public RemoveLastCommandTest() 19 | { 20 | _documentHistoryCommandsMock = new Mock(); 21 | _documentHistoryCommandsMock.Setup(s => s.RemoveLast()).Returns(NullDocument.Instance); 22 | 23 | _documentCommandMock = new Mock(); 24 | 25 | _documentCommandFactoryMock = new Mock(); 26 | _documentCommandFactoryMock.Setup(s => s.CreateCommand(It.IsAny())) 27 | .Returns(_documentCommandMock.Object); 28 | 29 | _removeLastCommand = new RemoveLastCommand(_documentHistoryCommandsMock.Object, _documentCommandFactoryMock.Object); 30 | } 31 | 32 | [Fact] 33 | public void ItShould_Handle_Nulls() 34 | { 35 | var command = new RemoveLastCommand(null, null); 36 | 37 | command.Execute(); 38 | 39 | _documentHistoryCommandsMock.Verify(v => v.RemoveLast(), Times.Never); 40 | _documentCommandFactoryMock.Verify(v => v.CreateCommand(It.IsAny()), Times.Never); 41 | _documentCommandMock.Verify(v => v.Execute(), Times.Never); 42 | } 43 | 44 | [Fact] 45 | public void ItShould_Remove_Last_Document() 46 | { 47 | _removeLastCommand.Execute(); 48 | 49 | _documentHistoryCommandsMock.Verify(v => v.RemoveLast(), Times.Once); 50 | _documentCommandFactoryMock.Verify(v => v.CreateCommand(It.Is(p => p == NullDocument.Instance)), Times.Once); 51 | _documentCommandMock.Verify(v => v.Execute(), Times.Once); 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen.Test/Infrastructure/Document/Commands/ReopenDocumentCommandTest.cs: -------------------------------------------------------------------------------- 1 | using EnvDTE; 2 | using Moq; 3 | using VSDocumentReopen.Domain.Documents; 4 | using VSDocumentReopen.Infrastructure.Document.Commands; 5 | using Xunit; 6 | 7 | namespace VSDocumentReopen.Test.Infrastructure.Document.Commands 8 | { 9 | public class ReopenDocumentCommandTest 10 | { 11 | private readonly Mock<_DTE> _dteMock; 12 | private readonly Mock _itemOperationsMock; 13 | private readonly Mock _closedDecumentMock; 14 | 15 | public ReopenDocumentCommandTest() 16 | { 17 | _itemOperationsMock = new Mock(); 18 | _itemOperationsMock.Setup(s => s.OpenFile(It.IsAny(), It.IsAny())); 19 | 20 | _dteMock = new Mock<_DTE>(); 21 | _dteMock.SetupGet(g => g.ItemOperations).Returns(_itemOperationsMock.Object); 22 | 23 | _closedDecumentMock = new Mock(); 24 | } 25 | 26 | [Fact] 27 | public void ItShould_Handle_Null_DTE() 28 | { 29 | _closedDecumentMock.Setup(s => s.IsValid()).Returns(true); 30 | 31 | var command = new ReopenDocumentCommand(null, _closedDecumentMock.Object); 32 | 33 | command.Execute(); 34 | 35 | Assert.NotNull(command); 36 | _itemOperationsMock.Verify(v => v.OpenFile(It.IsAny(), It.IsAny()), Times.Never); 37 | 38 | } 39 | 40 | [Fact] 41 | public void ItShould_Handle_Invalid_Document() 42 | { 43 | _closedDecumentMock.Setup(s => s.IsValid()).Returns(false); 44 | 45 | var command = new ReopenDocumentCommand(_dteMock.Object, _closedDecumentMock.Object); 46 | 47 | command.Execute(); 48 | 49 | Assert.NotNull(command); 50 | _itemOperationsMock.Verify(v => v.OpenFile(It.IsAny(), It.IsAny()), Times.Never); 51 | } 52 | 53 | [Fact] 54 | public void ItShould_Open_Valid_Document() 55 | { 56 | _closedDecumentMock.Setup(s => s.IsValid()).Returns(true); 57 | _closedDecumentMock.SetupGet(s => s.FullName).Returns("doc.cs"); 58 | _closedDecumentMock.Setup(s => s.Kind).Returns("kind"); 59 | 60 | var command = new ReopenDocumentCommand(_dteMock.Object, _closedDecumentMock.Object); 61 | 62 | command.Execute(); 63 | 64 | Assert.NotNull(command); 65 | _itemOperationsMock.Verify(s => s.OpenFile(It.Is(p => p == "doc.cs"), It.Is(p => p == "kind")), Times.Once); 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen.Test/Infrastructure/Logging/NullLoggerTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using VSDocumentReopen.Infrastructure.Logging; 5 | using Xunit; 6 | 7 | namespace VSDocumentReopen.Test.Infrastructure.Logging 8 | { 9 | public class NullLoggerTest 10 | { 11 | private class MyListenerThatDoesNotShowDialogOnFail : TraceListener 12 | { 13 | public int WriteCount { get; private set; } = 0; 14 | public int WriteLineCount { get; private set; } = 0; 15 | 16 | public override void Write(string message) => WriteCount++; 17 | 18 | public override void WriteLine(string message) => WriteLineCount++; 19 | } 20 | 21 | [Fact] 22 | public void ItShould_Be_Singleton() 23 | { 24 | var logger1 = NullLogger.Instance; 25 | var logger2 = NullLogger.Instance; 26 | 27 | Assert.Same(logger1, logger2); 28 | } 29 | 30 | #if DEBUG 31 | [Fact] 32 | public void ItShould_Call_Debug_WriteLine() 33 | { 34 | var methodsWithString = new List>() 35 | { 36 | new Action((msg) => NullLogger.Instance.Critical(msg)), 37 | new Action((msg) => NullLogger.Instance.Error(msg)), 38 | new Action((msg) => NullLogger.Instance.Info(msg)), 39 | new Action((msg) => NullLogger.Instance.Trace(msg)), 40 | new Action((msg) => NullLogger.Instance.Warning(msg)) 41 | }; 42 | 43 | var methodsWithException = new List>() 44 | { 45 | new Action((msg, ex) => NullLogger.Instance.Critical(msg, ex)), 46 | new Action((msg, ex) => NullLogger.Instance.Error(msg, ex)), 47 | new Action((msg, ex) => NullLogger.Instance.Warning(msg, ex)) 48 | }; 49 | 50 | foreach (var action in methodsWithString) 51 | { 52 | var listener = new MyListenerThatDoesNotShowDialogOnFail(); 53 | Debug.Listeners.Clear(); 54 | Debug.Listeners.Add(listener); 55 | 56 | action("test"); 57 | 58 | Assert.Equal(1, listener.WriteLineCount); 59 | } 60 | 61 | foreach (var action in methodsWithException) 62 | { 63 | var listener = new MyListenerThatDoesNotShowDialogOnFail(); 64 | Debug.Listeners.Clear(); 65 | Debug.Listeners.Add(listener); 66 | 67 | action("test", new Exception("ex msg")); 68 | 69 | Assert.Equal(1, listener.WriteLineCount); 70 | } 71 | } 72 | #endif 73 | } 74 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/VS/Commands/ShowDocumentsHIstoryCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.Design; 3 | using Microsoft.VisualStudio.Shell; 4 | using Microsoft.VisualStudio.Shell.Interop; 5 | using VSDocumentReopen.Infrastructure.Logging; 6 | using VSDocumentReopen.VS.ToolWindows; 7 | using Task = System.Threading.Tasks.Task; 8 | 9 | namespace VSDocumentReopen.VS.Commands 10 | { 11 | public sealed class ShowDocumentsHIstoryCommand 12 | { 13 | /// 14 | /// Command ID. 15 | /// 16 | public const int CommandId = 0x0110; 17 | 18 | /// 19 | /// Command menu group (command set GUID). 20 | /// 21 | public static readonly Guid CommandSet = new Guid("d968b4de-3a69-4eb1-b676-942055da9dfd"); 22 | 23 | private readonly AsyncPackage _package; 24 | 25 | private ShowDocumentsHIstoryCommand(AsyncPackage package, OleMenuCommandService commandService) 26 | { 27 | _package = package ?? throw new ArgumentNullException(nameof(package)); 28 | commandService = commandService ?? throw new ArgumentNullException(nameof(commandService)); 29 | 30 | var menuCommandId = new CommandID(CommandSet, CommandId); 31 | var menuItem = new MenuCommand(Execute, menuCommandId); 32 | commandService.AddCommand(menuItem); 33 | } 34 | 35 | public static ShowDocumentsHIstoryCommand Instance 36 | { 37 | get; 38 | private set; 39 | } 40 | 41 | public static async Task InitializeAsync(AsyncPackage package) 42 | { 43 | var commandService = package == null 44 | ? null 45 | : await package.GetServiceAsync(typeof(IMenuCommandService)) as OleMenuCommandService; 46 | Instance = new ShowDocumentsHIstoryCommand(package, commandService); 47 | } 48 | 49 | private void Execute(object sender, EventArgs e) 50 | { 51 | ToolWindowPane window = _package.FindToolWindow(typeof(ClosedDocumentsHistory), 0, true); 52 | if ((null == window) || (null == window.Frame)) 53 | { 54 | LoggerContext.Current.Logger.Error($"Command failed: {nameof(ShowDocumentsHIstoryCommand)} Cannot create tool window"); 55 | throw new NotSupportedException("Cannot create tool window"); 56 | } 57 | 58 | IVsWindowFrame windowFrame = (IVsWindowFrame)window.Frame; 59 | Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(windowFrame.Show()); 60 | 61 | LoggerContext.Current.Logger.Info($"VS Command: {nameof(ShowDocumentsHIstoryCommand)} was executed"); 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/VS/ToolWindows/ClosedDocumentsHistory.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | using Microsoft.VisualStudio.Shell; 3 | using VSDocumentReopen.Infrastructure; 4 | using VSDocumentReopen.Infrastructure.Document.Tracking; 5 | using VSDocumentReopen.Infrastructure.FileIcons; 6 | using VSDocumentReopen.Infrastructure.HistoryCommands; 7 | using VSDocumentReopen.Infrastructure.Repositories; 8 | using Task = System.Threading.Tasks.Task; 9 | 10 | namespace VSDocumentReopen.VS.ToolWindows 11 | { 12 | /// 13 | /// This class implements the tool window exposed by this package and hosts a user control. 14 | /// 15 | /// 16 | /// In Visual Studio tool windows are composed of a frame (implemented by the shell) and a pane, 17 | /// usually implemented by the package implementer. 18 | /// 19 | /// This class derives from the ToolWindowPane class provided from the MPF in order to use its 20 | /// implementation of the IVsUIElementPane interface. 21 | /// 22 | /// 23 | [Guid("203513ef-1922-433a-ba3e-1801fb4e9894")] 24 | public class ClosedDocumentsHistory : ToolWindowPane 25 | { 26 | public static ClosedDocumentsHistoryControl ContentWindow { get; private set; } 27 | 28 | /// 29 | /// Initializes a new instance of the class. 30 | /// 31 | public ClosedDocumentsHistory() 32 | : base(null) 33 | { 34 | Caption = "Closed Documents History"; 35 | 36 | // This is the user control hosted by the tool window; Note that, even if this class implements IDisposable, 37 | // we are not calling Dispose on this object. This is because ToolWindowPane calls Dispose on 38 | // the object returned by the Content property. 39 | 40 | Content = ContentWindow; 41 | } 42 | 43 | public static async Task InitializeAsync(IDocumentHistoryQueries documentHistoryQueries, 44 | IHistoryCommand reopenLastClosdCommand, 45 | IHistoryCommandFactory reopenSomeDocumentsCommandFactory, 46 | IHistoryCommandFactory removeSomeDocumentsCommandFactory, 47 | IHistoryCommand clearHistoryCommand, 48 | IFileExtensionIconResolver fileExtensionIconResolver, 49 | IHistoryToolWindowRepositoryFactory historyToolWindowRepositoryFactory) 50 | { 51 | ContentWindow = new ClosedDocumentsHistoryControl(documentHistoryQueries, 52 | reopenLastClosdCommand, 53 | reopenSomeDocumentsCommandFactory, 54 | removeSomeDocumentsCommandFactory, 55 | clearHistoryCommand, 56 | fileExtensionIconResolver, 57 | historyToolWindowRepositoryFactory); 58 | 59 | await Task.CompletedTask; 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen.Test/Infrastructure/FileIcons/CachedFileExtensionIconResolverTest.cs: -------------------------------------------------------------------------------- 1 | using Moq; 2 | using System.Drawing; 3 | using VSDocumentReopen.Domain.Documents; 4 | using VSDocumentReopen.Infrastructure.FileIcons; 5 | using Xunit; 6 | 7 | namespace VSDocumentReopen.Test.Infrastructure.FileIcons 8 | { 9 | public class CachedFileExtensionIconResolverTest 10 | { 11 | private readonly CachedFileExtensionIconResolver _fileExtensionIconResolver; 12 | private readonly Mock _fileExtensionResolverMock; 13 | 14 | public CachedFileExtensionIconResolverTest() 15 | { 16 | _fileExtensionResolverMock = new Mock(); 17 | 18 | _fileExtensionIconResolver = new CachedFileExtensionIconResolver(_fileExtensionResolverMock.Object); 19 | } 20 | 21 | [Fact] 22 | public void ItShould_Never_ReturnNull() 23 | { 24 | _fileExtensionResolverMock.Setup(s => s.GetIcon(It.IsAny())) 25 | .Returns(() => null); 26 | 27 | var icon = _fileExtensionIconResolver.GetIcon(null); 28 | var icon2 = _fileExtensionIconResolver.GetIcon(NullDocument.Instance); 29 | var icon3 = _fileExtensionIconResolver.GetIcon(new ClosedDocument()); 30 | 31 | Assert.NotNull(icon); 32 | Assert.NotNull(icon2); 33 | Assert.NotNull(icon3); 34 | 35 | _fileExtensionResolverMock.Verify(v => v.GetIcon(It.IsAny()), Times.Never); 36 | } 37 | 38 | [Fact] 39 | public void ItShould_Handle_InvalidFileIcon() 40 | { 41 | _fileExtensionResolverMock.Setup(s => s.GetIcon(It.IsAny())) 42 | .Returns(() => null); 43 | 44 | var icon = _fileExtensionIconResolver.GetIcon(new ClosedDocument() 45 | { 46 | FullName = "c/test.cs" 47 | }); 48 | 49 | Assert.NotNull(icon); 50 | 51 | _fileExtensionResolverMock.Verify(v => v.GetIcon(It.IsAny()), Times.Once); 52 | } 53 | 54 | [Fact] 55 | public void ItShould_Resolve_IconFromCache() 56 | { 57 | var ico = Icon.FromHandle(Resources.FileError_16x.GetHbitmap()); 58 | _fileExtensionResolverMock.Setup(s => s.GetIcon(It.IsAny())) 59 | .Returns(() => ico); 60 | 61 | var icon = _fileExtensionIconResolver.GetIcon(new ClosedDocument() 62 | { 63 | FullName = "c/test.xml" 64 | }); 65 | var icon2 = _fileExtensionIconResolver.GetIcon(new ClosedDocument() 66 | { 67 | FullName = "c/doc.xml" 68 | }); 69 | 70 | Assert.NotNull(icon); 71 | Assert.NotNull(icon2); 72 | Assert.Same(ico, icon); 73 | Assert.Same(ico, icon2); 74 | 75 | _fileExtensionResolverMock.Verify(v => v.GetIcon(It.IsAny()), Times.Once); 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen.Test/Infrastructure/FileIcons/VisualStudioFileExtensionIconResolverTest.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using VSDocumentReopen.Infrastructure.FileIcons; 3 | using Moq; 4 | using Microsoft.VisualStudio.Shell.Interop; 5 | using VSDocumentReopen.Domain.Documents; 6 | using Xunit; 7 | using Microsoft.Internal.VisualStudio.PlatformUI; 8 | 9 | namespace VSDocumentReopen.Test.Infrastructure.FileIcons 10 | { 11 | public class VisualStudioFileExtensionIconResolverTest 12 | { 13 | private readonly VisualStudioFileExtensionIconResolver _fileExtensionIconResolver; 14 | private readonly Mock _iVsImageService2Mock; 15 | 16 | public VisualStudioFileExtensionIconResolverTest() 17 | { 18 | _iVsImageService2Mock = new Mock(); 19 | _iVsImageService2Mock.Setup(s => s.GetIconForFile(It.Is(o => string.IsNullOrWhiteSpace(o)), It.IsAny<__VSUIDATAFORMAT>())) 20 | .Returns(() => null); 21 | 22 | _fileExtensionIconResolver = new VisualStudioFileExtensionIconResolver(_iVsImageService2Mock.Object); 23 | } 24 | 25 | [Fact] 26 | public void ItShouldHandle_Null() 27 | { 28 | var icon = _fileExtensionIconResolver.GetIcon(null); 29 | 30 | Assert.Null(icon); 31 | _iVsImageService2Mock.Verify(v => v.GetIconForFile(It.IsAny(), It.IsAny<__VSUIDATAFORMAT>()), Times.Never); 32 | } 33 | 34 | [Fact] 35 | public void ItShouldHandle_NullDocument() 36 | { 37 | var icon = _fileExtensionIconResolver.GetIcon(NullDocument.Instance); 38 | 39 | Assert.Null(icon); 40 | _iVsImageService2Mock.Verify(v => v.GetIconForFile(It.IsAny(), It.IsAny<__VSUIDATAFORMAT>()), Times.Never); 41 | } 42 | 43 | [Fact] 44 | public void ItShouldHandle_DocumentDoesNotExist() 45 | { 46 | var icon = _fileExtensionIconResolver.GetIcon(new ClosedDocument() 47 | { 48 | Name = null 49 | }); 50 | 51 | Assert.Null(icon); 52 | _iVsImageService2Mock.Verify(v => v.GetIconForFile(It.IsAny(), It.IsAny<__VSUIDATAFORMAT>()), Times.Never); 53 | } 54 | 55 | [Fact] 56 | public void ItShouldHandle_Document() 57 | { 58 | var ico = Icon.FromHandle(Resources.FileError_16x.GetHbitmap()); 59 | _iVsImageService2Mock.Setup(s => s.GetIconForFile(It.Is(o => o == "test.cs"), It.IsAny<__VSUIDATAFORMAT>())) 60 | .Returns(() => new WinFormsIconUIObject(ico)); 61 | 62 | var icon = _fileExtensionIconResolver.GetIcon(new ClosedDocument() 63 | { 64 | FullName = "c:/test.cs", 65 | Name = "test.cs" 66 | }); 67 | 68 | Assert.NotNull(icon); 69 | Assert.Same(ico, icon); 70 | _iVsImageService2Mock.Verify(v => v.GetIconForFile(It.Is(o => o == "test.cs"), It.IsAny<__VSUIDATAFORMAT>()), Times.Once); 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen.Test/Domain/Documents/ClosedDocumentComparerTest.cs: -------------------------------------------------------------------------------- 1 | using VSDocumentReopen.Domain.Documents; 2 | using Xunit; 3 | 4 | namespace VSDocumentReopen.Test.Domain.Documents 5 | { 6 | public class ClosedDocumentComparerTest 7 | { 8 | private readonly ClosedDocumentComparer _closedDocumentComparer; 9 | 10 | public ClosedDocumentComparerTest() 11 | { 12 | _closedDocumentComparer = new ClosedDocumentComparer(); 13 | } 14 | 15 | [Fact] 16 | public void Equals_Should_Handle_Null() 17 | { 18 | var ret = _closedDocumentComparer.Equals(null, null); 19 | 20 | Assert.True(ret); 21 | } 22 | 23 | [Fact] 24 | public void Equals_Should_Handle_NullDocument() 25 | { 26 | var ret = _closedDocumentComparer.Equals(NullDocument.Instance, NullDocument.Instance); 27 | 28 | Assert.True(ret); 29 | } 30 | 31 | [Fact] 32 | public void Equals_Should_Compare_Documents_By_FullName() 33 | { 34 | var ret = _closedDocumentComparer.Equals(NullDocument.Instance, new ClosedDocument() { FullName = null }); 35 | 36 | Assert.False(ret); 37 | } 38 | 39 | [Fact] 40 | public void Equals_Should_Compare_Documents_By_FullName2() 41 | { 42 | var ret = _closedDocumentComparer.Equals(NullDocument.Instance, new ClosedDocument() { FullName = "" }); 43 | 44 | Assert.True(ret); 45 | } 46 | 47 | [Fact] 48 | public void Equals_Should_Compare_Documents_By_FullName3() 49 | { 50 | var ret = _closedDocumentComparer.Equals(new ClosedDocument() { FullName = "c:\\test.cs" }, new ClosedDocument() {FullName = "c:\\test.cs"}); 51 | 52 | Assert.True(ret); 53 | } 54 | 55 | [Fact] 56 | public void Equals_Should_Compare_Documents_By_FullName4() 57 | { 58 | var ret = _closedDocumentComparer.Equals(new ClosedDocument() { FullName = "c:\\test.cs" }, new ClosedDocument() { FullName = "c:\\test2.cs" }); 59 | 60 | Assert.False(ret); 61 | } 62 | 63 | [Fact] 64 | public void GetHashCode_Should_Handle_Null() 65 | { 66 | var ret = _closedDocumentComparer.GetHashCode(null); 67 | 68 | Assert.Equal(0, ret); 69 | } 70 | 71 | [Fact] 72 | public void GetHashCode_Should_Handle_NullDocument() 73 | { 74 | var ret = _closedDocumentComparer.GetHashCode(NullDocument.Instance); 75 | 76 | Assert.NotEqual(0, ret); 77 | } 78 | 79 | [Fact] 80 | public void GetHashCode_Should_Handle_Null_FullName() 81 | { 82 | var ret = _closedDocumentComparer.GetHashCode(new ClosedDocument() {FullName = null}); 83 | 84 | Assert.Equal(0, ret); 85 | } 86 | 87 | [Fact] 88 | public void GetHashCode_Should_Handle_FullName() 89 | { 90 | var ret = _closedDocumentComparer.GetHashCode(new ClosedDocument() { FullName = "c:\\test.cs" }); 91 | 92 | Assert.NotEqual(0, ret); 93 | } 94 | } 95 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Infrastructure/Logging/Logentries/MemoryInfoEnricher.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Core; 2 | using Serilog.Events; 3 | using System; 4 | using System.Diagnostics; 5 | using System.Runtime.InteropServices; 6 | 7 | namespace VSDocumentReopen.Infrastructure.Logging.Logentries 8 | { 9 | public class MemoryInfoEnricher : ILogEventEnricher 10 | { 11 | [DllImport("kernel32.dll")] 12 | [return: MarshalAs(UnmanagedType.Bool)] 13 | static extern bool GetPhysicallyInstalledSystemMemory(out long TotalMemoryInKilobytes); 14 | 15 | private class MemoryInfo 16 | { 17 | private const double Kb = 1024.0; 18 | private const string ValueFormatter = "#,##0 Kb"; 19 | 20 | public string SystemMemory { get; } 21 | 22 | public string PrivateMemorySize64 { get; } 23 | public string PagedMemorySize64 { get; } 24 | public string VirtualMemorySize64 { get; } 25 | 26 | public string PeakVirtualMemorySize64 { get; } 27 | public string PeakPagedMemorySize64 { get; } 28 | public string PeakWorkingSet64 { get; } 29 | 30 | public string WorkingSet64 { get; } 31 | 32 | public MemoryInfo(long systemMemory, Process proc) 33 | { 34 | SystemMemory = systemMemory.ToString(ValueFormatter); 35 | 36 | PrivateMemorySize64 = (proc.PrivateMemorySize64 / Kb).ToString(ValueFormatter); 37 | PagedMemorySize64 = (proc.PagedMemorySize64 / Kb).ToString(ValueFormatter); 38 | VirtualMemorySize64 = (proc.VirtualMemorySize64 / Kb).ToString(ValueFormatter); 39 | WorkingSet64 = (proc.WorkingSet64 / Kb).ToString(ValueFormatter); 40 | 41 | PeakVirtualMemorySize64 = (proc.PeakVirtualMemorySize64 / Kb).ToString(ValueFormatter); 42 | PeakPagedMemorySize64 = (proc.PeakPagedMemorySize64 / Kb).ToString(ValueFormatter); 43 | PeakWorkingSet64 = (proc.PeakWorkingSet64 / Kb).ToString(ValueFormatter); 44 | } 45 | } 46 | 47 | private readonly Lazy _systemMemoryReader; 48 | 49 | public MemoryInfoEnricher() 50 | { 51 | _systemMemoryReader = new Lazy(GetSystemMemory, true); 52 | } 53 | 54 | private long GetSystemMemory() 55 | { 56 | GetPhysicallyInstalledSystemMemory(out long memoryInKb); 57 | return memoryInKb; 58 | } 59 | 60 | public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) 61 | { 62 | if (logEvent != null) 63 | { 64 | try 65 | { 66 | var currentProcess = Process.GetCurrentProcess(); 67 | 68 | logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("Memory Info", 69 | new MemoryInfo(_systemMemoryReader.Value, currentProcess), 70 | true)); 71 | } 72 | catch (Exception ex) 73 | { 74 | LoggerContext.Current.Logger.Error($"Unexpected error happened in {nameof(MemoryInfoEnricher)}", ex); 75 | } 76 | } 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen.Test/Infrastructure/HistoryCommands/RemoveSomeCommandTest.cs: -------------------------------------------------------------------------------- 1 | using Moq; 2 | using VSDocumentReopen.Domain.Documents; 3 | using VSDocumentReopen.Infrastructure.Document.Commands; 4 | using VSDocumentReopen.Infrastructure.Document.Factories; 5 | using VSDocumentReopen.Infrastructure.Document.Tracking; 6 | using VSDocumentReopen.Infrastructure.HistoryCommands; 7 | using Xunit; 8 | 9 | namespace VSDocumentReopen.Test.Infrastructure.HistoryCommands 10 | { 11 | public class RemoveSomeCommandTest 12 | { 13 | private readonly RemoveSomeCommand _removeSomeCommand; 14 | private readonly Mock _documentHistoryCommandsMock; 15 | private readonly Mock _documentCommandFactoryMock; 16 | private readonly Mock _documentCommandMock; 17 | 18 | public RemoveSomeCommandTest() 19 | { 20 | _documentHistoryCommandsMock = new Mock(); 21 | _documentHistoryCommandsMock.Setup(s => s.RemoveLast()).Returns(NullDocument.Instance); 22 | 23 | _documentCommandMock = new Mock(); 24 | 25 | _documentCommandFactoryMock = new Mock(); 26 | _documentCommandFactoryMock.Setup(s => s.CreateCommand(It.IsAny())) 27 | .Returns(_documentCommandMock.Object); 28 | 29 | _removeSomeCommand = new RemoveSomeCommand(_documentHistoryCommandsMock.Object, _documentCommandFactoryMock.Object, 30 | NullDocument.Instance, NullDocument.Instance); 31 | } 32 | 33 | [Fact] 34 | public void ItShould_Handle_Empty_Document_Array() 35 | { 36 | var command = new RemoveSomeCommand(null, null); 37 | 38 | command.Execute(); 39 | 40 | _documentHistoryCommandsMock.Verify(v => v.RemoveLast(), Times.Never); 41 | _documentCommandFactoryMock.Verify(v => v.CreateCommand(It.IsAny()), Times.Never); 42 | _documentCommandMock.Verify(v => v.Execute(), Times.Never); 43 | } 44 | 45 | [Fact] 46 | public void ItShould_Handle_Nulls() 47 | { 48 | var command = new RemoveSomeCommand(null, null, NullDocument.Instance); 49 | 50 | command.Execute(); 51 | 52 | _documentHistoryCommandsMock.Verify(v => v.RemoveLast(), Times.Never); 53 | _documentCommandFactoryMock.Verify(v => v.CreateCommand(It.IsAny()), Times.Never); 54 | _documentCommandMock.Verify(v => v.Execute(), Times.Never); 55 | } 56 | 57 | [Fact] 58 | public void ItShould_Remove_Last_Document() 59 | { 60 | _removeSomeCommand.Execute(); 61 | 62 | _documentCommandFactoryMock.Verify(v => v.CreateCommand(It.Is(p => p == NullDocument.Instance)), Times.Exactly(2)); 63 | _documentCommandMock.Verify(v => v.Execute(), Times.Exactly(2)); 64 | _documentHistoryCommandsMock.Verify(v => v.Remove(It.IsAny()), Times.Once); 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen.Test/Infrastructure/Helpers/ServiceStackJsonSerializerTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using VSDocumentReopen.Infrastructure.Helpers; 5 | using Xunit; 6 | 7 | namespace VSDocumentReopen.Test.Infrastructure.Helpers 8 | { 9 | public class ServiceStackJsonSerializerTest : IDisposable 10 | { 11 | private class TestObj 12 | { 13 | public int Id { get; set; } 14 | public string Data { get; set; } 15 | } 16 | private class TestObjData : IEnumerable 17 | { 18 | public IEnumerator GetEnumerator() 19 | { 20 | yield return new object[] { 0, "" }; 21 | yield return new object[] { 10, "0123456789!@#$%^&*()_+asdfghjkl;'qwertyuiop[]zxcvbnm,./" }; 22 | yield return new object[] { 9999, "teeeeeest" }; 23 | } 24 | 25 | IEnumerator IEnumerable.GetEnumerator() => throw new NotImplementedException(); 26 | } 27 | 28 | private readonly ServiceStackJsonSerializer _serviceStackJsonSerializer; 29 | 30 | public ServiceStackJsonSerializerTest() 31 | { 32 | _serviceStackJsonSerializer = new ServiceStackJsonSerializer(); 33 | } 34 | 35 | [Theory] 36 | [InlineData(null)] 37 | [InlineData("")] 38 | [InlineData(" ")] 39 | [InlineData(5)] 40 | public void Serialize_ShouldHandle_InvalidObjects(object obj) 41 | { 42 | var ret =_serviceStackJsonSerializer.Serialize(obj); 43 | 44 | Assert.Equal(ret?.Trim('"'), obj?.ToString()); 45 | } 46 | 47 | [Theory] 48 | [ClassData(typeof(TestObjData))] 49 | public void Serialize_ShouldHandle_ValidObjects(int id, string data) 50 | { 51 | var ret = _serviceStackJsonSerializer.Serialize(new TestObj() 52 | { 53 | Id = id, 54 | Data = data 55 | }); 56 | 57 | Assert.Equal($"{{\"Id\":{id},\"Data\":\"{data}\"}}", ret); 58 | } 59 | 60 | [Theory] 61 | [InlineData(null)] 62 | [InlineData(" ")] 63 | [InlineData("5")] 64 | public void Deserialize_ShouldHandle_InvalidString(string json) 65 | { 66 | var ret = _serviceStackJsonSerializer.Deserialize(json); 67 | 68 | Assert.Equal(json, ret?.ToString()); 69 | } 70 | 71 | [Fact] 72 | public void Deserialize_ShouldHandle_EmptyString() 73 | { 74 | var ret = _serviceStackJsonSerializer.Deserialize(""); 75 | 76 | Assert.Null(ret); 77 | } 78 | 79 | [Theory] 80 | [InlineData(0, "")] 81 | [InlineData(15, "test")] 82 | [InlineData(99999, "0123456789!@#$%^&*()_+asdfghjkl;'qwertyuiop[]zxcvbnm,./")] 83 | public void Deserialize_ShouldHandle_ValidJson(int id, string data) 84 | { 85 | var ret = _serviceStackJsonSerializer.Deserialize($"{{\"Id\":{id},\"Data\":\"{data}\"}}"); 86 | 87 | Assert.NotNull(ret); 88 | Assert.Equal(id, ret.Id); 89 | Assert.Equal(data, ret.Data); 90 | } 91 | 92 | public void Dispose() 93 | { 94 | } 95 | } 96 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Visual Studio Document Reopen 2 | Visual Studio Extension to reopen last closed document(s) with (CTRL + SHIFT + T) shortcut. 3 | 4 | [![Build Status](https://major-soft.visualstudio.com/GitHub/_apis/build/status/majorimi.vs-reopen)](https://major-soft.visualstudio.com/GitHub/_build/latest?definitionId=5) 5 | [![Visual Studio Marketplace Version](https://img.shields.io/visual-studio-marketplace/v/major.VSDocumentReopen?label=Version)](https://marketplace.visualstudio.com/items?itemName=major.VSDocumentReopen) 6 | [![Visual Studio Marketplace Rating](https://img.shields.io/visual-studio-marketplace/r/major.VSDocumentReopen?label=Marketplace%20Rating)](https://marketplace.visualstudio.com/items?itemName=major.VSDocumentReopen) 7 | [![Visual Studio Marketplace Downloads](https://img.shields.io/visual-studio-marketplace/d/major.VSDocumentReopen?label=Marketplace%20Downloads)](https://marketplace.visualstudio.com/items?itemName=major.VSDocumentReopen) 8 | 9 | Download from [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=major.VSDocumentReopen). 10 | For version history see [release notes](https://github.com/majorimi/vs-reopen/blob/master/ReleaseNotes.md). 11 | 12 | ### Advantages compared to build in "Recent files" menu: 13 | - Convenient shortcut key (CTRL + SHIFT + T) 14 | - Tracks the history by opened solution, not irrelevant files from other solutions 15 | - Infinite history, not just a few which can fit into the menu 16 | - See all history in Tool Window 17 | - Reopen any document(s) 18 | - Remove any document(s) from history without reopening 19 | - Clear document history 20 | 21 | #### Reopen recent document(s) menu features: 22 | :warning: **Before version 1.4.0 command menus can be found under "Tools" menu!** 23 | 24 | - Reopen closed Document(s) 25 | - Show last 5 closed documents 26 | - Open History Tool Window 27 | - Remove last Document(s) from history 28 | - Clear history 29 | 30 | ![Menu](https://raw.githubusercontent.com/majorimi/vs-reopen/master/VSDocumentReopen/VSDocumentReopen/Resources/VsToolsMenu.png "Document history menu") 31 | 32 | #### History Tool Window features: 33 | 34 | - Order document history by any column 35 | - Search in document path and name 36 | - Reopen any selected document(s) 37 | - Remove any selected document(s) form history 38 | - Clear history 39 | 40 | ![Window](https://raw.githubusercontent.com/majorimi/vs-reopen/master/VSDocumentReopen/VSDocumentReopen/Resources/VsToolsWindow.png "Document history Tool Window") 41 | 42 | #### Support 43 | This VS Extension is free to use. If you like it you can [Buy me a beer :)](https://blockchain.com/btc/payment_request?address=12dNbAbcW7W959QQR649AcwVL4TZGjMoTM&amount=0.00015807&message=Buy_me_a_beer) 44 | 45 | 46 | Buy me a beer 47 | 48 | -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen.Test/Infrastructure/HistoryCommands/HistoryCommandFactoryTest.cs: -------------------------------------------------------------------------------- 1 | using Moq; 2 | using System; 3 | using VSDocumentReopen.Domain.Documents; 4 | using VSDocumentReopen.Infrastructure.Document.Factories; 5 | using VSDocumentReopen.Infrastructure.Document.Tracking; 6 | using VSDocumentReopen.Infrastructure.HistoryCommands; 7 | using Xunit; 8 | 9 | namespace VSDocumentReopen.Test.Infrastructure.HistoryCommands 10 | { 11 | public class HistoryCommandFactoryTest 12 | { 13 | private class FakeCommand : IHistoryCommand 14 | { 15 | public void Execute() => throw new NotImplementedException(); 16 | } 17 | 18 | private class FakeCommand2 : IHistoryCommand 19 | { 20 | public IDocumentHistoryCommands DocumentHistoryCommands { get; } 21 | public IDocumentCommandFactory DocumentCommandFactory { get; } 22 | public IClosedDocument[] ClosedDocuments { get; } 23 | 24 | public FakeCommand2(IDocumentHistoryCommands documentHistoryCommands, 25 | IDocumentCommandFactory documentCommandFactory, 26 | params IClosedDocument[] closedDocuments) 27 | { 28 | DocumentHistoryCommands = documentHistoryCommands; 29 | DocumentCommandFactory = documentCommandFactory; 30 | ClosedDocuments = closedDocuments; 31 | } 32 | 33 | 34 | public void Execute() => throw new NotImplementedException(); 35 | } 36 | 37 | private readonly Mock _documentHistoryCommandsMock; 38 | private readonly Mock _documentCommandFactoryMock; 39 | 40 | public HistoryCommandFactoryTest() 41 | { 42 | _documentHistoryCommandsMock = new Mock(); 43 | _documentCommandFactoryMock = new Mock(); 44 | } 45 | 46 | [Fact] 47 | public void ItShould_Handle_Null() 48 | { 49 | var factory = new HistoryCommandFactory(null, null); 50 | 51 | var command = factory.CreateCommand(); 52 | 53 | Assert.NotNull(command); 54 | Assert.IsType(command); 55 | } 56 | 57 | [Fact] 58 | public void ItShould_Create_Command() 59 | { 60 | var factory = new HistoryCommandFactory(_documentHistoryCommandsMock.Object, _documentCommandFactoryMock.Object); 61 | 62 | var command = factory.CreateCommand(); 63 | 64 | Assert.NotNull(command); 65 | Assert.IsType(command); 66 | Assert.Equal((command as FakeCommand2).DocumentHistoryCommands, _documentHistoryCommandsMock.Object); 67 | Assert.Equal((command as FakeCommand2).DocumentCommandFactory, _documentCommandFactoryMock.Object); 68 | } 69 | 70 | [Fact] 71 | public void ItShould_Create_Command_With_Documents() 72 | { 73 | var factory = new HistoryCommandFactory(_documentHistoryCommandsMock.Object, _documentCommandFactoryMock.Object); 74 | 75 | var docs = new IClosedDocument[] 76 | { 77 | NullDocument.Instance 78 | }; 79 | 80 | var command = factory.CreateCommand(docs); 81 | 82 | Assert.NotNull(command); 83 | Assert.IsType(command); 84 | Assert.Equal((command as FakeCommand2).ClosedDocuments, docs); 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Infrastructure/Document/Tracking/DocumentHistoryManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using VSDocumentReopen.Domain.Documents; 5 | 6 | namespace VSDocumentReopen.Infrastructure.Document.Tracking 7 | { 8 | public sealed class DocumentHistoryManager : IDocumentHistoryManager 9 | { 10 | public event EventHandler HistoryChanged; 11 | 12 | private readonly ClosedDocumentComparer _closedDocumentComparer; 13 | private readonly LinkedList ClosedDocuments; 14 | 15 | public int Count => ClosedDocuments.Count; 16 | 17 | public DocumentHistoryManager() 18 | { 19 | _closedDocumentComparer = new ClosedDocumentComparer(); 20 | ClosedDocuments = new LinkedList(); 21 | } 22 | 23 | public void Clear() 24 | { 25 | ClosedDocuments.Clear(); 26 | OnHistoryChanged(); 27 | } 28 | 29 | public void Add(IClosedDocument document) 30 | { 31 | if(document == null) 32 | { 33 | return; 34 | } 35 | 36 | //Remove duplications 37 | var docs = ClosedDocuments 38 | .Where(x => _closedDocumentComparer.Equals(x, document)) 39 | .ToArray(); 40 | //TODO: use intersect 41 | foreach (var doc in docs) 42 | { 43 | ClosedDocuments.Remove(doc); 44 | } 45 | //TODO: filter history by MaxAllowed Number and Days... 46 | ClosedDocuments.AddFirst(document); 47 | OnHistoryChanged(); 48 | } 49 | 50 | public IClosedDocument RemoveLast() 51 | { 52 | if (ClosedDocuments.Count > 0) 53 | { 54 | var ret = ClosedDocuments.First.Value; 55 | ClosedDocuments.RemoveFirst(); 56 | OnHistoryChanged(); 57 | 58 | return ret; 59 | } 60 | 61 | return NullDocument.Instance; 62 | } 63 | 64 | public IEnumerable Get(int number) => ClosedDocuments.Take(number).ToArray(); 65 | 66 | public IEnumerable GetAll() 67 | { 68 | return ClosedDocuments.ToArray(); 69 | } 70 | 71 | public void Remove(IClosedDocument closedDocument) 72 | { 73 | if (ClosedDocuments.Remove(closedDocument)) 74 | { 75 | OnHistoryChanged(); 76 | } 77 | } 78 | 79 | public void Remove(IEnumerable closedDocuments) 80 | { 81 | bool removed = false; 82 | foreach (var item in closedDocuments) 83 | { 84 | if (ClosedDocuments.Remove(item)) 85 | { 86 | removed = true; 87 | } 88 | } 89 | 90 | if(removed) 91 | { 92 | OnHistoryChanged(); 93 | } 94 | } 95 | 96 | public void Initialize(IEnumerable closedDocuments) 97 | { 98 | Clear(); 99 | 100 | if (closedDocuments is null) 101 | { 102 | return; 103 | } 104 | 105 | //Remove duplications 106 | closedDocuments = closedDocuments.OrderByDescending(x => x.ClosedAt) 107 | .Distinct(_closedDocumentComparer) 108 | .Reverse(); 109 | 110 | foreach (var document in closedDocuments) 111 | { 112 | ClosedDocuments.AddFirst(document); 113 | } 114 | //TODO: filter history by MaxAllowed Number and Days... 115 | OnHistoryChanged(); 116 | } 117 | 118 | private void OnHistoryChanged() 119 | { 120 | HistoryChanged?.Invoke(this, new EventArgs()); 121 | } 122 | } 123 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen.Test/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /ReleaseNotes.md: -------------------------------------------------------------------------------- 1 | # Visual Studio Document Reopen 2 | Visual Studio Extension to reopen the last closed document(s) with (CTRL + SHIFT + T) shortcut. 3 | 4 | Download from [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=major.VSDocumentReopen). 5 | 6 | ## Version history 7 | 8 | ##### v. 1.4.10 9 | - Remove non necessary command key bindings. Since it some overrides user settings. 10 | 11 | ##### v. 1.4.9 12 | - Bug fixes 13 | ##### v. 1.4.8 14 | - Logging for issue investigation: "System.ArgumentException: The path is not of a legal form." 15 | 16 | ##### v. 1.4.7 17 | - Bug fix: Async package loading issue. Extension does not work if VS was started by double clicking on a .sln file. 18 | 19 | ##### v. 1.4.6 20 | - Code improvements: 21 | - Nuget package updates, 22 | - Async package loading 23 | - Enhance History Tool Window: 24 | - Remove non existing items from history toolbar menu button 25 | 26 | ##### v. 1.4.5 27 | - Tool Window issue fixes: 28 | - Search result label length issue 29 | - Dark theme support 30 | - Visual Studio 2019 upgrade 31 | 32 | ##### v. 1.4.4 33 | - Tool Window issue fixes: 34 | - #11: Tool bar icons vanish if the tool window gets too narrow 35 | - Column reorder does not work 36 | - Tool Window columns: 37 | - Can be show/hide form context menu 38 | - Order and size customizations saved and restored 39 | - Reset customization from context menu 40 | - Memory usage diagnostics 41 | 42 | ##### v. 1.4.3 43 | - Emergency update due to key binding issues, add more diagnostics. 44 | 45 | ##### v. 1.4.2 46 | - Bug fix: History Tool Window search box does not raise search event for Backspace 47 | - Bud fix: set key bindings throws ArgumentException for localized Visual Studio instances 48 | 49 | ##### v. 1.4.1 50 | - History Tool Window supports Visual Studio color theme 51 | - Store search history 52 | - Code enhancements and more detailed diagnostics 53 | 54 | #### v. 1.4.0 55 | - **Move menu commands from "Tools" to "File" menu!** 56 | - New menu command: Remove last closed document from history without open it (CTRL + SHIFT + D) 57 | - Remove history duplications 58 | - Add logging and diagnostics 59 | - Code enhancements 60 | 61 | ##### v. 1.3.1 62 | - Order history data by clicking on column headers in History Tool Window 63 | - Code enhancements, small issue fixes 64 | 65 | #### v. 1.3.0 66 | - Add support for **Visual Studio 2015** 67 | - Enhance History Tool Window 68 | - Show Solution Explorer style document Icon in "Type" column 69 | - Show icon in "Exists" column 70 | 71 | #### v. 1.2.0 72 | - Enhance History Tool Window: 73 | - Reopen any closed document by double clicking or selected document(s) using toolbar menu button 74 | - Remove selected document(s) using toolbar menu button or Delete key 75 | - Clear history with toolbar menu button 76 | - Filter history by document name or path 77 | - More info about the documents 78 | - Code enhancements, small issue fixes 79 | 80 | ##### v. 1.1.1 81 | - Enforce key bindings for menu commands 82 | - Enable vertical scroll in History Tool Window 83 | - Bugfix: History Tool Window does not initialized with history data 84 | 85 | #### v. 1.1.0 86 | - Reorganized menu command: under the Tools menu child menu 87 | - Track the last 5 closed document in child menu 88 | - New menu command: Show all history in a new Tool Window (CTRL + SHIFT + R) 89 | - Persist and load closed documents history (per solution) 90 | - Clear history functionality 91 | - New Preview image 92 | 93 | ##### v. 1.0.1 94 | - Bug fix: exception when file was deleted 95 | - New Icon and Preview images 96 | 97 | #### v. 1.0.0 98 | - Initial release. Extension add new command to Tools menu and CTRL+SHIFT+T shortcut 99 | -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/VSDocumentReopen.ruleset: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/VS/Commands/DocumentsHistoryCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.Design; 4 | using System.Linq; 5 | using Microsoft.VisualStudio.Shell; 6 | using VSDocumentReopen.Domain.Documents; 7 | using VSDocumentReopen.Infrastructure.Document.Tracking; 8 | using VSDocumentReopen.Infrastructure.Helpers; 9 | using VSDocumentReopen.Infrastructure.HistoryCommands; 10 | using VSDocumentReopen.Infrastructure.Logging; 11 | using Task = System.Threading.Tasks.Task; 12 | 13 | namespace VSDocumentReopen.VS.Commands 14 | { 15 | public sealed class DocumentsHistoryCommand 16 | { 17 | /// 18 | /// Command ID. 19 | /// 20 | public const int CommandId = 0x0200; 21 | 22 | /// 23 | /// Command menu group (command set GUID). 24 | /// 25 | public static readonly Guid CommandSet = new Guid("d968b4de-3a69-4eb1-b676-942055da9dfd"); 26 | 27 | private static readonly List Commands = new List(); 28 | private const string HistoryItemKey = "HistoryItem"; 29 | 30 | private readonly AsyncPackage _package; 31 | private readonly IDocumentHistoryQueries _documentHistoryQueries; 32 | private readonly IHistoryCommandFactory _reopenSomeDocumentsCommandFactory; 33 | 34 | private DocumentsHistoryCommand(AsyncPackage package, 35 | OleMenuCommandService commandService, 36 | IDocumentHistoryQueries documentHistoryQueries, 37 | IHistoryCommandFactory reopenSomeDocumentsCommandFactory) 38 | { 39 | _package = package ?? throw new ArgumentNullException(nameof(package)); 40 | commandService = commandService ?? throw new ArgumentNullException(nameof(commandService)); 41 | _documentHistoryQueries = documentHistoryQueries ?? throw new ArgumentNullException(nameof(documentHistoryQueries)); 42 | _reopenSomeDocumentsCommandFactory = reopenSomeDocumentsCommandFactory ?? throw new ArgumentNullException(nameof(reopenSomeDocumentsCommandFactory)); 43 | 44 | var menuCommandId = new CommandID(CommandSet, CommandId); 45 | var command = new OleMenuCommand(Execute, menuCommandId) 46 | { 47 | Visible = false, 48 | Enabled = false 49 | }; 50 | 51 | command.BeforeQueryStatus += DynamicStartBeforeQueryStatus; 52 | commandService.AddCommand(command); 53 | } 54 | 55 | private void DynamicStartBeforeQueryStatus(object sender, EventArgs e) 56 | { 57 | var currentCommand = (sender as OleMenuCommand) ?? throw new InvalidCastException($"Unable to cast {nameof(sender)} to {typeof(OleMenuCommand)}"); 58 | var mcs = _package.GetServiceAsync(typeof(IMenuCommandService)).GetAwaiter().GetResult() as OleMenuCommandService 59 | ?? throw new InvalidCastException($"Unable to cast {nameof(IMenuCommandService)} to {typeof(OleMenuCommandService)}"); 60 | 61 | foreach (var cmd in Commands) 62 | { 63 | mcs.RemoveCommand(cmd); 64 | } 65 | 66 | var history = _documentHistoryQueries.Get(Infrastructure.ConfigurationManager.Current.Config.MaxNumberOfHistoryItemsOnMenu); 67 | 68 | currentCommand.Visible = true; 69 | currentCommand.Text = history.Any() ? "" : ""; 70 | 71 | var j = 1; 72 | foreach (var item in history) 73 | { 74 | var menuCommandId = new CommandID(CommandSet, CommandId + j); 75 | var command = new OleMenuCommand(DynamicCommandCallback, menuCommandId); 76 | 77 | command.Properties.Add(HistoryItemKey, item); 78 | command.Text = $"{j++}: {PathFormatter.ShrinkPath(item.FullName, 50)}"; 79 | command.BeforeQueryStatus += (x, y) => 80 | { 81 | (x as OleMenuCommand).Visible = true; 82 | }; 83 | 84 | Commands.Add(command); 85 | mcs.AddCommand(command); 86 | } 87 | } 88 | 89 | private void DynamicCommandCallback(object sender, EventArgs e) 90 | { 91 | var cmd = (OleMenuCommand)sender ?? throw new InvalidCastException($"Unable to cast {nameof(sender)} to {typeof(OleMenuCommand)}"); 92 | 93 | var document = (cmd.Properties[HistoryItemKey] as IClosedDocument) ?? NullDocument.Instance; 94 | var command = _reopenSomeDocumentsCommandFactory.CreateCommand(document); 95 | command.Execute(); 96 | 97 | LoggerContext.Current.Logger.Info($"VS Command: {nameof(DocumentsHistoryCommand)} was executed with {command.GetType()}"); 98 | } 99 | 100 | public static DocumentsHistoryCommand Instance 101 | { 102 | get; 103 | private set; 104 | } 105 | 106 | public static async Task InitializeAsync(AsyncPackage package, IDocumentHistoryQueries documentHistoryQueries, IHistoryCommandFactory reopenSomeDocumentsCommandFactory) 107 | { 108 | var commandService = package == null 109 | ? null 110 | : await package.GetServiceAsync(typeof(IMenuCommandService)) as OleMenuCommandService; 111 | Instance = new DocumentsHistoryCommand(package, commandService, documentHistoryQueries, reopenSomeDocumentsCommandFactory); 112 | } 113 | 114 | private void Execute(object sender, EventArgs e) 115 | { 116 | //Do nothing, menu should be disabled... 117 | } 118 | } 119 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Infrastructure/Document/Tracking/DocumentEventsTracker.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using EnvDTE; 4 | using VSDocumentReopen.Domain; 5 | using VSDocumentReopen.Domain.Documents; 6 | using VSDocumentReopen.Infrastructure.Logging; 7 | using VSDocumentReopen.VS.MessageBox; 8 | 9 | namespace VSDocumentReopen.Infrastructure.Document.Tracking 10 | { 11 | public sealed class DocumentEventsTracker : IDisposable 12 | { 13 | private readonly _DTE _dte; 14 | private readonly IDocumentHistoryManager _documentHistoryManager; 15 | private readonly IHistoryRepositoryFactory _historyRepositoryFactory; 16 | private readonly IMessageBox _messageBox; 17 | private readonly SolutionEvents _solutionEvents; 18 | private readonly DocumentEvents _documentEvents; 19 | 20 | private SolutionInfo _currentSolution; 21 | 22 | public SolutionStates SolutionState { get; private set; } 23 | 24 | public DocumentEventsTracker(_DTE dte, 25 | IDocumentHistoryManager documentHistoryManager, 26 | IHistoryRepositoryFactory historyRepositoryFactory, 27 | IMessageBox messageBox) 28 | { 29 | _dte = dte ?? throw new ArgumentNullException(nameof(dte)); 30 | _solutionEvents = _dte.Events.SolutionEvents; 31 | _documentEvents = _dte.Events.DocumentEvents; 32 | 33 | _documentHistoryManager = documentHistoryManager ?? throw new ArgumentNullException(nameof(documentHistoryManager)); 34 | _historyRepositoryFactory = historyRepositoryFactory ?? throw new ArgumentNullException(nameof(historyRepositoryFactory)); 35 | _messageBox = messageBox ?? throw new ArgumentNullException(nameof(messageBox)); 36 | 37 | Initialize(); 38 | } 39 | 40 | private void Initialize() 41 | { 42 | SolutionState = SolutionStates.None; 43 | 44 | _solutionEvents.Opened += OnSolutionEventsOnOpened; 45 | _solutionEvents.BeforeClosing += OnSolutionEventsOnBeforeClosing; 46 | _solutionEvents.AfterClosing += OnSolutionEventsOnAfterClosing; 47 | 48 | if(_dte.Solution.IsOpen) //VS2019 pushing for Async loading it is possible to have a loaded solution at this point... 49 | { 50 | OnSolutionEventsOnOpened(); 51 | } 52 | } 53 | 54 | private void OnSolutionEventsOnOpened() 55 | { 56 | _documentEvents.DocumentClosing += DocumentEventsOnDocumentClosing; 57 | SolutionState = SolutionStates.Opened; 58 | 59 | string slnPath = _dte.Solution.FullName; //can be empty if it is a new solution?? 60 | LoggerContext.Current.Logger.Info($"{nameof(DocumentEventsTracker)}.{nameof(OnSolutionEventsOnOpened)}() method executing. Opened solution Full path: '{slnPath}'"); 61 | 62 | if (string.IsNullOrWhiteSpace(slnPath)) 63 | { 64 | slnPath = _dte.Solution.Properties.Item("Path")?.Value?.ToString(); //https://github.com/3F/vsCommandEvent/blob/master/vsCommandEvent/Environment.cs 65 | LoggerContext.Current.Logger.Warning($"{nameof(DocumentEventsTracker)}.{nameof(OnSolutionEventsOnOpened)}() method executing. _dte.Solution.FullName was EMPTY or NULL. Getting Solution.Properties Full path: '{slnPath}'"); 66 | } 67 | 68 | var solutionDir = Path.GetDirectoryName(slnPath); 69 | var solutionName = Path.GetFileName(slnPath).Replace(".sln", string.Empty); 70 | _currentSolution = new SolutionInfo(solutionDir, solutionName); 71 | 72 | //Create history repo 73 | var historyRepository = _historyRepositoryFactory.CreateHistoryRepository(_currentSolution); 74 | 75 | //Load history and init state 76 | var history = historyRepository?.GetHistory(); 77 | _documentHistoryManager.Initialize(history); 78 | } 79 | 80 | private void OnSolutionEventsOnBeforeClosing() 81 | { 82 | //TODO: try to validate if it was closed or canceled... 83 | SolutionState = SolutionStates.StartedToClose; 84 | } 85 | 86 | private void OnSolutionEventsOnAfterClosing() 87 | { 88 | _documentEvents.DocumentClosing -= DocumentEventsOnDocumentClosing; 89 | 90 | //Save state 91 | var historyRepository = _historyRepositoryFactory.CreateHistoryRepository(_currentSolution); 92 | if (!historyRepository.SaveHistory(_documentHistoryManager.GetAll())) 93 | { 94 | _messageBox.ShowError("Visual Studio Document Reopen", "Failed to save Closed Document History!"); 95 | } 96 | 97 | SolutionState = SolutionStates.None; 98 | _currentSolution = null; 99 | _documentHistoryManager.Clear(); 100 | } 101 | 102 | private void DocumentEventsOnDocumentClosing(EnvDTE.Document document) 103 | { 104 | if (SolutionState == SolutionStates.Opened) 105 | { 106 | var doc = new ClosedDocument() 107 | { 108 | FullName = document.FullName, 109 | Name = document.Name, 110 | Kind = document.Kind, 111 | Language = document.Language, 112 | ClosedAt = DateTime.Now, 113 | }; 114 | 115 | _documentHistoryManager.Add(doc); 116 | } 117 | } 118 | 119 | public void Dispose() 120 | { 121 | _solutionEvents.Opened -= OnSolutionEventsOnOpened; 122 | _solutionEvents.BeforeClosing -= OnSolutionEventsOnBeforeClosing; 123 | _solutionEvents.AfterClosing -= OnSolutionEventsOnAfterClosing; 124 | 125 | _documentEvents.DocumentClosing -= DocumentEventsOnDocumentClosing; 126 | } 127 | } 128 | } -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/ReopenPackage.vsct: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | Reopen recent document(s) 31 | Reopen and manage recently closed documents history 32 | 33 | 34 | 35 | 36 | 37 | 44 | 45 | 55 | 56 | 63 | 64 | 71 | 78 | 79 | 80 | 81 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen.Test/VS/Commands/DocumentsHistoryCommandTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.Shell; 2 | using Moq; 3 | using System; 4 | using VSDocumentReopen.Domain.Documents; 5 | using VSDocumentReopen.Infrastructure.Document.Tracking; 6 | using VSDocumentReopen.Infrastructure.HistoryCommands; 7 | using VSDocumentReopen.Test.AssemblyFictures; 8 | using VSDocumentReopen.VS.Commands; 9 | using Xunit; 10 | using Task = System.Threading.Tasks.Task; 11 | 12 | namespace VSDocumentReopen.Test.VS.Commands 13 | { 14 | public class DocumentsHistoryCommandTest : VisualStudioCommandTestBase, IAssemblyFixture 15 | { 16 | private readonly Mock _documentHistoryQueriesMock; 17 | private readonly Mock _historyCommandMock; 18 | private readonly Mock _historyCommandFactoryMock; 19 | 20 | private readonly DocumentsHistoryCommand _documentsHistoryCommand; 21 | private readonly ConfigContext _configContext; 22 | 23 | public DocumentsHistoryCommandTest(ConfigContext configContext) 24 | { 25 | _documentHistoryQueriesMock = new Mock(); 26 | 27 | _historyCommandMock = new Mock(); 28 | _historyCommandMock.Setup(s => s.Execute()); 29 | 30 | _historyCommandFactoryMock = new Mock(); 31 | 32 | Task.Run(() => DocumentsHistoryCommand.InitializeAsync(_asyncPackageMock.Object, 33 | _documentHistoryQueriesMock.Object, 34 | _historyCommandFactoryMock.Object)).Wait(); 35 | _documentsHistoryCommand = DocumentsHistoryCommand.Instance; 36 | _configContext = configContext; 37 | } 38 | 39 | [Fact] 40 | public void CommandId_ShouldBe() 41 | { 42 | Assert.Equal(0x0200, DocumentsHistoryCommand.CommandId); 43 | } 44 | 45 | [Fact] 46 | public async Task ItShould_Handle_Null_AsyncPackageAsync() 47 | { 48 | await Assert.ThrowsAsync(() => 49 | { 50 | return Task.Run(() => DocumentsHistoryCommand.InitializeAsync(null, _documentHistoryQueriesMock.Object, 51 | _historyCommandFactoryMock.Object)); 52 | }); 53 | } 54 | 55 | [Fact] 56 | public async void ItShould_Handle_Null_IDocumentHistoryQueries() 57 | { 58 | await Assert.ThrowsAsync(() => 59 | { 60 | return Task.Run(() => DocumentsHistoryCommand.InitializeAsync(_asyncPackageMock.Object, null, _historyCommandFactoryMock.Object)); 61 | }); 62 | } 63 | 64 | [Fact] 65 | public async void ItShould_Handle_Null_IHistoryCommandFactory() 66 | { 67 | await Assert.ThrowsAsync(() => 68 | { 69 | return Task.Run(() => DocumentsHistoryCommand.InitializeAsync(_asyncPackageMock.Object, _documentHistoryQueriesMock.Object, null)); 70 | }); 71 | } 72 | 73 | [Fact] 74 | public void Execute_Command_Should_Do_Nothing() 75 | { 76 | InvokeCommand(_documentsHistoryCommand); 77 | 78 | _documentHistoryQueriesMock.Verify(v => v.Get(It.IsAny()), Times.Never); 79 | _historyCommandFactoryMock.Verify(v => v.CreateCommand(It.IsAny()), Times.Never); 80 | } 81 | 82 | [Fact] 83 | public void ItShould_Handle_Empty_Document_Hisotry() 84 | { 85 | var cmd = new OleMenuCommand(null, null); 86 | _documentHistoryQueriesMock.Setup(s => s.Get(It.Is(p => p == 5))).Returns(new IClosedDocument[0]); 87 | _configContext.Configuration.SetupGet(s => s.MaxNumberOfHistoryItemsOnMenu).Returns(5); 88 | 89 | InvokeCommand(_documentsHistoryCommand, "DynamicStartBeforeQueryStatus", cmd); 90 | 91 | _configContext.Configuration.VerifyGet(v => v.MaxNumberOfHistoryItemsOnMenu, Times.Once); 92 | _documentHistoryQueriesMock.Verify(v => v.Get(It.Is(p => p == 5)), Times.Once); 93 | _historyCommandFactoryMock.Verify(v => v.CreateCommand(It.IsAny()), Times.Never); 94 | Assert.True(cmd.Visible); 95 | Assert.Equal("", cmd.Text); 96 | } 97 | 98 | [Fact] 99 | public void ItShould_Show_Document_Hisotry() 100 | { 101 | var cmd = new OleMenuCommand(null, null); 102 | _documentHistoryQueriesMock.Setup(s => s.Get(It.Is(p => p == 3))).Returns(new IClosedDocument[] 103 | { NullDocument.Instance, NullDocument.Instance }); 104 | _configContext.Configuration.SetupGet(s => s.MaxNumberOfHistoryItemsOnMenu).Returns(3); 105 | 106 | InvokeCommand(_documentsHistoryCommand, "DynamicStartBeforeQueryStatus", cmd); 107 | 108 | _configContext.Configuration.VerifyGet(v => v.MaxNumberOfHistoryItemsOnMenu, Times.AtLeastOnce); 109 | _documentHistoryQueriesMock.Verify(v => v.Get(It.Is(p => p == 3)), Times.Once); 110 | _historyCommandFactoryMock.Verify(v => v.CreateCommand(It.IsAny()), Times.Never); 111 | Assert.True(cmd.Visible); 112 | Assert.Equal("", cmd.Text); 113 | } 114 | 115 | [Fact] 116 | public void ItShould_Create_And_Execute_History_Command_With_NullDocument() 117 | { 118 | var cmd = new OleMenuCommand(null, null); 119 | _historyCommandFactoryMock.Setup(s => s.CreateCommand(It.Is(p => p[0] == NullDocument.Instance))).Returns(_historyCommandMock.Object); 120 | 121 | InvokeCommand(_documentsHistoryCommand, "DynamicCommandCallback", cmd); 122 | 123 | _historyCommandFactoryMock.Verify(v => v.CreateCommand(It.Is(p => p[0] == NullDocument.Instance)), Times.Once); 124 | _historyCommandMock.Verify(v => v.Execute(), Times.Once); 125 | } 126 | 127 | [Fact] 128 | public void ItShould_Create_And_Execute_History_Command() 129 | { 130 | var doc = new ClosedDocument(); 131 | var cmd = new OleMenuCommand(null, null); 132 | cmd.Properties.Add("HistoryItem", doc); 133 | _historyCommandFactoryMock.Setup(s => s.CreateCommand(It.Is(p => p[0] == doc))).Returns(_historyCommandMock.Object); 134 | 135 | InvokeCommand(_documentsHistoryCommand, "DynamicCommandCallback", cmd); 136 | 137 | _historyCommandFactoryMock.Verify(v => v.CreateCommand(It.Is(p => p[0] == doc)), Times.Once); 138 | _historyCommandMock.Verify(v => v.Execute(), Times.Once); 139 | } 140 | } 141 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015/2017 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # Visual Studio 2017 auto generated files 33 | Generated\ Files/ 34 | 35 | # MSTest test Results 36 | [Tt]est[Rr]esult*/ 37 | [Bb]uild[Ll]og.* 38 | 39 | # NUNIT 40 | *.VisualState.xml 41 | TestResult.xml 42 | 43 | # Build Results of an ATL Project 44 | [Dd]ebugPS/ 45 | [Rr]eleasePS/ 46 | dlldata.c 47 | 48 | # Benchmark Results 49 | BenchmarkDotNet.Artifacts/ 50 | 51 | # .NET Core 52 | project.lock.json 53 | project.fragment.lock.json 54 | artifacts/ 55 | **/Properties/launchSettings.json 56 | 57 | # StyleCop 58 | StyleCopReport.xml 59 | 60 | # Files built by Visual Studio 61 | *_i.c 62 | *_p.c 63 | *_i.h 64 | *.ilk 65 | *.meta 66 | *.obj 67 | *.iobj 68 | *.pch 69 | *.pdb 70 | *.ipdb 71 | *.pgc 72 | *.pgd 73 | *.rsp 74 | *.sbr 75 | *.tlb 76 | *.tli 77 | *.tlh 78 | *.tmp 79 | *.tmp_proj 80 | *.log 81 | *.vspscc 82 | *.vssscc 83 | .builds 84 | *.pidb 85 | *.svclog 86 | *.scc 87 | 88 | # Chutzpah Test files 89 | _Chutzpah* 90 | 91 | # Visual C++ cache files 92 | ipch/ 93 | *.aps 94 | *.ncb 95 | *.opendb 96 | *.opensdf 97 | *.sdf 98 | *.cachefile 99 | *.VC.db 100 | *.VC.VC.opendb 101 | 102 | # Visual Studio profiler 103 | *.psess 104 | *.vsp 105 | *.vspx 106 | *.sap 107 | 108 | # Visual Studio Trace Files 109 | *.e2e 110 | 111 | # TFS 2012 Local Workspace 112 | $tf/ 113 | 114 | # Guidance Automation Toolkit 115 | *.gpState 116 | 117 | # ReSharper is a .NET coding add-in 118 | _ReSharper*/ 119 | *.[Rr]e[Ss]harper 120 | *.DotSettings.user 121 | 122 | # JustCode is a .NET coding add-in 123 | .JustCode 124 | 125 | # TeamCity is a build add-in 126 | _TeamCity* 127 | 128 | # DotCover is a Code Coverage Tool 129 | *.dotCover 130 | 131 | # AxoCover is a Code Coverage Tool 132 | .axoCover/* 133 | !.axoCover/settings.json 134 | 135 | # Visual Studio code coverage results 136 | *.coverage 137 | *.coveragexml 138 | 139 | # NCrunch 140 | _NCrunch_* 141 | .*crunch*.local.xml 142 | nCrunchTemp_* 143 | 144 | # MightyMoose 145 | *.mm.* 146 | AutoTest.Net/ 147 | 148 | # Web workbench (sass) 149 | .sass-cache/ 150 | 151 | # Installshield output folder 152 | [Ee]xpress/ 153 | 154 | # DocProject is a documentation generator add-in 155 | DocProject/buildhelp/ 156 | DocProject/Help/*.HxT 157 | DocProject/Help/*.HxC 158 | DocProject/Help/*.hhc 159 | DocProject/Help/*.hhk 160 | DocProject/Help/*.hhp 161 | DocProject/Help/Html2 162 | DocProject/Help/html 163 | 164 | # Click-Once directory 165 | publish/ 166 | 167 | # Publish Web Output 168 | *.[Pp]ublish.xml 169 | *.azurePubxml 170 | # Note: Comment the next line if you want to checkin your web deploy settings, 171 | # but database connection strings (with potential passwords) will be unencrypted 172 | *.pubxml 173 | *.publishproj 174 | 175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 176 | # checkin your Azure Web App publish settings, but sensitive information contained 177 | # in these scripts will be unencrypted 178 | PublishScripts/ 179 | 180 | # NuGet Packages 181 | *.nupkg 182 | # The packages folder can be ignored because of Package Restore 183 | **/[Pp]ackages/* 184 | # except build/, which is used as an MSBuild target. 185 | !**/[Pp]ackages/build/ 186 | # Uncomment if necessary however generally it will be regenerated when needed 187 | #!**/[Pp]ackages/repositories.config 188 | # NuGet v3's project.json files produces more ignorable files 189 | *.nuget.props 190 | *.nuget.targets 191 | 192 | # Microsoft Azure Build Output 193 | csx/ 194 | *.build.csdef 195 | 196 | # Microsoft Azure Emulator 197 | ecf/ 198 | rcf/ 199 | 200 | # Windows Store app package directories and files 201 | AppPackages/ 202 | BundleArtifacts/ 203 | Package.StoreAssociation.xml 204 | _pkginfo.txt 205 | *.appx 206 | 207 | # Visual Studio cache files 208 | # files ending in .cache can be ignored 209 | *.[Cc]ache 210 | # but keep track of directories ending in .cache 211 | !*.[Cc]ache/ 212 | 213 | # Others 214 | ClientBin/ 215 | ~$* 216 | *~ 217 | *.dbmdl 218 | *.dbproj.schemaview 219 | *.jfm 220 | *.pfx 221 | *.publishsettings 222 | orleans.codegen.cs 223 | 224 | # Including strong name files can present a security risk 225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 226 | #*.snk 227 | 228 | # Since there are multiple workflows, uncomment next line to ignore bower_components 229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 230 | #bower_components/ 231 | 232 | # RIA/Silverlight projects 233 | Generated_Code/ 234 | 235 | # Backup & report files from converting an old project file 236 | # to a newer Visual Studio version. Backup files are not needed, 237 | # because we have git ;-) 238 | _UpgradeReport_Files/ 239 | Backup*/ 240 | UpgradeLog*.XML 241 | UpgradeLog*.htm 242 | ServiceFabricBackup/ 243 | *.rptproj.bak 244 | 245 | # SQL Server files 246 | *.mdf 247 | *.ldf 248 | *.ndf 249 | 250 | # Business Intelligence projects 251 | *.rdl.data 252 | *.bim.layout 253 | *.bim_*.settings 254 | *.rptproj.rsuser 255 | 256 | # Microsoft Fakes 257 | FakesAssemblies/ 258 | 259 | # GhostDoc plugin setting file 260 | *.GhostDoc.xml 261 | 262 | # Node.js Tools for Visual Studio 263 | .ntvs_analysis.dat 264 | node_modules/ 265 | 266 | # Visual Studio 6 build log 267 | *.plg 268 | 269 | # Visual Studio 6 workspace options file 270 | *.opt 271 | 272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 273 | *.vbw 274 | 275 | # Visual Studio LightSwitch build output 276 | **/*.HTMLClient/GeneratedArtifacts 277 | **/*.DesktopClient/GeneratedArtifacts 278 | **/*.DesktopClient/ModelManifest.xml 279 | **/*.Server/GeneratedArtifacts 280 | **/*.Server/ModelManifest.xml 281 | _Pvt_Extensions 282 | 283 | # Paket dependency manager 284 | .paket/paket.exe 285 | paket-files/ 286 | 287 | # FAKE - F# Make 288 | .fake/ 289 | 290 | # JetBrains Rider 291 | .idea/ 292 | *.sln.iml 293 | 294 | # CodeRush 295 | .cr/ 296 | 297 | # Python Tools for Visual Studio (PTVS) 298 | __pycache__/ 299 | *.pyc 300 | 301 | # Cake - Uncomment if you are using it 302 | # tools/** 303 | # !tools/packages.config 304 | 305 | # Tabs Studio 306 | *.tss 307 | 308 | # Telerik's JustMock configuration file 309 | *.jmconfig 310 | 311 | # BizTalk build output 312 | *.btp.cs 313 | *.btm.cs 314 | *.odx.cs 315 | *.xsd.cs 316 | 317 | # OpenCover UI analysis results 318 | OpenCover/ 319 | 320 | # Azure Stream Analytics local run output 321 | ASALocalRun/ 322 | 323 | # MSBuild Binary and Structured Log 324 | *.binlog 325 | 326 | # NVidia Nsight GPU debugger configuration file 327 | *.nvuser 328 | 329 | # MFractors (Xamarin productivity tool) working folder 330 | .mfractor/ 331 | 332 | # Project specific 333 | !**/3rd Party/* -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace VSDocumentReopen { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | public class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | public static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("VSDocumentReopen.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | public static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | 63 | /// 64 | /// Looks up a localized resource of type System.Drawing.Bitmap. 65 | /// 66 | public static System.Drawing.Bitmap ClearWindowContent_16x { 67 | get { 68 | object obj = ResourceManager.GetObject("ClearWindowContent_16x", resourceCulture); 69 | return ((System.Drawing.Bitmap)(obj)); 70 | } 71 | } 72 | 73 | /// 74 | /// Looks up a localized resource of type System.Drawing.Bitmap. 75 | /// 76 | public static System.Drawing.Bitmap ClearWindowContent_16x_Gray { 77 | get { 78 | object obj = ResourceManager.GetObject("ClearWindowContent_16x_Gray", resourceCulture); 79 | return ((System.Drawing.Bitmap)(obj)); 80 | } 81 | } 82 | 83 | /// 84 | /// Looks up a localized resource of type System.Drawing.Bitmap. 85 | /// 86 | public static System.Drawing.Bitmap FileError_16x { 87 | get { 88 | object obj = ResourceManager.GetObject("FileError_16x", resourceCulture); 89 | return ((System.Drawing.Bitmap)(obj)); 90 | } 91 | } 92 | 93 | /// 94 | /// Looks up a localized resource of type System.Drawing.Bitmap. 95 | /// 96 | public static System.Drawing.Bitmap FileOK_16x { 97 | get { 98 | object obj = ResourceManager.GetObject("FileOK_16x", resourceCulture); 99 | return ((System.Drawing.Bitmap)(obj)); 100 | } 101 | } 102 | 103 | /// 104 | /// Looks up a localized resource of type System.Drawing.Bitmap. 105 | /// 106 | public static System.Drawing.Bitmap OpenFile_16x { 107 | get { 108 | object obj = ResourceManager.GetObject("OpenFile_16x", resourceCulture); 109 | return ((System.Drawing.Bitmap)(obj)); 110 | } 111 | } 112 | 113 | /// 114 | /// Looks up a localized resource of type System.Drawing.Bitmap. 115 | /// 116 | public static System.Drawing.Bitmap OpenFile_16x_Gray { 117 | get { 118 | object obj = ResourceManager.GetObject("OpenFile_16x_Gray", resourceCulture); 119 | return ((System.Drawing.Bitmap)(obj)); 120 | } 121 | } 122 | 123 | /// 124 | /// Looks up a localized resource of type System.Drawing.Bitmap. 125 | /// 126 | public static System.Drawing.Bitmap RemoveGuide_16x { 127 | get { 128 | object obj = ResourceManager.GetObject("RemoveGuide_16x", resourceCulture); 129 | return ((System.Drawing.Bitmap)(obj)); 130 | } 131 | } 132 | 133 | /// 134 | /// Looks up a localized resource of type System.Drawing.Bitmap. 135 | /// 136 | public static System.Drawing.Bitmap RemoveGuide_16x_Gray { 137 | get { 138 | object obj = ResourceManager.GetObject("RemoveGuide_16x_Gray", resourceCulture); 139 | return ((System.Drawing.Bitmap)(obj)); 140 | } 141 | } 142 | 143 | /// 144 | /// Looks up a localized resource of type System.Drawing.Bitmap. 145 | /// 146 | public static System.Drawing.Bitmap RemoveNonExisting_16x { 147 | get { 148 | object obj = ResourceManager.GetObject("RemoveNonExisting_16x", resourceCulture); 149 | return ((System.Drawing.Bitmap)(obj)); 150 | } 151 | } 152 | 153 | /// 154 | /// Looks up a localized resource of type System.Drawing.Bitmap. 155 | /// 156 | public static System.Drawing.Bitmap RemoveNonExisting_16x_Gray { 157 | get { 158 | object obj = ResourceManager.GetObject("RemoveNonExisting_16x_Gray", resourceCulture); 159 | return ((System.Drawing.Bitmap)(obj)); 160 | } 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen/VSPackage.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | VisualStudio Document Reopen 122 | 123 | 124 | Visual Studio Extension to reopen the last closed documents with (CTRL + SHIFT + T) 125 | 126 | 127 | 128 | Resources\icon.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 129 | 130 | 131 | Resources\LICENSE.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;windows-1250 132 | 133 | 134 | Resources\VsToolsMenu.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 135 | 136 | 137 | Resources\ClearWindowContent_16x.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 138 | 139 | 140 | Resources\OpenFile_16x.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 141 | 142 | 143 | Resources\RemoveGuide_16x.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 144 | 145 | -------------------------------------------------------------------------------- /VSDocumentReopen/VSDocumentReopen.Test/Infrastructure/Document/Tracking/DocumentHistoryManagerTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using VSDocumentReopen.Domain.Documents; 5 | using VSDocumentReopen.Infrastructure.Document.Tracking; 6 | using Xunit; 7 | 8 | namespace VSDocumentReopen.Test.Infrastructure.Document.Tracking 9 | { 10 | public class DocumentHistoryManagerTest 11 | { 12 | private readonly DocumentHistoryManager _documentHistoryManager; 13 | private bool _historyChanged; 14 | 15 | public DocumentHistoryManagerTest() 16 | { 17 | _documentHistoryManager = new DocumentHistoryManager(); 18 | _historyChanged = false; 19 | _documentHistoryManager.HistoryChanged += _documentHistoryManager_HistoryChanged; 20 | } 21 | 22 | private void _documentHistoryManager_HistoryChanged(object sender, EventArgs e) => _historyChanged = true; 23 | 24 | [Fact] 25 | public void ItShould_Handle_No_Event_Subscribers() 26 | { 27 | _documentHistoryManager.HistoryChanged -= _documentHistoryManager_HistoryChanged; 28 | 29 | _documentHistoryManager.Add(NullDocument.Instance); 30 | 31 | Assert.False(_historyChanged); 32 | } 33 | 34 | [Fact] 35 | public void ItShould_Be_Empty_ByDefault() 36 | { 37 | Assert.Equal(0, _documentHistoryManager.Count); 38 | } 39 | 40 | [Fact] 41 | public void ItShould_Not_Add_Null() 42 | { 43 | _documentHistoryManager.Add(null); 44 | 45 | Assert.Equal(0, _documentHistoryManager.Count); 46 | } 47 | 48 | [Fact] 49 | public void ItShould_Add_Document() 50 | { 51 | _documentHistoryManager.Add(NullDocument.Instance); 52 | _documentHistoryManager.Add(new ClosedDocument() { FullName = "c:\\test.cs" }); 53 | _documentHistoryManager.Add(NullDocument.Instance); 54 | _documentHistoryManager.Add(new ClosedDocument() { FullName = "c:\\test.cs" }); 55 | 56 | var all = _documentHistoryManager.GetAll(); 57 | 58 | Assert.True(_historyChanged); 59 | Assert.Equal(2, _documentHistoryManager.Count); 60 | Assert.Equal("c:\\test.cs", all.ElementAt(0).FullName); 61 | Assert.Equal(DateTime.MinValue, all.ElementAt(0).ClosedAt); 62 | Assert.Equal(NullDocument.Instance, all.ElementAt(1)); 63 | Assert.Equal(DateTime.MinValue, all.ElementAt(1).ClosedAt); 64 | } 65 | 66 | [Fact] 67 | public void ItShould_Clear_All_Documents_Raise_Event() 68 | { 69 | _documentHistoryManager.Add(NullDocument.Instance); 70 | _documentHistoryManager.Clear(); 71 | 72 | Assert.True(_historyChanged); 73 | Assert.Equal(0, _documentHistoryManager.Count); 74 | } 75 | 76 | [Fact] 77 | public void ItShould_Never_Return_Null_From_RemoveLast_Document() 78 | { 79 | var last = _documentHistoryManager.RemoveLast(); 80 | 81 | Assert.NotNull(last); 82 | Assert.Equal(last, NullDocument.Instance); 83 | Assert.Equal(0, _documentHistoryManager.Count); 84 | } 85 | 86 | [Fact] 87 | public void ItShould_RemoveLast_Document() 88 | { 89 | _documentHistoryManager.Add(new ClosedDocument() { FullName = "c:\\test.cs" }); 90 | _documentHistoryManager.Add(NullDocument.Instance); 91 | 92 | var last = _documentHistoryManager.RemoveLast(); 93 | var all = _documentHistoryManager.GetAll(); 94 | 95 | Assert.Equal(1, _documentHistoryManager.Count); 96 | Assert.Equal(NullDocument.Instance, last); 97 | Assert.Single(all); 98 | Assert.Equal("c:\\test.cs", all.First().FullName); 99 | } 100 | 101 | [Fact] 102 | public void ItShould_Get_NumberOf_Document() 103 | { 104 | _documentHistoryManager.Add(NullDocument.Instance); 105 | _documentHistoryManager.Add(new ClosedDocument() { FullName = "c:\\test.cs", ClosedAt = DateTime.Now }); 106 | _documentHistoryManager.Add(new ClosedDocument() { FullName = "c:\\test2.cs", ClosedAt = DateTime.Now }); 107 | _documentHistoryManager.Add(new ClosedDocument() { FullName = "c:\\test3.cs", ClosedAt = DateTime.Now }); 108 | 109 | var result = _documentHistoryManager.Get(2); 110 | 111 | Assert.Equal(4, _documentHistoryManager.Count); 112 | Assert.Equal(2, result.Count()); 113 | Assert.Equal("c:\\test3.cs", result.ElementAt(0).FullName); 114 | Assert.Equal("c:\\test2.cs", result.ElementAt(1).FullName); 115 | } 116 | 117 | [Fact] 118 | public void ItShould_GetAll_Document() 119 | { 120 | _documentHistoryManager.Add(NullDocument.Instance); 121 | _documentHistoryManager.Add(new ClosedDocument() { FullName = "c:\\test.cs", ClosedAt = DateTime.Now }); 122 | _documentHistoryManager.Add(new ClosedDocument() { FullName = "c:\\test2.cs", ClosedAt = DateTime.Now }); 123 | _documentHistoryManager.Add(new ClosedDocument() { FullName = "c:\\test3.cs", ClosedAt = DateTime.Now }); 124 | 125 | var all = _documentHistoryManager.GetAll(); 126 | 127 | Assert.Equal(4, _documentHistoryManager.Count); 128 | Assert.Equal(4, all.Count()); 129 | Assert.Equal("c:\\test3.cs", all.ElementAt(0).FullName); 130 | Assert.Equal("c:\\test2.cs", all.ElementAt(1).FullName); 131 | Assert.Equal("c:\\test.cs", all.ElementAt(2).FullName); 132 | Assert.Equal(NullDocument.Instance, all.ElementAt(3)); 133 | } 134 | 135 | [Fact] 136 | public void ItShould_Remove_Document() 137 | { 138 | _documentHistoryManager.Add(NullDocument.Instance); 139 | _documentHistoryManager.Add(new ClosedDocument() { FullName = "c:\\test.cs", ClosedAt = DateTime.Now }); 140 | _documentHistoryManager.Add(new ClosedDocument() { FullName = "c:\\test2.cs", ClosedAt = DateTime.Now }); 141 | _documentHistoryManager.Add(new ClosedDocument() { FullName = "c:\\test3.cs", ClosedAt = DateTime.Now }); 142 | 143 | IClosedDocument doc = _documentHistoryManager.Get(2).Last(); //test2.cs 144 | _documentHistoryManager.Remove(doc); 145 | 146 | var all = _documentHistoryManager.GetAll(); 147 | 148 | Assert.True(_historyChanged); 149 | Assert.Equal(3, _documentHistoryManager.Count); 150 | Assert.Equal(3, all.Count()); 151 | Assert.Equal("c:\\test3.cs", all.ElementAt(0).FullName); 152 | Assert.Equal("c:\\test.cs", all.ElementAt(1).FullName); 153 | Assert.Equal(NullDocument.Instance, all.ElementAt(2)); 154 | } 155 | 156 | [Fact] 157 | public void ItShould_Remove_Multiple_Document() 158 | { 159 | _documentHistoryManager.Add(NullDocument.Instance); 160 | _documentHistoryManager.Add(new ClosedDocument() { FullName = "c:\\test.cs", ClosedAt = DateTime.Now}); 161 | _documentHistoryManager.Add(new ClosedDocument() { FullName = "c:\\test2.cs", ClosedAt = DateTime.Now }); 162 | _documentHistoryManager.Add(new ClosedDocument() { FullName = "c:\\test3.cs", ClosedAt = DateTime.Now }); 163 | 164 | var docs = _documentHistoryManager.Get(2); 165 | _documentHistoryManager.Remove(docs); 166 | 167 | var all2 = _documentHistoryManager.GetAll(); 168 | 169 | Assert.True(_historyChanged); 170 | Assert.Equal(2, _documentHistoryManager.Count); 171 | Assert.Equal(2, all2.Count()); 172 | Assert.Equal("c:\\test.cs", all2.ElementAt(0).FullName); 173 | Assert.Equal(NullDocument.Instance, all2.ElementAt(1)); 174 | } 175 | 176 | [Fact] 177 | public void ItShould_Handle_Null_Initialize() 178 | { 179 | _documentHistoryManager.Initialize(null); 180 | 181 | Assert.True(_historyChanged); 182 | Assert.Equal(0, _documentHistoryManager.Count); 183 | } 184 | 185 | [Fact] 186 | public void ItShould_Initialize_History() 187 | { 188 | var docs = new List() 189 | { 190 | NullDocument.Instance, 191 | new ClosedDocument() { FullName = "c:\\test.cs", ClosedAt = new DateTime(2017, 08, 05) }, 192 | NullDocument.Instance, 193 | new ClosedDocument() { FullName = "c:\\test2.cs", ClosedAt = new DateTime(2017, 08, 06) }, 194 | new ClosedDocument() { FullName = "c:\\test2.cs", ClosedAt = new DateTime(2017, 08, 07) }, 195 | }; 196 | 197 | _documentHistoryManager.Initialize(docs); 198 | var all = _documentHistoryManager.GetAll(); 199 | 200 | Assert.True(_historyChanged); 201 | Assert.Equal(3, _documentHistoryManager.Count); 202 | Assert.Equal("c:\\test2.cs", all.ElementAt(0).FullName); 203 | Assert.Equal(new DateTime(2017, 08, 07), all.ElementAt(0).ClosedAt); 204 | Assert.Equal("c:\\test.cs", all.ElementAt(1).FullName); 205 | Assert.Equal(new DateTime(2017, 08, 05), all.ElementAt(1).ClosedAt); 206 | Assert.Equal(NullDocument.Instance, all.ElementAt(2)); 207 | } 208 | } 209 | } --------------------------------------------------------------------------------