├── .editorconfig ├── .gitattributes ├── .github └── workflows │ └── dotnet.yml ├── .gitignore ├── BrightExtensions.sln ├── BrightGit.Extensibility ├── .vsextension │ └── string-resources.json ├── BrightGit.Extensibility.csproj ├── Commands │ ├── EFGitMigrationDownCommand.cs │ ├── EFGitMigrationHookAddCommand.cs │ ├── EFGitMigrationHookCheckCommand.cs │ ├── EFGitMigrationHookRemoveCommand.cs │ ├── EFGitTest.cs │ ├── TabsRestoreCommand.cs │ ├── TabsSaveCommand.cs │ └── TabsSortCommand.cs ├── ExtensionEntrypoint.cs ├── Helpers │ ├── LibGit2SharpHelper.cs │ └── VSHelper.cs ├── Listeners │ ├── GitSharpHookListener.cs │ ├── SolutionSubscriptionObserver.cs │ └── SolutionTrackerObserver.cs ├── Meta.cs ├── Models │ ├── TabDocumentInfo.cs │ └── TabsInfo.cs ├── Services │ ├── DialogService.cs │ ├── EFCoreManagerService.cs │ ├── GitFileWatcherService.cs │ ├── GitSharpHookService.cs │ ├── IDialogService.cs │ ├── SettingsData.cs │ ├── SettingsEFCoreData.cs │ ├── SettingsService.cs │ ├── SettingsTabsData.cs │ ├── TabManagerService.cs │ ├── TabsStorageData.cs │ └── TabsStorageService.cs └── Windows │ ├── SettingsWindow.cs │ ├── SettingsWindowCommand.cs │ ├── SettingsWindowContent.cs │ ├── SettingsWindowContent.xaml │ ├── SettingsWindowViewModel.cs │ ├── TabsWindow.cs │ ├── TabsWindowCommand.cs │ ├── TabsWindowContent.cs │ ├── TabsWindowContent.xaml │ └── TabsWindowViewModel.cs ├── BrightGit.SharpAutoMigrator ├── BrightGit.SharpAutoMigrator.csproj ├── DotnetHelper.cs ├── Program.cs ├── Properties │ └── PublishProfiles │ │ └── FolderProfile.pubxml ├── Readme.txt └── Resources │ └── post-checkout ├── BrightGit.SharpCommon ├── BrightGit.SharpCommon.csproj ├── DotnetHelper.cs ├── Helpers │ ├── GitHelper.cs │ └── MigratorHelper.cs ├── Models │ ├── GitHookType.cs │ ├── RunData.cs │ └── RunType.cs ├── SharpHookHelper.cs └── SharpRunHelper.cs ├── BrightGit.SharpHook ├── BrightGit.SharpHook.csproj ├── Program.cs └── Properties │ ├── PublishProfiles │ └── FolderProfile.pubxml │ └── launchSettings.json ├── BrightGit.SharpRun ├── BrightGit.SharpRun.csproj └── Program.cs ├── BrightXaml.Extensibility ├── .vsextension │ └── string-resources.json ├── BrightXaml.Extensibility.csproj ├── Commands │ ├── CleanBinAndObjCommand.cs │ ├── DevOpenSolutionCommand.cs │ ├── ExtractClassesInUseCommand.cs │ ├── ExtractFolderCommand.cs │ ├── ExtractViewCommand.cs │ ├── FormatXamlAllCommand.cs │ ├── FormatXamlCommand.cs │ ├── HelloWorldCommand.cs │ ├── KillXamlDesignerCommand.cs │ ├── PropertyToINPCAllCommand.cs │ ├── PropertyToINPCCommand.cs │ ├── ShowCodeBehindCommand.cs │ ├── ShowDefinitionCommand.cs │ ├── ShowViewCommand.cs │ ├── ShowViewModelCommand.cs │ └── ToggleViewModelCommand.cs ├── ExtensionEntrypoint.cs ├── Helpers │ ├── ClipboardHelper.cs │ └── VSHelper.cs ├── Listeners │ └── ShowDefinitionListener.cs ├── Meta.cs ├── Models │ ├── BindingDefinitionOffsets.cs │ ├── ComboIntData.cs │ └── CommandInfo.cs ├── Properties │ └── launchSettings.json ├── Services │ ├── DialogService.cs │ ├── IDialogService.cs │ ├── SettingsData.cs │ ├── SettingsFormatXamlData.cs │ ├── SettingsGoToBindingData.cs │ ├── SettingsPropInpcData.cs │ └── SettingsService.cs ├── Utilities │ ├── ExportFilesHelper.cs │ ├── PropToInpcHelper.cs │ ├── PropertyLineData.cs │ ├── ShowDefinitionHelper.cs │ ├── ViewModelHelper.cs │ └── XamlFormatter.cs └── Windows │ ├── ChooseItemWindow.cs │ ├── ChooseItemWindowCommand.cs │ ├── ChooseItemWindowContent.cs │ ├── ChooseItemWindowContent.xaml │ ├── ChooseItemWindowViewModel.cs │ ├── HelpWindow.cs │ ├── HelpWindowCommand.cs │ ├── HelpWindowContent.cs │ ├── HelpWindowContent.xaml │ ├── HelpWindowViewModel.cs │ ├── ProgressWindow.cs │ ├── ProgressWindowCommand.cs │ ├── ProgressWindowContent.cs │ ├── ProgressWindowContent.xaml │ ├── ProgressWindowViewModel.cs │ ├── SettingsWindow.cs │ ├── SettingsWindowCommand.cs │ ├── SettingsWindowContent.cs │ ├── SettingsWindowContent.xaml │ └── SettingsWindowViewModel.cs ├── BrightXaml.ExtensibilityTests ├── BrightXaml.ExtensibilityTests.csproj ├── MockEntrypoint.cs ├── Resources │ ├── FullDocument01_Bad.xml │ ├── FullDocument01_Good.xml │ ├── FullDocument02_Bad.xml │ ├── FullDocument02_Good.xml │ ├── FullDocument03_Bad.xml │ ├── FullDocument03_Good.xml │ ├── FullDocument04_Bad.xml │ ├── FullDocument04_Good.xml │ ├── MultipleLine01_Bad.xml │ ├── MultipleLine01_Good.xml │ ├── MultipleLine02_Bad.xml │ ├── MultipleLine02_Good.xml │ ├── MultipleLine03_Bad.xml │ ├── MultipleLine03_Good.xml │ ├── MultipleLine04_Bad.xml │ ├── MultipleLine04_Good.xml │ ├── MultipleLine05_Bad.xml │ ├── MultipleLine05_Good.xml │ ├── MultipleLine06_Bad.xml │ ├── MultipleLine06_Good.xml │ ├── MultipleLine07_Bad.xml │ ├── MultipleLine07_Good.xml │ ├── MultipleLine08_Bad.xml │ ├── MultipleLine08_Good.xml │ ├── RemoveEmpty_MultipleLine01_Bad.xml │ ├── RemoveEmpty_MultipleLine01_Good.xml │ ├── RemoveEmpty_MultipleLine02_Bad.xml │ ├── RemoveEmpty_MultipleLine02_Good.xml │ ├── SingleLine01_Bad.xml │ ├── SingleLine01_Good.xml │ ├── SingleLine02_Bad.xml │ ├── SingleLine02_Good.xml │ ├── SingleLine03_Bad.xml │ ├── SingleLine03_Good.xml │ ├── SingleLine04_Bad.xml │ ├── SingleLine04_Good.xml │ ├── SingleLine05_Bad.xml │ ├── SingleLine05_Good.xml │ ├── SingleLineComment01_Bad.xml │ ├── SingleLineComment01_Good.xml │ ├── SingleLineComment02_Bad.xml │ ├── SingleLineComment02_Good.xml │ ├── SplitTagsPerLine01_Bad.xml │ ├── SplitTagsPerLine01_Good.xml │ ├── SplitTagsPerLine02_Bad.xml │ └── SplitTagsPerLine02_Good.xml ├── TestHelper.cs └── Utilities │ ├── PropToInpcHelperTests.cs │ ├── ShowDefinitionHelperTests.cs │ ├── ViewModelHelperTests.cs │ └── XamlFormatterTests.cs ├── LICENSE └── README.md /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | 3 | # CEE0027: String not localized 4 | dotnet_diagnostic.CEE0027.severity = silent 5 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.github/workflows/dotnet.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a .NET project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net 3 | 4 | name: .NET 5 | 6 | on: 7 | push: 8 | branches: [ "main" ] 9 | pull_request: 10 | branches: [ "main" ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: windows-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | 20 | - name: Setup .NET 21 | uses: actions/setup-dotnet@v4 22 | with: 23 | dotnet-version: 8.0.x 24 | 25 | - name: Restore dependencies 26 | run: dotnet restore 27 | 28 | - name: Build 29 | run: dotnet build --no-restore --configuration Release 30 | 31 | - name: Test 32 | run: dotnet test --no-build --configuration Release --verbosity normal 33 | 34 | - name: Upload VSIX artifact (BrightXaml) 35 | uses: actions/upload-artifact@v4 36 | with: 37 | name: vsix-artifact 38 | path: '**/BrightXaml*.vsix' -------------------------------------------------------------------------------- /BrightGit.Extensibility/.vsextension/string-resources.json: -------------------------------------------------------------------------------- 1 | { 2 | "BrightGit.Extensibility.Command1.DisplayName": "Sample Remote Command" 3 | } 4 | -------------------------------------------------------------------------------- /BrightGit.Extensibility/BrightGit.Extensibility.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net8.0-windows 4 | enable 5 | disable 6 | 7 | 8 | 9 | win-x64 10 | 11 | 1.0.0.0 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | MSBuild:Compile 20 | 21 | 22 | MSBuild:Compile 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /BrightGit.Extensibility/Commands/EFGitMigrationHookAddCommand.cs: -------------------------------------------------------------------------------- 1 | namespace BrightGit.Extensibility.Commands; 2 | 3 | using BrightGit.Extensibility.Helpers; 4 | using BrightGit.SharpCommon; 5 | using Microsoft; 6 | using Microsoft.VisualStudio.Extensibility; 7 | using Microsoft.VisualStudio.Extensibility.Commands; 8 | using Microsoft.VisualStudio.Extensibility.Shell; 9 | using System.Diagnostics; 10 | using System.Threading; 11 | using System.Threading.Tasks; 12 | 13 | [VisualStudioContribution] 14 | internal class EFGitMigrationHookAddCommand : Command 15 | { 16 | private readonly TraceSource logger; 17 | 18 | public EFGitMigrationHookAddCommand(TraceSource traceSource) 19 | { 20 | this.logger = Requires.NotNull(traceSource, nameof(traceSource)); 21 | } 22 | 23 | /// 24 | public override CommandConfiguration CommandConfiguration => new(displayName: "EF Auto Migrator - Add Hook") 25 | { 26 | //Placements = [CommandPlacement.KnownPlacements.ExtensionsMenu], 27 | Icon = new(ImageMoniker.KnownValues.AddLink, IconSettings.IconAndText), 28 | }; 29 | 30 | /// 31 | public override Task InitializeAsync(CancellationToken cancellationToken) 32 | { 33 | // Use InitializeAsync for any one-time setup or initialization. 34 | return base.InitializeAsync(cancellationToken); 35 | } 36 | 37 | /// 38 | public override async Task ExecuteCommandAsync(IClientContext context, CancellationToken cancellationToken) 39 | { 40 | try 41 | { 42 | var solutionDirectoryPath = await VSHelper.GetSolutionDirectoryAsync(Extensibility.Workspaces(), cancellationToken); 43 | if (string.IsNullOrWhiteSpace(solutionDirectoryPath)) 44 | { 45 | await Extensibility.Shell().ShowPromptAsync("Please open a solution before adding the hook.", PromptOptions.OK, cancellationToken); 46 | return; 47 | } 48 | 49 | SharpHookHelper.AddAutoMigratorHook(solutionDirectoryPath); 50 | await Extensibility.Shell().ShowPromptAsync($"Auto Migration hook added to repo {Path.GetDirectoryName(solutionDirectoryPath)}.", PromptOptions.OK, cancellationToken); 51 | } 52 | catch (Exception ex) 53 | { 54 | logger.TraceEvent(TraceEventType.Error, 0, ex.Message); 55 | await Extensibility.Shell().ShowPromptAsync(ex.Message, PromptOptions.OK, cancellationToken); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /BrightGit.Extensibility/Commands/EFGitMigrationHookCheckCommand.cs: -------------------------------------------------------------------------------- 1 | namespace BrightGit.Extensibility.Commands; 2 | 3 | using BrightGit.Extensibility.Helpers; 4 | using BrightGit.SharpCommon; 5 | using Microsoft; 6 | using Microsoft.VisualStudio.Extensibility; 7 | using Microsoft.VisualStudio.Extensibility.Commands; 8 | using Microsoft.VisualStudio.Extensibility.Shell; 9 | using System.Diagnostics; 10 | using System.Threading; 11 | using System.Threading.Tasks; 12 | 13 | [VisualStudioContribution] 14 | internal class EFGitMigrationHookCheckCommand : Command 15 | { 16 | private readonly TraceSource logger; 17 | 18 | public EFGitMigrationHookCheckCommand(TraceSource traceSource) 19 | { 20 | this.logger = Requires.NotNull(traceSource, nameof(traceSource)); 21 | } 22 | 23 | /// 24 | public override CommandConfiguration CommandConfiguration => new(displayName: "EF Auto Migrator - Check Hook State") 25 | { 26 | //Placements = [CommandPlacement.KnownPlacements.ExtensionsMenu], 27 | Icon = new(ImageMoniker.KnownValues.LinkAlert, IconSettings.IconAndText), 28 | }; 29 | 30 | /// 31 | public override Task InitializeAsync(CancellationToken cancellationToken) 32 | { 33 | // Use InitializeAsync for any one-time setup or initialization. 34 | return base.InitializeAsync(cancellationToken); 35 | } 36 | 37 | /// 38 | public override async Task ExecuteCommandAsync(IClientContext context, CancellationToken cancellationToken) 39 | { 40 | try 41 | { 42 | var solutionDirectoryPath = await VSHelper.GetSolutionDirectoryAsync(Extensibility.Workspaces(), cancellationToken); 43 | if (string.IsNullOrWhiteSpace(solutionDirectoryPath)) 44 | { 45 | await Extensibility.Shell().ShowPromptAsync("Please open a solution before checking the hook state.", PromptOptions.OK, cancellationToken); 46 | return; 47 | } 48 | 49 | var isHookActive = SharpHookHelper.CheckAutoMigratorHook(solutionDirectoryPath); 50 | var msg = isHookActive ? "Auto Migration hook is active for this repo." : "Auto Migration hook is NOT active for this repo."; 51 | await Extensibility.Shell().ShowPromptAsync(msg, PromptOptions.OK, cancellationToken); 52 | } 53 | catch (Exception ex) 54 | { 55 | logger.TraceEvent(TraceEventType.Error, 0, ex.Message); 56 | await Extensibility.Shell().ShowPromptAsync(ex.Message, PromptOptions.OK, cancellationToken); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /BrightGit.Extensibility/Commands/EFGitMigrationHookRemoveCommand.cs: -------------------------------------------------------------------------------- 1 | namespace BrightGit.Extensibility.Commands; 2 | 3 | using BrightGit.Extensibility.Helpers; 4 | using BrightGit.SharpCommon; 5 | using Microsoft; 6 | using Microsoft.VisualStudio.Extensibility; 7 | using Microsoft.VisualStudio.Extensibility.Commands; 8 | using Microsoft.VisualStudio.Extensibility.Shell; 9 | using System.Diagnostics; 10 | using System.Threading; 11 | using System.Threading.Tasks; 12 | 13 | [VisualStudioContribution] 14 | internal class EFGitMigrationHookRemoveCommand : Command 15 | { 16 | private readonly TraceSource logger; 17 | 18 | public EFGitMigrationHookRemoveCommand(TraceSource traceSource) 19 | { 20 | this.logger = Requires.NotNull(traceSource, nameof(traceSource)); 21 | } 22 | 23 | /// 24 | public override CommandConfiguration CommandConfiguration => new(displayName: "EF Auto Migrator - Remove Hook") 25 | { 26 | //Placements = [CommandPlacement.KnownPlacements.ExtensionsMenu], 27 | Icon = new(ImageMoniker.KnownValues.RemoveLink, IconSettings.IconAndText), 28 | }; 29 | 30 | /// 31 | public override Task InitializeAsync(CancellationToken cancellationToken) 32 | { 33 | // Use InitializeAsync for any one-time setup or initialization. 34 | return base.InitializeAsync(cancellationToken); 35 | } 36 | 37 | /// 38 | public override async Task ExecuteCommandAsync(IClientContext context, CancellationToken cancellationToken) 39 | { 40 | try 41 | { 42 | var solutionDirectoryPath = await VSHelper.GetSolutionDirectoryAsync(Extensibility.Workspaces(), cancellationToken); 43 | if (string.IsNullOrWhiteSpace(solutionDirectoryPath)) 44 | { 45 | await Extensibility.Shell().ShowPromptAsync("Please open a solution before removing the hook.", PromptOptions.OK, cancellationToken); 46 | return; 47 | } 48 | 49 | SharpHookHelper.RemoveAutoMigratorHook(solutionDirectoryPath); 50 | await Extensibility.Shell().ShowPromptAsync($"Auto Migration hook removed from {Path.GetDirectoryName(solutionDirectoryPath)}.", PromptOptions.OK, cancellationToken); 51 | } 52 | catch (Exception ex) 53 | { 54 | logger.TraceEvent(TraceEventType.Error, 0, ex.Message); 55 | await Extensibility.Shell().ShowPromptAsync(ex.Message, PromptOptions.OK, cancellationToken); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /BrightGit.Extensibility/Commands/EFGitTest.cs: -------------------------------------------------------------------------------- 1 | namespace BrightGit.Extensibility.Commands; 2 | using LibGit2Sharp; 3 | using Microsoft; 4 | using Microsoft.VisualStudio.Extensibility; 5 | using Microsoft.VisualStudio.Extensibility.Commands; 6 | using Microsoft.VisualStudio.Extensibility.Shell; 7 | using System.Diagnostics; 8 | using System.Reflection; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | 12 | [VisualStudioContribution] 13 | internal class EFGitTest : Command 14 | { 15 | private readonly TraceSource logger; 16 | 17 | public EFGitTest(TraceSource traceSource) 18 | { 19 | this.logger = Requires.NotNull(traceSource, nameof(traceSource)); 20 | } 21 | 22 | /// 23 | public override CommandConfiguration CommandConfiguration => new(displayName: "(Dev) EF Core - Test Git Library") 24 | { 25 | // Use in debug only for testing, might be deleted later. 26 | #if DEBUG 27 | Placements = [CommandPlacement.KnownPlacements.ExtensionsMenu], 28 | #endif 29 | Icon = new(ImageMoniker.KnownValues.Extension, IconSettings.IconAndText), 30 | }; 31 | 32 | /// 33 | public override Task InitializeAsync(CancellationToken cancellationToken) 34 | { 35 | // Use InitializeAsync for any one-time setup or initialization. 36 | return base.InitializeAsync(cancellationToken); 37 | } 38 | 39 | /// 40 | public override async Task ExecuteCommandAsync(IClientContext context, CancellationToken cancellationToken) 41 | { 42 | try 43 | { 44 | // Set the native library path for LibGit2Sharp when inside VS extension (VSIX). 45 | string assemblyFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); 46 | assemblyFolder = assemblyFolder[..^2]; 47 | GlobalSettings.NativeLibraryPath = Path.Combine(assemblyFolder, "runtimes", "win-x64", "native"); 48 | 49 | var repo = new Repository(""); 50 | await Extensibility.Shell().ShowPromptAsync("Success", PromptOptions.OK, cancellationToken); 51 | } 52 | catch (Exception ex) 53 | { 54 | logger.TraceEvent(TraceEventType.Error, 0, ex.Message); 55 | await Extensibility.Shell().ShowPromptAsync(ex.Message, PromptOptions.OK, cancellationToken); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /BrightGit.Extensibility/Commands/TabsRestoreCommand.cs: -------------------------------------------------------------------------------- 1 | namespace BrightGit.Extensibility.Commands; 2 | 3 | using BrightGit.Extensibility.Helpers; 4 | using BrightGit.Extensibility.Services; 5 | using Microsoft; 6 | using Microsoft.VisualStudio.Extensibility; 7 | using Microsoft.VisualStudio.Extensibility.Commands; 8 | using Microsoft.VisualStudio.Extensibility.Shell; 9 | using System.Diagnostics; 10 | using System.Threading; 11 | using System.Threading.Tasks; 12 | 13 | [VisualStudioContribution] 14 | internal class TabsRestoreCommand : Command 15 | { 16 | private readonly TraceSource logger; 17 | private readonly TabsStorageService tabsStorageService; 18 | private readonly TabManagerService tabManagerService; 19 | 20 | public TabsRestoreCommand(TraceSource traceSource, TabsStorageService tabsStorageService, TabManagerService tabManagerService) 21 | { 22 | this.logger = Requires.NotNull(traceSource, nameof(traceSource)); 23 | this.tabsStorageService = tabsStorageService; 24 | this.tabManagerService = tabManagerService; 25 | } 26 | 27 | /// 28 | public override CommandConfiguration CommandConfiguration => new(displayName: "(WIP) Restore Tabs") 29 | { 30 | //Placements = [CommandPlacement.KnownPlacements.ExtensionsMenu], 31 | Icon = new(ImageMoniker.KnownValues.RestoreDefaultView, IconSettings.IconAndText), 32 | }; 33 | 34 | /// 35 | public override Task InitializeAsync(CancellationToken cancellationToken) 36 | { 37 | // Use InitializeAsync for any one-time setup or initialization. 38 | return base.InitializeAsync(cancellationToken); 39 | } 40 | 41 | /// 42 | public override async Task ExecuteCommandAsync(IClientContext context, CancellationToken cancellationToken) 43 | { 44 | var shell = Extensibility.Shell(); 45 | var documents = Extensibility.Documents(); 46 | var configuration = Extensibility.Configuration(); 47 | var workspaces = Extensibility.Workspaces(); 48 | 49 | try 50 | { 51 | var sw = Stopwatch.StartNew(); 52 | 53 | // Check if we are in a solution. 54 | var solutionName = await VSHelper.GetSolutionNameAsync(workspaces, cancellationToken); 55 | if (string.IsNullOrWhiteSpace(solutionName)) 56 | { 57 | await shell.ShowPromptAsync("Please open a solution before restoring tabs", PromptOptions.OK, cancellationToken); 58 | return; 59 | } 60 | 61 | // Format the branch name. 62 | string gitHeadPath = Path.Combine(await VSHelper.GetSolutionDirectoryAsync(workspaces, cancellationToken), ".git", "HEAD"); 63 | string gitBranchName = string.Empty; 64 | if (File.Exists(gitHeadPath)) 65 | { 66 | gitBranchName = File.ReadAllText(gitHeadPath).Split('/').LastOrDefault().TrimEnd('\n'); 67 | gitBranchName = $"{gitBranchName}"; 68 | } 69 | 70 | var tabsRestored = await tabManagerService.RestoreTabsAsync(true, gitBranchName, shell, documents, workspaces, cancellationToken); 71 | 72 | sw.Stop(); 73 | Debug.WriteLine($"Restored {tabsRestored?.Tabs.Count} tabs for {solutionName}.{gitBranchName} ({sw.ElapsedMilliseconds}ms)"); 74 | await shell.ShowPromptAsync($"Restored {tabsRestored?.Tabs.Count} tabs for {solutionName}.{gitBranchName} ({sw.ElapsedMilliseconds}ms)", PromptOptions.OK, cancellationToken); 75 | } 76 | catch (Exception ex) 77 | { 78 | Debug.WriteLine(ex); 79 | await shell.ShowPromptAsync($"Error restoring tabs: {ex.Message}", PromptOptions.OK, cancellationToken); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /BrightGit.Extensibility/Commands/TabsSaveCommand.cs: -------------------------------------------------------------------------------- 1 | namespace BrightGit.Extensibility.Commands; 2 | 3 | using BrightGit.Extensibility.Helpers; 4 | using BrightGit.Extensibility.Services; 5 | using Microsoft; 6 | using Microsoft.VisualStudio.Extensibility; 7 | using Microsoft.VisualStudio.Extensibility.Commands; 8 | using Microsoft.VisualStudio.Extensibility.Shell; 9 | using System.Diagnostics; 10 | using System.Threading; 11 | using System.Threading.Tasks; 12 | 13 | [VisualStudioContribution] 14 | internal class TabsSaveCommand : Command 15 | { 16 | private readonly TraceSource logger; 17 | private readonly SettingsService settingsService; 18 | private readonly TabsStorageService tabsStorageService; 19 | private readonly TabManagerService tabManagerService; 20 | 21 | public TabsSaveCommand(TraceSource traceSource, 22 | SettingsService settingsService, 23 | TabsStorageService tabsStorageService, 24 | TabManagerService tabManagerService) 25 | { 26 | this.logger = Requires.NotNull(traceSource, nameof(traceSource)); 27 | this.settingsService = settingsService; 28 | this.tabsStorageService = tabsStorageService; 29 | this.tabManagerService = tabManagerService; 30 | } 31 | 32 | /// 33 | public override CommandConfiguration CommandConfiguration => new(displayName: "(WIP) Save Tabs") 34 | { 35 | //Placements = [CommandPlacement.KnownPlacements.ExtensionsMenu], 36 | Icon = new(ImageMoniker.KnownValues.SaveFileDialog, IconSettings.IconAndText), 37 | }; 38 | 39 | /// 40 | public override Task InitializeAsync(CancellationToken cancellationToken) 41 | { 42 | // Use InitializeAsync for any one-time setup or initialization. 43 | return base.InitializeAsync(cancellationToken); 44 | } 45 | 46 | /// 47 | public override async Task ExecuteCommandAsync(IClientContext context, CancellationToken cancellationToken) 48 | { 49 | var shell = Extensibility.Shell(); 50 | var documents = Extensibility.Documents(); 51 | var configuration = Extensibility.Configuration(); 52 | var workspaces = Extensibility.Workspaces(); 53 | 54 | try 55 | { 56 | var sw = Stopwatch.StartNew(); 57 | 58 | // Check if we are in a solution. 59 | var solutionName = await VSHelper.GetSolutionNameAsync(workspaces, cancellationToken); 60 | if (string.IsNullOrWhiteSpace(solutionName)) 61 | { 62 | await shell.ShowPromptAsync("Please open a solution before saving tabs", PromptOptions.OK, cancellationToken); 63 | return; 64 | } 65 | 66 | // Format the branch name. 67 | string gitHeadPath = Path.Combine(await VSHelper.GetSolutionDirectoryAsync(workspaces, cancellationToken), ".git", "HEAD"); 68 | string gitBranchName = string.Empty; 69 | if (File.Exists(gitHeadPath)) 70 | { 71 | gitBranchName = File.ReadAllText(gitHeadPath).Split('/').LastOrDefault().TrimEnd('\n'); 72 | gitBranchName = $"{gitBranchName}"; 73 | } 74 | 75 | // Debug only. 76 | var openedDocuments = await documents.GetOpenDocumentsAsync(cancellationToken); 77 | Debug.WriteLine($"TavsSaveCommand - openedDocuments: {openedDocuments.Count}"); 78 | 79 | // Save tabs. 80 | var tabsSaved = await tabManagerService.SaveTabsAsync(true, gitBranchName, shell, documents, workspaces, cancellationToken); 81 | 82 | sw.Stop(); 83 | Debug.WriteLine($"Saved tabs {tabsSaved?.Tabs?.Count ?? 0} for {tabsSaved?.SolutionName}.{gitBranchName} ({sw.ElapsedMilliseconds}ms)"); 84 | await shell.ShowPromptAsync($"Saved {tabsSaved?.Tabs.Count ?? 0} tabs for {tabsSaved?.SolutionName}.{gitBranchName} ({sw.ElapsedMilliseconds}ms)", PromptOptions.OK, cancellationToken); 85 | } 86 | catch (Exception ex) 87 | { 88 | Debug.WriteLine(ex); 89 | await shell.ShowPromptAsync($"Error saving tabs: {ex.Message}", PromptOptions.OK, cancellationToken); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /BrightGit.Extensibility/Commands/TabsSortCommand.cs: -------------------------------------------------------------------------------- 1 | namespace BrightGit.Extensibility.Commands; 2 | 3 | using Microsoft; 4 | using Microsoft.VisualStudio.Extensibility; 5 | using Microsoft.VisualStudio.Extensibility.Commands; 6 | using Microsoft.VisualStudio.Extensibility.Shell; 7 | using Microsoft.VisualStudio.RpcContracts.Documents; 8 | using System.Diagnostics; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | 12 | [VisualStudioContribution] 13 | internal class TabsSortCommand : Command 14 | { 15 | private readonly TraceSource logger; 16 | 17 | public TabsSortCommand(TraceSource traceSource) 18 | { 19 | this.logger = Requires.NotNull(traceSource, nameof(traceSource)); 20 | } 21 | 22 | /// 23 | public override CommandConfiguration CommandConfiguration => new(displayName: "(WIP) Sort Tabs") 24 | { 25 | //Placements = [CommandPlacement.KnownPlacements.ExtensionsMenu], 26 | Icon = new(ImageMoniker.KnownValues.SaveFileDialog, IconSettings.IconAndText), 27 | }; 28 | 29 | /// 30 | public override Task InitializeAsync(CancellationToken cancellationToken) 31 | { 32 | // Use InitializeAsync for any one-time setup or initialization. 33 | return base.InitializeAsync(cancellationToken); 34 | } 35 | 36 | /// 37 | public override async Task ExecuteCommandAsync(IClientContext context, CancellationToken cancellationToken) 38 | { 39 | try 40 | { 41 | var sw = Stopwatch.StartNew(); 42 | 43 | var openedDocuments = await Extensibility.Documents().GetOpenDocumentsAsync(cancellationToken); 44 | if (openedDocuments.Any()) 45 | { 46 | var sortedDocuments = openedDocuments.OrderBy(d => Path.GetFileName(d.Moniker.LocalPath)).ToList(); 47 | 48 | // TODO: This is a terrible way to do this... I couldn't find a better way to simply reorder the tabs with the API. 49 | 50 | // Close all documents. 51 | //await Task.WhenAll(openedDocuments.Select(document => document.CloseAsync(SaveDocumentOption.PromptSave, Extensibility, cancellationToken))); 52 | await Task.WhenAll(openedDocuments.Select(document => Extensibility.Documents().CloseDocumentAsync(document.Moniker, SaveDocumentOption.PromptSave, cancellationToken))); 53 | 54 | // Open documents in sorted order. 55 | await Task.WhenAll(sortedDocuments.Select(document => Extensibility.Documents().OpenDocumentAsync(document.Moniker, cancellationToken))); 56 | 57 | sw.Stop(); 58 | Debug.WriteLine($"Sorted {openedDocuments.Count} tabs in {sw.ElapsedMilliseconds}ms"); 59 | await Extensibility.Shell().ShowPromptAsync($"Sorted {openedDocuments.Count} tabs in {sw.ElapsedMilliseconds}ms", PromptOptions.OK, cancellationToken); 60 | } 61 | else 62 | { 63 | await Extensibility.Shell().ShowPromptAsync("No tabs to sort", PromptOptions.OK, cancellationToken); 64 | return; 65 | } 66 | } 67 | catch (Exception ex) 68 | { 69 | Debug.WriteLine(ex); 70 | await Extensibility.Shell().ShowPromptAsync("An error occurred while sorting tabs", PromptOptions.OK, cancellationToken); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /BrightGit.Extensibility/ExtensionEntrypoint.cs: -------------------------------------------------------------------------------- 1 | namespace BrightGit.Extensibility; 2 | 3 | using BrightGit.Extensibility.Commands; 4 | using BrightGit.Extensibility.Listeners; 5 | using BrightGit.Extensibility.Services; 6 | using BrightGit.Extensibility.Windows; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using Microsoft.VisualStudio.Extensibility; 9 | using Microsoft.VisualStudio.Extensibility.Commands; 10 | using Microsoft.VisualStudio.ProjectSystem.Query; 11 | using System.Threading; 12 | using System.Threading.Tasks; 13 | 14 | /// 15 | /// Extension entrypoint for the VisualStudio.Extensibility extension. 16 | /// 17 | [VisualStudioContribution] 18 | internal class ExtensionEntrypoint : Extension 19 | { 20 | /// 21 | public override ExtensionConfiguration ExtensionConfiguration => new() 22 | { 23 | Metadata = new( 24 | id: "BrightGit.14722f3b-45e7-4e62-bfde-b25896550871", 25 | version: this.ExtensionAssemblyVersion, 26 | publisherName: "Luis Henrique Goll", 27 | displayName: "Bright Git Extension", 28 | description: "Bright Commands and Automations with C# developers using git source control in mind!"), 29 | LoadedWhen = ActivationConstraint.SolutionState(SolutionState.FullyLoaded), 30 | //LoadedWhen = ActivationConstraint.SolutionState(SolutionState.Exists) 31 | }; 32 | 33 | /// 34 | protected override void InitializeServices(IServiceCollection serviceCollection) 35 | { 36 | base.InitializeServices(serviceCollection); 37 | 38 | // You can configure dependency injection here by adding services to the serviceCollection. 39 | serviceCollection.AddTransient(provider => new DialogService()); 40 | serviceCollection.AddSingleton(); 41 | serviceCollection.AddSingleton(); 42 | serviceCollection.AddSingleton(); 43 | serviceCollection.AddSingleton(); 44 | serviceCollection.AddSingleton(); 45 | serviceCollection.AddSingleton(); 46 | } 47 | 48 | protected override async Task OnInitializedAsync(VisualStudioExtensibility extensibility, CancellationToken cancellationToken) 49 | { 50 | // Start monitoring Git hooks. 51 | //base.ServiceProvider.GetRequiredService().Extensibility = extensibility; 52 | //_ = base.ServiceProvider.GetRequiredService().StartMonitoringAsync(); 53 | 54 | // Start monitoring Git HEAD. 55 | base.ServiceProvider.GetRequiredService().Extensibility = extensibility; 56 | _ = base.ServiceProvider.GetRequiredService().StartMonitoringAsync(); 57 | 58 | // Subscribe to solution changes. 59 | var solutions = await extensibility.Workspaces().QuerySolutionAsync(solution => solution.With(p => p.Path), cancellationToken); 60 | var firstSolution = solutions.FirstOrDefault(); 61 | if (firstSolution != null) 62 | { 63 | var observer = new SolutionSubscriptionObserver(); 64 | var subscribe = firstSolution.Projects.With(p => p.Path).SubscribeAsync(observer, CancellationToken.None); 65 | } 66 | 67 | // Carry on with the initialization. 68 | await base.OnInitializedAsync(extensibility, cancellationToken); 69 | } 70 | 71 | [VisualStudioContribution] 72 | //public static MenuConfiguration MyMenu => new("%MyMenu.DisplayName%") 73 | public static MenuConfiguration MyMenu => new("Bright Git") 74 | { 75 | Placements = new[] 76 | { 77 | CommandPlacement.KnownPlacements.ExtensionsMenu 78 | }, 79 | Children = new[] 80 | { 81 | MenuChild.Command(), 82 | MenuChild.Separator, 83 | MenuChild.Command(), 84 | MenuChild.Command(), 85 | MenuChild.Command(), 86 | 87 | MenuChild.Separator, 88 | MenuChild.Command(), 89 | MenuChild.Command(), 90 | #if DEBUG 91 | MenuChild.Separator, 92 | MenuChild.Command(), 93 | MenuChild.Command(), 94 | #endif 95 | MenuChild.Separator, 96 | MenuChild.Command(), 97 | #if DEBUG 98 | MenuChild.Command(), 99 | #endif 100 | //MenuChild.Command(), 101 | }, 102 | }; 103 | } 104 | -------------------------------------------------------------------------------- /BrightGit.Extensibility/Helpers/LibGit2SharpHelper.cs: -------------------------------------------------------------------------------- 1 | using LibGit2Sharp; 2 | using System.Reflection; 3 | 4 | namespace BrightGit.Extensibility.Helpers; 5 | internal static class LibGit2SharpHelper 6 | { 7 | public static void RegisterNativePath(string path) 8 | { 9 | // We check first because if we overwrite after in use, it will throw an exception. 10 | if (GlobalSettings.NativeLibraryPath == null) 11 | { 12 | // Set the native library path for LibGit2Sharp when inside VS extension (VSIX). 13 | string assemblyFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); 14 | assemblyFolder = assemblyFolder[..^2]; 15 | GlobalSettings.NativeLibraryPath = Path.Combine(assemblyFolder, "runtimes", "win-x64", "native"); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /BrightGit.Extensibility/Helpers/VSHelper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.Extensibility; 2 | using Microsoft.VisualStudio.ProjectSystem.Query; 3 | 4 | namespace BrightGit.Extensibility.Helpers; 5 | public static class VSHelper 6 | { 7 | public static async Task GetSolutionPathAsync(WorkspacesExtensibility workspace, CancellationToken cancellationToken) 8 | { 9 | // Get the solution path. 10 | var solutionPath = (await workspace.QuerySolutionAsync(solution => solution.With(p => p.Path), cancellationToken)).FirstOrDefault()?.Path; 11 | return solutionPath; 12 | } 13 | 14 | public static async Task GetSolutionNameAsync(WorkspacesExtensibility workspace, CancellationToken cancellationToken) 15 | { 16 | return Path.GetFileNameWithoutExtension(await GetSolutionPathAsync(workspace, cancellationToken)); 17 | } 18 | 19 | public static async Task GetSolutionDirectoryAsync(WorkspacesExtensibility workspace, CancellationToken cancellationToken) 20 | { 21 | // Get the solution directory. 22 | var solutionDirectory = (await workspace.QuerySolutionAsync(solution => solution.With(p => p.Directory), cancellationToken)).FirstOrDefault()?.Directory; 23 | return solutionDirectory; 24 | } 25 | 26 | public static async Task> GetProjectsDirectoryAsync(WorkspacesExtensibility workspace, CancellationToken cancellationToken) 27 | { 28 | // Get the directories from all active projects in the solution. 29 | var projectsDirs = await workspace.QuerySolutionAsync(solution => solution.Get(p => p.Projects).With(p => p.Path), cancellationToken); 30 | return projectsDirs.Select(p => p.Path).ToList(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /BrightGit.Extensibility/Listeners/GitSharpHookListener.cs: -------------------------------------------------------------------------------- 1 | using BrightGit.Extensibility.Services; 2 | using Microsoft.VisualStudio.Extensibility; 3 | using Microsoft.VisualStudio.Extensibility.Editor; 4 | using System.Diagnostics; 5 | 6 | namespace BrightGit.Extensibility.Listeners; 7 | [VisualStudioContribution] 8 | public class GitSharpHookListener : ExtensionPart, ITextViewOpenClosedListener, ITextViewChangedListener 9 | { 10 | public TextViewExtensionConfiguration TextViewExtensionConfiguration => new() 11 | { 12 | AppliesTo = 13 | [ 14 | DocumentFilter.FromGlobPattern("**/*", true), 15 | ], 16 | }; 17 | 18 | public GitSharpHookListener(TraceSource traceSource, SettingsService settingsService, GitSharpHookService gitSharpHookService) 19 | { 20 | // If any of the features are enabled, we need to listen for Git hooks. 21 | if (settingsService.Data.Tabs.IsEnabled || settingsService.Data.EFCore.IsEnabled) 22 | { 23 | // TODO: 24 | // Disabled at the moment as I'm experimenting with FileWatcherService. 25 | //_ = gitSharpHookService.StartMonitoringAsync(); 26 | } 27 | } 28 | 29 | // We inherit from Listeners just to trigger our constructor and start monitoring git events. 30 | public Task TextViewChangedAsync(TextViewChangedArgs args, CancellationToken cancellationToken) 31 | { 32 | return Task.CompletedTask; 33 | } 34 | 35 | public Task TextViewClosedAsync(ITextViewSnapshot textView, CancellationToken cancellationToken) 36 | { 37 | return Task.CompletedTask; 38 | } 39 | 40 | public Task TextViewOpenedAsync(ITextViewSnapshot textView, CancellationToken cancellationToken) 41 | { 42 | return Task.CompletedTask; 43 | } 44 | } -------------------------------------------------------------------------------- /BrightGit.Extensibility/Listeners/SolutionSubscriptionObserver.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.ProjectSystem.Query; 2 | using System.Diagnostics; 3 | 4 | namespace BrightGit.Extensibility.Listeners; 5 | public class SolutionSubscriptionObserver : IObserver> 6 | { 7 | public void OnCompleted() 8 | { 9 | Debug.WriteLine("SolutionSubscriptionObserver.OnCompleted"); 10 | } 11 | 12 | public void OnError(Exception error) 13 | { } 14 | 15 | public void OnNext(IQueryResults value) 16 | { 17 | Debug.WriteLine("SolutionSubscriptionObserver.OnNext"); 18 | } 19 | } -------------------------------------------------------------------------------- /BrightGit.Extensibility/Listeners/SolutionTrackerObserver.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.ProjectSystem.Query; 2 | 3 | namespace BrightGit.Extensibility.Listeners; 4 | 5 | // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. 6 | #pragma warning disable VSEXTPREVIEW_PROJECTQUERY_TRACKING 7 | 8 | public class SolutionTrackerObserver : IObserver> 9 | { 10 | public void OnCompleted() 11 | { } 12 | 13 | public void OnError(Exception error) 14 | { } 15 | 16 | public void OnNext(IQueryTrackUpdates value) 17 | { } 18 | } 19 | 20 | // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. 21 | #pragma warning restore VSEXTPREVIEW_PROJECTQUERY_TRACKING -------------------------------------------------------------------------------- /BrightGit.Extensibility/Meta.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | namespace BrightGit.Extensibility; 4 | internal static class Meta 5 | { 6 | public static Version Version { get; } = Assembly.GetExecutingAssembly().GetName().Version; 7 | 8 | public static bool IsDebug { get; } = 9 | #if DEBUG 10 | true; 11 | #else 12 | false; 13 | #endif 14 | } 15 | -------------------------------------------------------------------------------- /BrightGit.Extensibility/Models/TabDocumentInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace BrightGit.Extensibility.Models; 4 | [DataContract] 5 | public class TabDocumentInfo 6 | { 7 | [DataMember] 8 | public string FilePath { get; set; } 9 | 10 | [DataMember] 11 | public int Index { get; set; } 12 | 13 | [DataMember] 14 | public bool IsPinned { get; set; } 15 | 16 | [DataMember] 17 | public string FileName => (!string.IsNullOrWhiteSpace(FilePath)) ? Path.GetFileName(FilePath) : string.Empty; 18 | } 19 | -------------------------------------------------------------------------------- /BrightGit.Extensibility/Models/TabsInfo.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.PlatformUI; 2 | using System.Runtime.Serialization; 3 | 4 | namespace BrightGit.Extensibility.Models; 5 | [DataContract] 6 | public class TabsInfo : ObservableObject 7 | { 8 | [DataMember] 9 | public string Id { get; set; } 10 | 11 | [DataMember] 12 | public string SolutionName { get; set; } 13 | 14 | [DataMember] 15 | public string BranchName { get; set; } 16 | 17 | [DataMember] 18 | public string Name { get => name; set => SetProperty(ref name, value); } 19 | private string name; 20 | 21 | [DataMember] 22 | public DateTime DateSaved { get => dateSaved; set => SetProperty(ref dateSaved, value); } 23 | private DateTime dateSaved; 24 | 25 | [DataMember] 26 | public List Tabs { get; set; } = new(); 27 | } 28 | -------------------------------------------------------------------------------- /BrightGit.Extensibility/Services/DialogService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.Extensibility.Shell; 2 | 3 | namespace BrightGit.Extensibility.Services; 4 | public class DialogService : IDialogService 5 | { 6 | public ShellExtensibility Shell { get; set; } 7 | 8 | public async Task ShowPromptOptionsAsync(string message, List items, CancellationToken cancellationToken) 9 | { 10 | if (message == null) 11 | message = "Choose:"; 12 | 13 | // Remove duplicates and sort the items. 14 | items = items.Distinct().ToList(); 15 | 16 | // Create a dictionary to map items to unique integers. 17 | Dictionary itemMap = new(); 18 | for (int i = 0; i < items.Count; i++) 19 | { 20 | itemMap[i] = items[i]; 21 | } 22 | 23 | // Create PromptOptions and populate it with the item map. 24 | var promptOptions = new PromptOptions 25 | { 26 | DismissedReturns = -1, 27 | DefaultChoiceIndex = 0, 28 | }; 29 | 30 | foreach (var kvp in itemMap) 31 | { 32 | promptOptions.Choices.Add(kvp.Value, kvp.Key); 33 | } 34 | 35 | // Show the prompt and get the result. 36 | var result = await Shell.ShowPromptAsync( 37 | message, 38 | promptOptions, 39 | cancellationToken); 40 | 41 | // Map the selected integer back to the corresponding item. 42 | if (itemMap.ContainsKey(result)) 43 | { 44 | return itemMap[result]; 45 | } 46 | 47 | // Return null or handle the case when no valid choice is made. 48 | return null; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /BrightGit.Extensibility/Services/EFCoreManagerService.cs: -------------------------------------------------------------------------------- 1 | namespace BrightGit.Extensibility.Services; 2 | public class EFCoreManagerService 3 | { 4 | } 5 | -------------------------------------------------------------------------------- /BrightGit.Extensibility/Services/IDialogService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.Extensibility.Shell; 2 | 3 | namespace BrightGit.Extensibility.Services; 4 | public interface IDialogService 5 | { 6 | ShellExtensibility Shell { get; set; } 7 | 8 | /// 9 | /// Only use this for a maximum of 5 items... It displays horizontally only. 10 | /// 11 | Task ShowPromptOptionsAsync(string title, List items, CancellationToken cancellationToken); 12 | } -------------------------------------------------------------------------------- /BrightGit.Extensibility/Services/SettingsData.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.PlatformUI; 2 | using System.Runtime.Serialization; 3 | 4 | namespace BrightGit.Extensibility.Services; 5 | [DataContract] 6 | public class SettingsData : ObservableObject 7 | { 8 | [DataMember] 9 | public SettingsEFCoreData EFCore { get => efcore; set => SetProperty(ref efcore, value); } 10 | private SettingsEFCoreData efcore; 11 | 12 | [DataMember] 13 | public SettingsTabsData Tabs { get => tabs; set => SetProperty(ref tabs, value); } 14 | private SettingsTabsData tabs; 15 | 16 | public SettingsData() 17 | { 18 | Tabs = new SettingsTabsData(); 19 | EFCore = new SettingsEFCoreData(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /BrightGit.Extensibility/Services/SettingsEFCoreData.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.PlatformUI; 2 | using System.Runtime.Serialization; 3 | 4 | namespace BrightGit.Extensibility.Services; 5 | [DataContract] 6 | public class SettingsEFCoreData : ObservableObject 7 | { 8 | [DataMember] 9 | public bool IsEnabled { get => isEnabled; set => SetProperty(ref isEnabled, value); } 10 | private bool isEnabled; 11 | } 12 | -------------------------------------------------------------------------------- /BrightGit.Extensibility/Services/SettingsService.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Text.Json; 3 | 4 | namespace BrightGit.Extensibility.Services; 5 | public class SettingsService 6 | { 7 | private const string fileName = "BrightGitSettings.json"; 8 | private readonly string directory; 9 | private readonly string filePath; 10 | 11 | public SettingsData Data { get; set; } = new(); 12 | 13 | public SettingsService() 14 | { 15 | directory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "BrightExtensions"); 16 | filePath = Path.Combine(directory, fileName); 17 | Load(); 18 | } 19 | 20 | public void Load() 21 | { 22 | try 23 | { 24 | // Check if directory exists. 25 | if (!Directory.Exists(directory)) 26 | Directory.CreateDirectory(directory); 27 | 28 | // Load settings. 29 | if (File.Exists(filePath)) 30 | { 31 | var json = File.ReadAllText(filePath); 32 | Data = JsonSerializer.Deserialize(json) ?? new SettingsData(); 33 | } 34 | } 35 | catch (Exception ex) 36 | { 37 | Debug.WriteLine(ex); 38 | 39 | // Use default settings. 40 | Data = new SettingsData(); 41 | } 42 | } 43 | 44 | public void Save() 45 | { 46 | try 47 | { 48 | var json = JsonSerializer.Serialize(Data, new JsonSerializerOptions { WriteIndented = true }); 49 | File.WriteAllText(filePath, json); 50 | } 51 | catch (Exception ex) 52 | { 53 | // Add log? 54 | Debug.WriteLine(ex); 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /BrightGit.Extensibility/Services/SettingsTabsData.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.PlatformUI; 2 | using System.Runtime.Serialization; 3 | 4 | namespace BrightGit.Extensibility.Services; 5 | [DataContract] 6 | public class SettingsTabsData : ObservableObject 7 | { 8 | [DataMember] 9 | public bool IsEnabled { get => isEnabled; set => SetProperty(ref isEnabled, value); } 10 | private bool isEnabled; 11 | 12 | [DataMember] 13 | public bool CloseTabsOnSave { get => closeTabsOnSave; set => SetProperty(ref closeTabsOnSave, value); } 14 | private bool closeTabsOnSave; 15 | 16 | [DataMember] 17 | public bool CloseTabsOnBranchChange { get => closeTabsOnBranchChange; set => SetProperty(ref closeTabsOnBranchChange, value); } 18 | private bool closeTabsOnBranchChange = true; 19 | } 20 | -------------------------------------------------------------------------------- /BrightGit.Extensibility/Services/TabsStorageData.cs: -------------------------------------------------------------------------------- 1 | using BrightGit.Extensibility.Models; 2 | using Microsoft.VisualStudio.PlatformUI; 3 | using System.Runtime.Serialization; 4 | 5 | namespace BrightGit.Extensibility.Services; 6 | [DataContract] 7 | public class TabsStorageData : ObservableObject 8 | { 9 | [DataMember] 10 | public List TabsBranch { get => tabsBranch; set => SetProperty(ref tabsBranch, value); } 11 | private List tabsBranch = new(); 12 | 13 | [DataMember] 14 | public List TabsCustom { get => tabsCustom; set => SetProperty(ref tabsCustom, value); } 15 | private List tabsCustom = new(); 16 | } 17 | -------------------------------------------------------------------------------- /BrightGit.Extensibility/Services/TabsStorageService.cs: -------------------------------------------------------------------------------- 1 | using BrightGit.Extensibility.Models; 2 | using System.Diagnostics; 3 | using System.Text.Json; 4 | 5 | namespace BrightGit.Extensibility.Services; 6 | public class TabsStorageService 7 | { 8 | private const string fileName = "BrightTabs.json"; 9 | private readonly string fileDirectory; 10 | private readonly string filePath; 11 | 12 | public TabsStorageData Data { get; set; } = new(); 13 | 14 | public TabsStorageService() 15 | { 16 | fileDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "BrightExtensions"); 17 | filePath = Path.Combine(fileDirectory, fileName); 18 | Load(); 19 | } 20 | 21 | public void Load() 22 | { 23 | try 24 | { 25 | // Check if directory exists. 26 | if (!Directory.Exists(fileDirectory)) 27 | Directory.CreateDirectory(fileDirectory); 28 | 29 | // Load settings. 30 | if (File.Exists(filePath)) 31 | { 32 | var json = File.ReadAllText(filePath); 33 | Data = JsonSerializer.Deserialize(json) ?? new TabsStorageData(); 34 | } 35 | } 36 | catch (Exception ex) 37 | { 38 | Debug.WriteLine(ex); 39 | 40 | // Use default settings. 41 | Data = new TabsStorageData(); 42 | } 43 | } 44 | 45 | public void Save() 46 | { 47 | try 48 | { 49 | var json = JsonSerializer.Serialize(Data, new JsonSerializerOptions { WriteIndented = true }); 50 | File.WriteAllText(filePath, json); 51 | } 52 | catch (Exception ex) 53 | { 54 | // Add log? 55 | Debug.WriteLine(ex); 56 | } 57 | } 58 | 59 | public void AddTabsCustom(TabsInfo tabsInfo) 60 | { 61 | AddTabs(tabsInfo, Data.TabsCustom); 62 | } 63 | 64 | public void AddTabsBranch(TabsInfo tabsInfo) 65 | { 66 | AddTabs(tabsInfo, Data.TabsBranch); 67 | } 68 | 69 | private void AddTabs(TabsInfo tabsInfo, List tabs) 70 | { 71 | if (tabsInfo != null) 72 | { 73 | // Remove existing tabs info if any. 74 | var existingTabsInfo = tabs.FirstOrDefault(x => x.Id == tabsInfo.Id); 75 | if (existingTabsInfo != null) 76 | tabs.Remove(existingTabsInfo); 77 | 78 | // Add new tabs info. 79 | tabs.Add(tabsInfo); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /BrightGit.Extensibility/Windows/SettingsWindow.cs: -------------------------------------------------------------------------------- 1 | namespace BrightGit.Extensibility.Windows; 2 | 3 | using BrightGit.Extensibility.Services; 4 | using Microsoft.VisualStudio.Extensibility; 5 | using Microsoft.VisualStudio.Extensibility.ToolWindows; 6 | using Microsoft.VisualStudio.RpcContracts.RemoteUI; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | /// 11 | /// A sample tool window. 12 | /// 13 | [VisualStudioContribution] 14 | public class SettingsWindow : ToolWindow 15 | { 16 | private readonly SettingsWindowContent content; 17 | 18 | /// 19 | /// Initializes a new instance of the class. 20 | /// 21 | public SettingsWindow(SettingsService settingsService) 22 | { 23 | this.Title = "Bright Git - Settings"; 24 | this.content = new SettingsWindowContent(settingsService); 25 | content.ViewModel.CloseWindow = (cancellationToken) => { _ = HideAsync(cancellationToken); }; 26 | } 27 | 28 | /// 29 | public override ToolWindowConfiguration ToolWindowConfiguration => new() 30 | { 31 | // Use this object initializer to set optional parameters for the tool window. 32 | Placement = ToolWindowPlacement.Floating, 33 | }; 34 | 35 | /// 36 | public override Task InitializeAsync(CancellationToken cancellationToken) 37 | { 38 | // Use InitializeAsync for any one-time setup or initialization. 39 | return Task.CompletedTask; 40 | } 41 | 42 | /// 43 | public override Task GetContentAsync(CancellationToken cancellationToken) 44 | { 45 | return Task.FromResult(content); 46 | } 47 | 48 | public override Task OnShowAsync(CancellationToken cancellationToken) 49 | { 50 | return base.OnShowAsync(cancellationToken); 51 | } 52 | 53 | public override Task OnHideAsync(CancellationToken cancellationToken) 54 | { 55 | // Auto save when closing? 56 | //content.ViewModel.SaveSettings(); 57 | return base.OnHideAsync(cancellationToken); 58 | } 59 | 60 | /// 61 | protected override void Dispose(bool disposing) 62 | { 63 | if (disposing) 64 | content.Dispose(); 65 | 66 | base.Dispose(disposing); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /BrightGit.Extensibility/Windows/SettingsWindowCommand.cs: -------------------------------------------------------------------------------- 1 | namespace BrightGit.Extensibility.Windows; 2 | 3 | using Microsoft.VisualStudio.Extensibility; 4 | using Microsoft.VisualStudio.Extensibility.Commands; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | [VisualStudioContribution] 9 | public class SettingsWindowCommand : Command 10 | { 11 | /// 12 | public override CommandConfiguration CommandConfiguration => new(displayName: "Settings") 13 | { 14 | //Placements = [CommandPlacement.KnownPlacements.ExtensionsMenu], 15 | Icon = new(ImageMoniker.KnownValues.Settings, IconSettings.IconAndText), 16 | }; 17 | 18 | /// 19 | public override async Task ExecuteCommandAsync(IClientContext context, CancellationToken cancellationToken) 20 | { 21 | await this.Extensibility.Shell().ShowToolWindowAsync(activate: true, cancellationToken); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /BrightGit.Extensibility/Windows/SettingsWindowContent.cs: -------------------------------------------------------------------------------- 1 | namespace BrightGit.Extensibility.Windows; 2 | 3 | using BrightGit.Extensibility.Services; 4 | using Microsoft.VisualStudio.Extensibility.UI; 5 | 6 | /// 7 | /// A remote user control to use as tool window UI content. 8 | /// 9 | internal class SettingsWindowContent : RemoteUserControl 10 | { 11 | public SettingsWindowViewModel ViewModel => base.DataContext as SettingsWindowViewModel; 12 | 13 | /// 14 | /// Initializes a new instance of the class. 15 | /// 16 | public SettingsWindowContent(SettingsService settingsService) 17 | : base(dataContext: new SettingsWindowViewModel(settingsService)) 18 | { 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /BrightGit.Extensibility/Windows/SettingsWindowContent.xaml: -------------------------------------------------------------------------------- 1 |  8 | 9 | 10 | 14 | 18 | 22 | 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 |